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