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