1 // Copyright (c) 2011 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/command_line.h" 10 #include "base/synchronization/lock.h" 11 #include "base/utf_string_conversions.h" 12 #include "chrome/browser/password_manager/password_manager.h" 13 #include "chrome/browser/tab_contents/tab_util.h" 14 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 15 #include "content/browser/browser_thread.h" 16 #include "content/browser/renderer_host/render_process_host.h" 17 #include "content/browser/renderer_host/render_view_host.h" 18 #include "content/browser/renderer_host/render_view_host_delegate.h" 19 #include "content/browser/renderer_host/resource_dispatcher_host.h" 20 #include "content/browser/renderer_host/resource_dispatcher_host_request_info.h" 21 #include "content/browser/tab_contents/constrained_window.h" 22 #include "content/browser/tab_contents/tab_contents.h" 23 #include "content/common/notification_service.h" 24 #include "grit/generated_resources.h" 25 #include "net/base/auth.h" 26 #include "net/base/net_util.h" 27 #include "net/url_request/url_request.h" 28 #include "ui/base/l10n/l10n_util.h" 29 30 using webkit_glue::PasswordForm; 31 32 class LoginHandlerImpl; 33 34 // Helper to remove the ref from an net::URLRequest to the LoginHandler. 35 // Should only be called from the IO thread, since it accesses an 36 // net::URLRequest. 37 void ResetLoginHandlerForRequest(net::URLRequest* request) { 38 ResourceDispatcherHostRequestInfo* info = 39 ResourceDispatcherHost::InfoForRequest(request); 40 if (!info) 41 return; 42 43 info->set_login_handler(NULL); 44 } 45 46 // Get the signon_realm under which this auth info should be stored. 47 // 48 // The format of the signon_realm for proxy auth is: 49 // proxy-host/auth-realm 50 // The format of the signon_realm for server auth is: 51 // url-scheme://url-host[:url-port]/auth-realm 52 // 53 // Be careful when changing this function, since you could make existing 54 // saved logins un-retrievable. 55 std::string GetSignonRealm(const GURL& url, 56 const net::AuthChallengeInfo& auth_info) { 57 std::string signon_realm; 58 if (auth_info.is_proxy) { 59 signon_realm = WideToASCII(auth_info.host_and_port); 60 signon_realm.append("/"); 61 } else { 62 // Take scheme, host, and port from the url. 63 signon_realm = url.GetOrigin().spec(); 64 // This ends with a "/". 65 } 66 signon_realm.append(WideToUTF8(auth_info.realm)); 67 return signon_realm; 68 } 69 70 // ---------------------------------------------------------------------------- 71 // LoginHandler 72 73 LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info, 74 net::URLRequest* request) 75 : handled_auth_(false), 76 dialog_(NULL), 77 auth_info_(auth_info), 78 request_(request), 79 password_manager_(NULL), 80 login_model_(NULL) { 81 // This constructor is called on the I/O thread, so we cannot load the nib 82 // here. BuildViewForPasswordManager() will be invoked on the UI thread 83 // later, so wait with loading the nib until then. 84 DCHECK(request_) << "LoginHandler constructed with NULL request"; 85 DCHECK(auth_info_) << "LoginHandler constructed with NULL auth info"; 86 87 AddRef(); // matched by LoginHandler::ReleaseSoon(). 88 89 BrowserThread::PostTask( 90 BrowserThread::UI, FROM_HERE, 91 NewRunnableMethod(this, &LoginHandler::AddObservers)); 92 93 if (!ResourceDispatcherHost::RenderViewForRequest( 94 request_, &render_process_host_id_, &tab_contents_id_)) { 95 NOTREACHED(); 96 } 97 } 98 99 LoginHandler::~LoginHandler() { 100 SetModel(NULL); 101 } 102 103 void LoginHandler::SetPasswordForm(const webkit_glue::PasswordForm& form) { 104 password_form_ = form; 105 } 106 107 void LoginHandler::SetPasswordManager(PasswordManager* password_manager) { 108 password_manager_ = password_manager; 109 } 110 111 TabContents* LoginHandler::GetTabContentsForLogin() const { 112 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 113 114 return tab_util::GetTabContentsByID(render_process_host_id_, 115 tab_contents_id_); 116 } 117 118 RenderViewHostDelegate* LoginHandler::GetRenderViewHostDelegate() const { 119 RenderViewHost* rvh = RenderViewHost::FromID(render_process_host_id_, 120 tab_contents_id_); 121 if (!rvh) 122 return NULL; 123 124 return rvh->delegate(); 125 } 126 127 void LoginHandler::SetAuth(const string16& username, 128 const string16& password) { 129 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 130 131 if (TestAndSetAuthHandled()) 132 return; 133 134 // Tell the password manager the credentials were submitted / accepted. 135 if (password_manager_) { 136 password_form_.username_value = username; 137 password_form_.password_value = password; 138 password_manager_->ProvisionallySavePassword(password_form_); 139 } 140 141 // Calling NotifyAuthSupplied() directly instead of posting a task 142 // allows other LoginHandler instances to queue their 143 // CloseContentsDeferred() before ours. Closing dialogs in the 144 // opposite order as they were created avoids races where remaining 145 // dialogs in the same tab may be briefly displayed to the user 146 // before they are removed. 147 NotifyAuthSupplied(username, password); 148 149 BrowserThread::PostTask( 150 BrowserThread::UI, FROM_HERE, 151 NewRunnableMethod(this, &LoginHandler::CloseContentsDeferred)); 152 BrowserThread::PostTask( 153 BrowserThread::IO, FROM_HERE, 154 NewRunnableMethod( 155 this, &LoginHandler::SetAuthDeferred, username, password)); 156 } 157 158 void LoginHandler::CancelAuth() { 159 if (TestAndSetAuthHandled()) 160 return; 161 162 // Similar to how we deal with notifications above in SetAuth() 163 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { 164 NotifyAuthCancelled(); 165 } else { 166 BrowserThread::PostTask( 167 BrowserThread::UI, FROM_HERE, 168 NewRunnableMethod(this, &LoginHandler::NotifyAuthCancelled)); 169 } 170 171 BrowserThread::PostTask( 172 BrowserThread::UI, FROM_HERE, 173 NewRunnableMethod(this, &LoginHandler::CloseContentsDeferred)); 174 BrowserThread::PostTask( 175 BrowserThread::IO, FROM_HERE, 176 NewRunnableMethod(this, &LoginHandler::CancelAuthDeferred)); 177 } 178 179 void LoginHandler::OnRequestCancelled() { 180 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) << 181 "Why is OnRequestCancelled called from the UI thread?"; 182 183 // Reference is no longer valid. 184 request_ = NULL; 185 186 // Give up on auth if the request was cancelled. 187 CancelAuth(); 188 } 189 190 void LoginHandler::AddObservers() { 191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 192 193 registrar_.Add(this, NotificationType::AUTH_SUPPLIED, 194 NotificationService::AllSources()); 195 registrar_.Add(this, NotificationType::AUTH_CANCELLED, 196 NotificationService::AllSources()); 197 } 198 199 void LoginHandler::RemoveObservers() { 200 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 201 202 registrar_.Remove(this, NotificationType::AUTH_SUPPLIED, 203 NotificationService::AllSources()); 204 registrar_.Remove(this, NotificationType::AUTH_CANCELLED, 205 NotificationService::AllSources()); 206 207 DCHECK(registrar_.IsEmpty()); 208 } 209 210 void LoginHandler::Observe(NotificationType type, 211 const NotificationSource& source, 212 const NotificationDetails& details) { 213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 214 DCHECK(type == NotificationType::AUTH_SUPPLIED || 215 type == NotificationType::AUTH_CANCELLED); 216 217 TabContents* requesting_contents = GetTabContentsForLogin(); 218 if (!requesting_contents) 219 return; 220 221 // Break out early if we aren't interested in the notification. 222 if (WasAuthHandled()) 223 return; 224 225 LoginNotificationDetails* login_details = 226 Details<LoginNotificationDetails>(details).ptr(); 227 228 // WasAuthHandled() should always test positive before we publish 229 // AUTH_SUPPLIED or AUTH_CANCELLED notifications. 230 DCHECK(login_details->handler() != this); 231 232 // Only handle notification for the identical auth info. 233 if (*login_details->handler()->auth_info() != *auth_info()) 234 return; 235 236 // Set or cancel the auth in this handler. 237 if (type == NotificationType::AUTH_SUPPLIED) { 238 AuthSuppliedLoginNotificationDetails* supplied_details = 239 Details<AuthSuppliedLoginNotificationDetails>(details).ptr(); 240 SetAuth(supplied_details->username(), supplied_details->password()); 241 } else { 242 DCHECK(type == NotificationType::AUTH_CANCELLED); 243 CancelAuth(); 244 } 245 } 246 247 void LoginHandler::SetModel(LoginModel* model) { 248 if (login_model_) 249 login_model_->SetObserver(NULL); 250 login_model_ = model; 251 if (login_model_) 252 login_model_->SetObserver(this); 253 } 254 255 void LoginHandler::SetDialog(ConstrainedWindow* dialog) { 256 dialog_ = dialog; 257 } 258 259 void LoginHandler::NotifyAuthNeeded() { 260 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 261 if (WasAuthHandled()) 262 return; 263 264 NotificationService* service = NotificationService::current(); 265 NavigationController* controller = NULL; 266 267 TabContents* requesting_contents = GetTabContentsForLogin(); 268 if (requesting_contents) 269 controller = &requesting_contents->controller(); 270 271 LoginNotificationDetails details(this); 272 273 service->Notify(NotificationType::AUTH_NEEDED, 274 Source<NavigationController>(controller), 275 Details<LoginNotificationDetails>(&details)); 276 } 277 278 void LoginHandler::NotifyAuthCancelled() { 279 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 280 DCHECK(WasAuthHandled()); 281 282 NotificationService* service = NotificationService::current(); 283 NavigationController* controller = NULL; 284 285 TabContents* requesting_contents = GetTabContentsForLogin(); 286 if (requesting_contents) 287 controller = &requesting_contents->controller(); 288 289 LoginNotificationDetails details(this); 290 291 service->Notify(NotificationType::AUTH_CANCELLED, 292 Source<NavigationController>(controller), 293 Details<LoginNotificationDetails>(&details)); 294 } 295 296 void LoginHandler::NotifyAuthSupplied(const string16& username, 297 const string16& password) { 298 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 299 DCHECK(WasAuthHandled()); 300 301 TabContents* requesting_contents = GetTabContentsForLogin(); 302 if (!requesting_contents) 303 return; 304 305 NotificationService* service = NotificationService::current(); 306 NavigationController* controller = &requesting_contents->controller(); 307 AuthSuppliedLoginNotificationDetails details(this, username, password); 308 309 service->Notify(NotificationType::AUTH_SUPPLIED, 310 Source<NavigationController>(controller), 311 Details<AuthSuppliedLoginNotificationDetails>(&details)); 312 } 313 314 void LoginHandler::ReleaseSoon() { 315 if (!TestAndSetAuthHandled()) { 316 BrowserThread::PostTask( 317 BrowserThread::IO, FROM_HERE, 318 NewRunnableMethod(this, &LoginHandler::CancelAuthDeferred)); 319 BrowserThread::PostTask( 320 BrowserThread::UI, FROM_HERE, 321 NewRunnableMethod(this, &LoginHandler::NotifyAuthCancelled)); 322 } 323 324 BrowserThread::PostTask( 325 BrowserThread::UI, FROM_HERE, 326 NewRunnableMethod(this, &LoginHandler::RemoveObservers)); 327 328 // Delete this object once all InvokeLaters have been called. 329 BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this); 330 } 331 332 // Returns whether authentication had been handled (SetAuth or CancelAuth). 333 bool LoginHandler::WasAuthHandled() const { 334 base::AutoLock lock(handled_auth_lock_); 335 bool was_handled = handled_auth_; 336 return was_handled; 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(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 // The hosting ConstrainedWindow may have been freed. 375 if (dialog_) 376 dialog_->CloseConstrainedWindow(); 377 } 378 379 // ---------------------------------------------------------------------------- 380 // LoginDialogTask 381 382 // This task is run on the UI thread and creates a constrained window with 383 // a LoginView to prompt the user. The response will be sent to LoginHandler, 384 // which then routes it to the net::URLRequest on the I/O thread. 385 class LoginDialogTask : public Task { 386 public: 387 LoginDialogTask(const GURL& request_url, 388 net::AuthChallengeInfo* auth_info, 389 LoginHandler* handler) 390 : request_url_(request_url), auth_info_(auth_info), handler_(handler) { 391 } 392 virtual ~LoginDialogTask() { 393 } 394 395 void Run() { 396 TabContents* parent_contents = handler_->GetTabContentsForLogin(); 397 if (!parent_contents || handler_->WasAuthHandled()) { 398 // The request may have been cancelled, or it may be for a renderer 399 // not hosted by a tab (e.g. an extension). Cancel just in case 400 // (cancelling twice is a no-op). 401 handler_->CancelAuth(); 402 return; 403 } 404 405 // Tell the password manager to look for saved passwords. 406 TabContentsWrapper* wrapper = 407 TabContentsWrapper::GetCurrentWrapperForContents(parent_contents); 408 if (!wrapper) 409 return; 410 PasswordManager* password_manager = wrapper->password_manager(); 411 std::vector<PasswordForm> v; 412 MakeInputForPasswordManager(&v); 413 password_manager->OnPasswordFormsFound(v); 414 handler_->SetPasswordManager(password_manager); 415 416 string16 host_and_port_hack16 = WideToUTF16Hack(auth_info_->host_and_port); 417 string16 realm_hack16 = WideToUTF16Hack(auth_info_->realm); 418 string16 explanation = realm_hack16.empty() ? 419 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM, 420 host_and_port_hack16) : 421 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION, 422 host_and_port_hack16, 423 realm_hack16); 424 handler_->BuildViewForPasswordManager(password_manager, explanation); 425 } 426 427 private: 428 // Helper to create a PasswordForm and stuff it into a vector as input 429 // for PasswordManager::PasswordFormsFound, the hook into PasswordManager. 430 void MakeInputForPasswordManager( 431 std::vector<PasswordForm>* password_manager_input) { 432 PasswordForm dialog_form; 433 if (LowerCaseEqualsASCII(auth_info_->scheme, "basic")) { 434 dialog_form.scheme = PasswordForm::SCHEME_BASIC; 435 } else if (LowerCaseEqualsASCII(auth_info_->scheme, "digest")) { 436 dialog_form.scheme = PasswordForm::SCHEME_DIGEST; 437 } else { 438 dialog_form.scheme = PasswordForm::SCHEME_OTHER; 439 } 440 std::string host_and_port(WideToASCII(auth_info_->host_and_port)); 441 if (auth_info_->is_proxy) { 442 std::string origin = host_and_port; 443 // We don't expect this to already start with http:// or https://. 444 DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0); 445 origin = std::string("http://") + origin; 446 dialog_form.origin = GURL(origin); 447 } else if (net::GetHostAndPort(request_url_) != host_and_port) { 448 dialog_form.origin = GURL(); 449 NOTREACHED(); // crbug.com/32718 450 } else { 451 dialog_form.origin = GURL(request_url_.scheme() + "://" + host_and_port); 452 } 453 dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info_); 454 password_manager_input->push_back(dialog_form); 455 // Set the password form for the handler (by copy). 456 handler_->SetPasswordForm(dialog_form); 457 } 458 459 // The url from the net::URLRequest initiating the auth challenge. 460 GURL request_url_; 461 462 // Info about who/where/what is asking for authentication. 463 scoped_refptr<net::AuthChallengeInfo> auth_info_; 464 465 // Where to send the authentication when obtained. 466 // This is owned by the ResourceDispatcherHost that invoked us. 467 LoginHandler* handler_; 468 469 DISALLOW_COPY_AND_ASSIGN(LoginDialogTask); 470 }; 471 472 // ---------------------------------------------------------------------------- 473 // Public API 474 475 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info, 476 net::URLRequest* request) { 477 LoginHandler* handler = LoginHandler::Create(auth_info, request); 478 BrowserThread::PostTask( 479 BrowserThread::UI, FROM_HERE, new LoginDialogTask( 480 request->url(), auth_info, handler)); 481 return handler; 482 } 483