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