1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/login/login_prompt.h" 6 7 #include <vector> 8 9 #include "base/bind.h" 10 #include "base/command_line.h" 11 #include "base/strings/utf_string_conversions.h" 12 #include "base/synchronization/lock.h" 13 #include "chrome/browser/chrome_notification_types.h" 14 #include "chrome/browser/password_manager/chrome_password_manager_client.h" 15 #include "chrome/browser/prerender/prerender_contents.h" 16 #include "chrome/browser/tab_contents/tab_util.h" 17 #include "chrome/browser/ui/login/login_interstitial_delegate.h" 18 #include "components/password_manager/core/browser/browser_save_password_progress_logger.h" 19 #include "components/password_manager/core/browser/password_manager.h" 20 #include "content/public/browser/browser_thread.h" 21 #include "content/public/browser/notification_registrar.h" 22 #include "content/public/browser/notification_service.h" 23 #include "content/public/browser/render_frame_host.h" 24 #include "content/public/browser/resource_dispatcher_host.h" 25 #include "content/public/browser/resource_request_info.h" 26 #include "content/public/browser/web_contents.h" 27 #include "grit/generated_resources.h" 28 #include "net/base/auth.h" 29 #include "net/base/load_flags.h" 30 #include "net/base/net_util.h" 31 #include "net/http/http_transaction_factory.h" 32 #include "net/url_request/url_request.h" 33 #include "net/url_request/url_request_context.h" 34 #include "ui/base/l10n/l10n_util.h" 35 #include "ui/gfx/text_elider.h" 36 37 using autofill::PasswordForm; 38 using content::BrowserThread; 39 using content::NavigationController; 40 using content::RenderViewHost; 41 using content::RenderViewHostDelegate; 42 using content::ResourceDispatcherHost; 43 using content::ResourceRequestInfo; 44 using content::WebContents; 45 46 class LoginHandlerImpl; 47 48 // Helper to remove the ref from an net::URLRequest to the LoginHandler. 49 // Should only be called from the IO thread, since it accesses an 50 // net::URLRequest. 51 void ResetLoginHandlerForRequest(net::URLRequest* request) { 52 ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request); 53 } 54 55 // Get the signon_realm under which this auth info should be stored. 56 // 57 // The format of the signon_realm for proxy auth is: 58 // proxy-host/auth-realm 59 // The format of the signon_realm for server auth is: 60 // url-scheme://url-host[:url-port]/auth-realm 61 // 62 // Be careful when changing this function, since you could make existing 63 // saved logins un-retrievable. 64 std::string GetSignonRealm(const GURL& url, 65 const net::AuthChallengeInfo& auth_info) { 66 std::string signon_realm; 67 if (auth_info.is_proxy) { 68 signon_realm = auth_info.challenger.ToString(); 69 signon_realm.append("/"); 70 } else { 71 // Take scheme, host, and port from the url. 72 signon_realm = url.GetOrigin().spec(); 73 // This ends with a "/". 74 } 75 signon_realm.append(auth_info.realm); 76 return signon_realm; 77 } 78 79 // ---------------------------------------------------------------------------- 80 // LoginHandler 81 82 LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info, 83 net::URLRequest* request) 84 : handled_auth_(false), 85 auth_info_(auth_info), 86 request_(request), 87 http_network_session_( 88 request_->context()->http_transaction_factory()->GetSession()), 89 password_manager_(NULL), 90 login_model_(NULL) { 91 // This constructor is called on the I/O thread, so we cannot load the nib 92 // here. BuildViewForPasswordManager() will be invoked on the UI thread 93 // later, so wait with loading the nib until then. 94 DCHECK(request_) << "LoginHandler constructed with NULL request"; 95 DCHECK(auth_info_.get()) << "LoginHandler constructed with NULL auth info"; 96 97 AddRef(); // matched by LoginHandler::ReleaseSoon(). 98 99 BrowserThread::PostTask( 100 BrowserThread::UI, FROM_HERE, 101 base::Bind(&LoginHandler::AddObservers, this)); 102 103 if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame( 104 &render_process_host_id_, &render_frame_id_)) { 105 NOTREACHED(); 106 } 107 } 108 109 void LoginHandler::OnRequestCancelled() { 110 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) << 111 "Why is OnRequestCancelled called from the UI thread?"; 112 113 // Reference is no longer valid. 114 request_ = NULL; 115 116 // Give up on auth if the request was cancelled. 117 CancelAuth(); 118 } 119 120 void LoginHandler::SetPasswordForm(const autofill::PasswordForm& form) { 121 password_form_ = form; 122 } 123 124 void LoginHandler::SetPasswordManager( 125 password_manager::PasswordManager* password_manager) { 126 password_manager_ = password_manager; 127 } 128 129 WebContents* LoginHandler::GetWebContentsForLogin() const { 130 DCHECK_CURRENTLY_ON(BrowserThread::UI); 131 132 content::RenderFrameHost* rfh = content::RenderFrameHost::FromID( 133 render_process_host_id_, render_frame_id_); 134 return WebContents::FromRenderFrameHost(rfh); 135 } 136 137 void LoginHandler::SetAuth(const base::string16& username, 138 const base::string16& password) { 139 DCHECK_CURRENTLY_ON(BrowserThread::UI); 140 141 scoped_ptr<password_manager::BrowserSavePasswordProgressLogger> logger; 142 if (password_manager_ && password_manager_->client()->IsLoggingActive()) { 143 logger.reset(new password_manager::BrowserSavePasswordProgressLogger( 144 password_manager_->client())); 145 logger->LogMessage( 146 autofill::SavePasswordProgressLogger::STRING_SET_AUTH_METHOD); 147 } 148 149 bool already_handled = TestAndSetAuthHandled(); 150 if (logger) { 151 logger->LogBoolean( 152 autofill::SavePasswordProgressLogger::STRING_AUTHENTICATION_HANDLED, 153 already_handled); 154 } 155 if (already_handled) 156 return; 157 158 // Tell the password manager the credentials were submitted / accepted. 159 if (password_manager_) { 160 password_form_.username_value = username; 161 password_form_.password_value = password; 162 password_manager_->ProvisionallySavePassword(password_form_); 163 if (logger) { 164 logger->LogPasswordForm( 165 autofill::SavePasswordProgressLogger::STRING_LOGINHANDLER_FORM, 166 password_form_); 167 } 168 } 169 170 // Calling NotifyAuthSupplied() directly instead of posting a task 171 // allows other LoginHandler instances to queue their 172 // CloseContentsDeferred() before ours. Closing dialogs in the 173 // opposite order as they were created avoids races where remaining 174 // dialogs in the same tab may be briefly displayed to the user 175 // before they are removed. 176 NotifyAuthSupplied(username, password); 177 178 BrowserThread::PostTask( 179 BrowserThread::UI, FROM_HERE, 180 base::Bind(&LoginHandler::CloseContentsDeferred, this)); 181 BrowserThread::PostTask( 182 BrowserThread::IO, FROM_HERE, 183 base::Bind(&LoginHandler::SetAuthDeferred, this, username, password)); 184 } 185 186 void LoginHandler::CancelAuth() { 187 if (TestAndSetAuthHandled()) 188 return; 189 190 // Similar to how we deal with notifications above in SetAuth() 191 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { 192 NotifyAuthCancelled(); 193 } else { 194 BrowserThread::PostTask( 195 BrowserThread::UI, FROM_HERE, 196 base::Bind(&LoginHandler::NotifyAuthCancelled, this)); 197 } 198 199 BrowserThread::PostTask( 200 BrowserThread::UI, FROM_HERE, 201 base::Bind(&LoginHandler::CloseContentsDeferred, this)); 202 BrowserThread::PostTask( 203 BrowserThread::IO, FROM_HERE, 204 base::Bind(&LoginHandler::CancelAuthDeferred, this)); 205 } 206 207 208 void LoginHandler::Observe(int type, 209 const content::NotificationSource& source, 210 const content::NotificationDetails& details) { 211 DCHECK_CURRENTLY_ON(BrowserThread::UI); 212 DCHECK(type == chrome::NOTIFICATION_AUTH_SUPPLIED || 213 type == chrome::NOTIFICATION_AUTH_CANCELLED); 214 215 WebContents* requesting_contents = GetWebContentsForLogin(); 216 if (!requesting_contents) 217 return; 218 219 // Break out early if we aren't interested in the notification. 220 if (WasAuthHandled()) 221 return; 222 223 LoginNotificationDetails* login_details = 224 content::Details<LoginNotificationDetails>(details).ptr(); 225 226 // WasAuthHandled() should always test positive before we publish 227 // AUTH_SUPPLIED or AUTH_CANCELLED notifications. 228 DCHECK(login_details->handler() != this); 229 230 // Only handle notification for the identical auth info. 231 if (!login_details->handler()->auth_info()->Equals(*auth_info())) 232 return; 233 234 // Ignore login notification events from other profiles. 235 if (login_details->handler()->http_network_session_ != 236 http_network_session_) 237 return; 238 239 // Set or cancel the auth in this handler. 240 if (type == chrome::NOTIFICATION_AUTH_SUPPLIED) { 241 AuthSuppliedLoginNotificationDetails* supplied_details = 242 content::Details<AuthSuppliedLoginNotificationDetails>(details).ptr(); 243 SetAuth(supplied_details->username(), supplied_details->password()); 244 } else { 245 DCHECK(type == chrome::NOTIFICATION_AUTH_CANCELLED); 246 CancelAuth(); 247 } 248 } 249 250 // Returns whether authentication had been handled (SetAuth or CancelAuth). 251 bool LoginHandler::WasAuthHandled() const { 252 base::AutoLock lock(handled_auth_lock_); 253 bool was_handled = handled_auth_; 254 return was_handled; 255 } 256 257 LoginHandler::~LoginHandler() { 258 SetModel(NULL); 259 } 260 261 void LoginHandler::SetModel(password_manager::LoginModel* model) { 262 if (login_model_) 263 login_model_->RemoveObserver(this); 264 login_model_ = model; 265 if (login_model_) 266 login_model_->AddObserver(this); 267 } 268 269 void LoginHandler::NotifyAuthNeeded() { 270 DCHECK_CURRENTLY_ON(BrowserThread::UI); 271 if (WasAuthHandled()) 272 return; 273 274 content::NotificationService* service = 275 content::NotificationService::current(); 276 NavigationController* controller = NULL; 277 278 WebContents* requesting_contents = GetWebContentsForLogin(); 279 if (requesting_contents) 280 controller = &requesting_contents->GetController(); 281 282 LoginNotificationDetails details(this); 283 284 service->Notify(chrome::NOTIFICATION_AUTH_NEEDED, 285 content::Source<NavigationController>(controller), 286 content::Details<LoginNotificationDetails>(&details)); 287 } 288 289 void LoginHandler::ReleaseSoon() { 290 if (!TestAndSetAuthHandled()) { 291 BrowserThread::PostTask( 292 BrowserThread::IO, FROM_HERE, 293 base::Bind(&LoginHandler::CancelAuthDeferred, this)); 294 BrowserThread::PostTask( 295 BrowserThread::UI, FROM_HERE, 296 base::Bind(&LoginHandler::NotifyAuthCancelled, this)); 297 } 298 299 BrowserThread::PostTask( 300 BrowserThread::UI, FROM_HERE, 301 base::Bind(&LoginHandler::RemoveObservers, this)); 302 303 // Delete this object once all InvokeLaters have been called. 304 BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this); 305 } 306 307 void LoginHandler::AddObservers() { 308 DCHECK_CURRENTLY_ON(BrowserThread::UI); 309 310 // This is probably OK; we need to listen to everything and we break out of 311 // the Observe() if we aren't handling the same auth_info(). 312 registrar_.reset(new content::NotificationRegistrar); 313 registrar_->Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED, 314 content::NotificationService::AllBrowserContextsAndSources()); 315 registrar_->Add(this, chrome::NOTIFICATION_AUTH_CANCELLED, 316 content::NotificationService::AllBrowserContextsAndSources()); 317 } 318 319 void LoginHandler::RemoveObservers() { 320 DCHECK_CURRENTLY_ON(BrowserThread::UI); 321 322 registrar_.reset(); 323 } 324 325 void LoginHandler::NotifyAuthSupplied(const base::string16& username, 326 const base::string16& password) { 327 DCHECK_CURRENTLY_ON(BrowserThread::UI); 328 DCHECK(WasAuthHandled()); 329 330 WebContents* requesting_contents = GetWebContentsForLogin(); 331 if (!requesting_contents) 332 return; 333 334 content::NotificationService* service = 335 content::NotificationService::current(); 336 NavigationController* controller = 337 &requesting_contents->GetController(); 338 AuthSuppliedLoginNotificationDetails details(this, username, password); 339 340 service->Notify( 341 chrome::NOTIFICATION_AUTH_SUPPLIED, 342 content::Source<NavigationController>(controller), 343 content::Details<AuthSuppliedLoginNotificationDetails>(&details)); 344 } 345 346 void LoginHandler::NotifyAuthCancelled() { 347 DCHECK_CURRENTLY_ON(BrowserThread::UI); 348 DCHECK(WasAuthHandled()); 349 350 content::NotificationService* service = 351 content::NotificationService::current(); 352 NavigationController* controller = NULL; 353 354 WebContents* requesting_contents = GetWebContentsForLogin(); 355 if (requesting_contents) 356 controller = &requesting_contents->GetController(); 357 358 LoginNotificationDetails details(this); 359 360 service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED, 361 content::Source<NavigationController>(controller), 362 content::Details<LoginNotificationDetails>(&details)); 363 } 364 365 // Marks authentication as handled and returns the previous handled state. 366 bool LoginHandler::TestAndSetAuthHandled() { 367 base::AutoLock lock(handled_auth_lock_); 368 bool was_handled = handled_auth_; 369 handled_auth_ = true; 370 return was_handled; 371 } 372 373 // Calls SetAuth from the IO loop. 374 void LoginHandler::SetAuthDeferred(const base::string16& username, 375 const base::string16& password) { 376 DCHECK_CURRENTLY_ON(BrowserThread::IO); 377 378 if (request_) { 379 request_->SetAuth(net::AuthCredentials(username, password)); 380 ResetLoginHandlerForRequest(request_); 381 } 382 } 383 384 // Calls CancelAuth from the IO loop. 385 void LoginHandler::CancelAuthDeferred() { 386 DCHECK_CURRENTLY_ON(BrowserThread::IO); 387 388 if (request_) { 389 request_->CancelAuth(); 390 // Verify that CancelAuth doesn't destroy the request via our delegate. 391 DCHECK(request_ != NULL); 392 ResetLoginHandlerForRequest(request_); 393 } 394 } 395 396 // Closes the view_contents from the UI loop. 397 void LoginHandler::CloseContentsDeferred() { 398 DCHECK_CURRENTLY_ON(BrowserThread::UI); 399 400 CloseDialog(); 401 402 WebContents* requesting_contents = GetWebContentsForLogin(); 403 if (!requesting_contents) 404 return; 405 // If a (blank) login interstitial was displayed, proceed so that the 406 // navigation is committed. 407 content::InterstitialPage* interstitial_page = 408 requesting_contents->GetInterstitialPage(); 409 if (interstitial_page) 410 interstitial_page->Proceed(); 411 } 412 413 // Helper to create a PasswordForm and stuff it into a vector as input 414 // for PasswordManager::PasswordFormsParsed, the hook into PasswordManager. 415 void MakeInputForPasswordManager( 416 const GURL& request_url, 417 net::AuthChallengeInfo* auth_info, 418 LoginHandler* handler, 419 std::vector<PasswordForm>* password_manager_input) { 420 PasswordForm dialog_form; 421 if (LowerCaseEqualsASCII(auth_info->scheme, "basic")) { 422 dialog_form.scheme = PasswordForm::SCHEME_BASIC; 423 } else if (LowerCaseEqualsASCII(auth_info->scheme, "digest")) { 424 dialog_form.scheme = PasswordForm::SCHEME_DIGEST; 425 } else { 426 dialog_form.scheme = PasswordForm::SCHEME_OTHER; 427 } 428 std::string host_and_port(auth_info->challenger.ToString()); 429 if (auth_info->is_proxy) { 430 std::string origin = host_and_port; 431 // We don't expect this to already start with http:// or https://. 432 DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0); 433 origin = std::string("http://") + origin; 434 dialog_form.origin = GURL(origin); 435 } else if (!auth_info->challenger.Equals( 436 net::HostPortPair::FromURL(request_url))) { 437 dialog_form.origin = GURL(); 438 NOTREACHED(); // crbug.com/32718 439 } else { 440 dialog_form.origin = GURL(request_url.scheme() + "://" + host_and_port); 441 } 442 dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info); 443 password_manager_input->push_back(dialog_form); 444 // Set the password form for the handler (by copy). 445 handler->SetPasswordForm(dialog_form); 446 } 447 448 void ShowLoginPrompt(const GURL& request_url, 449 net::AuthChallengeInfo* auth_info, 450 LoginHandler* handler) { 451 DCHECK_CURRENTLY_ON(BrowserThread::UI); 452 WebContents* parent_contents = handler->GetWebContentsForLogin(); 453 if (!parent_contents) 454 return; 455 prerender::PrerenderContents* prerender_contents = 456 prerender::PrerenderContents::FromWebContents(parent_contents); 457 if (prerender_contents) { 458 prerender_contents->Destroy(prerender::FINAL_STATUS_AUTH_NEEDED); 459 return; 460 } 461 462 password_manager::PasswordManager* password_manager = 463 ChromePasswordManagerClient::GetManagerFromWebContents(parent_contents); 464 if (!password_manager) { 465 // Same logic as above. 466 handler->CancelAuth(); 467 return; 468 } 469 470 // Tell the password manager to look for saved passwords. 471 std::vector<PasswordForm> v; 472 MakeInputForPasswordManager(request_url, auth_info, handler, &v); 473 password_manager->OnPasswordFormsParsed(v); 474 handler->SetPasswordManager(password_manager); 475 476 // The realm is controlled by the remote server, so there is no reason 477 // to believe it is of a reasonable length. 478 base::string16 elided_realm; 479 gfx::ElideString(base::UTF8ToUTF16(auth_info->realm), 120, &elided_realm); 480 481 base::string16 host_and_port = base::ASCIIToUTF16( 482 request_url.scheme() + "://" + auth_info->challenger.ToString()); 483 base::string16 explanation = elided_realm.empty() ? 484 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM, 485 host_and_port) : 486 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION, 487 host_and_port, 488 elided_realm); 489 handler->BuildViewForPasswordManager(password_manager, explanation); 490 } 491 492 // This callback is run on the UI thread and creates a constrained window with 493 // a LoginView to prompt the user. If the prompt is triggered because of 494 // a cross origin navigation in the main frame, a blank interstitial is first 495 // created which in turn creates the LoginView. Otherwise, a LoginView is 496 // directly in this callback. In both cases, the response will be sent to 497 // LoginHandler, which then routes it to the net::URLRequest on the I/O thread. 498 void LoginDialogCallback(const GURL& request_url, 499 net::AuthChallengeInfo* auth_info, 500 LoginHandler* handler, 501 bool is_main_frame) { 502 DCHECK_CURRENTLY_ON(BrowserThread::UI); 503 WebContents* parent_contents = handler->GetWebContentsForLogin(); 504 if (!parent_contents || handler->WasAuthHandled()) { 505 // The request may have been cancelled, or it may be for a renderer 506 // not hosted by a tab (e.g. an extension). Cancel just in case 507 // (cancelling twice is a no-op). 508 handler->CancelAuth(); 509 return; 510 } 511 512 if (is_main_frame && 513 parent_contents->GetVisibleURL().GetOrigin() != request_url.GetOrigin()) { 514 // Show a blank interstitial for main-frame, cross origin requests 515 // so that the correct URL is shown in the omnibox. 516 base::Closure callback = base::Bind(&ShowLoginPrompt, 517 request_url, 518 make_scoped_refptr(auth_info), 519 make_scoped_refptr(handler)); 520 // This is owned by the interstitial it creates. 521 new LoginInterstitialDelegate(parent_contents, 522 request_url, 523 callback); 524 } else { 525 ShowLoginPrompt(request_url, 526 auth_info, 527 handler); 528 } 529 } 530 531 // ---------------------------------------------------------------------------- 532 // Public API 533 534 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info, 535 net::URLRequest* request) { 536 bool is_main_frame = (request->load_flags() & net::LOAD_MAIN_FRAME) != 0; 537 LoginHandler* handler = LoginHandler::Create(auth_info, request); 538 BrowserThread::PostTask( 539 BrowserThread::UI, FROM_HERE, 540 base::Bind(&LoginDialogCallback, request->url(), 541 make_scoped_refptr(auth_info), make_scoped_refptr(handler), 542 is_main_frame)); 543 return handler; 544 } 545