Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / ui / login / login_prompt.cc
blob863ee166429d97ae904ae2b5105f4024d5314507
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 #if defined(ENABLE_EXTENSIONS)
40 #include "components/guest_view/browser/guest_view_base.h"
41 #endif
43 using autofill::PasswordForm;
44 using content::BrowserThread;
45 using content::NavigationController;
46 using content::RenderViewHost;
47 using content::RenderViewHostDelegate;
48 using content::ResourceDispatcherHost;
49 using content::ResourceRequestInfo;
50 using content::WebContents;
52 class LoginHandlerImpl;
54 // Helper to remove the ref from an net::URLRequest to the LoginHandler.
55 // Should only be called from the IO thread, since it accesses an
56 // net::URLRequest.
57 void ResetLoginHandlerForRequest(net::URLRequest* request) {
58 ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request);
61 // Get the signon_realm under which this auth info should be stored.
63 // The format of the signon_realm for proxy auth is:
64 // proxy-host/auth-realm
65 // The format of the signon_realm for server auth is:
66 // url-scheme://url-host[:url-port]/auth-realm
68 // Be careful when changing this function, since you could make existing
69 // saved logins un-retrievable.
70 std::string GetSignonRealm(const GURL& url,
71 const net::AuthChallengeInfo& auth_info) {
72 std::string signon_realm;
73 if (auth_info.is_proxy) {
74 signon_realm = auth_info.challenger.ToString();
75 signon_realm.append("/");
76 } else {
77 // Take scheme, host, and port from the url.
78 signon_realm = url.GetOrigin().spec();
79 // This ends with a "/".
81 signon_realm.append(auth_info.realm);
82 return signon_realm;
85 // ----------------------------------------------------------------------------
86 // LoginHandler
88 LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
89 net::URLRequest* request)
90 : handled_auth_(false),
91 auth_info_(auth_info),
92 request_(request),
93 http_network_session_(
94 request_->context()->http_transaction_factory()->GetSession()),
95 password_manager_(NULL),
96 login_model_(NULL) {
97 // This constructor is called on the I/O thread, so we cannot load the nib
98 // here. BuildViewForPasswordManager() will be invoked on the UI thread
99 // later, so wait with loading the nib until then.
100 DCHECK(request_) << "LoginHandler constructed with NULL request";
101 DCHECK(auth_info_.get()) << "LoginHandler constructed with NULL auth info";
103 AddRef(); // matched by LoginHandler::ReleaseSoon().
105 BrowserThread::PostTask(
106 BrowserThread::UI, FROM_HERE,
107 base::Bind(&LoginHandler::AddObservers, this));
109 if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame(
110 &render_process_host_id_, &render_frame_id_)) {
111 NOTREACHED();
115 void LoginHandler::OnRequestCancelled() {
116 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) <<
117 "Why is OnRequestCancelled called from the UI thread?";
119 // Reference is no longer valid.
120 request_ = NULL;
122 // Give up on auth if the request was cancelled.
123 CancelAuth();
126 void LoginHandler::SetPasswordForm(const autofill::PasswordForm& form) {
127 password_form_ = form;
130 void LoginHandler::SetPasswordManager(
131 password_manager::PasswordManager* password_manager) {
132 password_manager_ = password_manager;
135 WebContents* LoginHandler::GetWebContentsForLogin() const {
136 DCHECK_CURRENTLY_ON(BrowserThread::UI);
138 content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
139 render_process_host_id_, render_frame_id_);
140 return WebContents::FromRenderFrameHost(rfh);
143 password_manager::ContentPasswordManagerDriver*
144 LoginHandler::GetPasswordManagerDriverForLogin() {
145 content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
146 render_process_host_id_, render_frame_id_);
147 return password_manager::ContentPasswordManagerDriver::GetForRenderFrameHost(
148 rfh);
151 void LoginHandler::SetAuth(const base::string16& username,
152 const base::string16& password) {
153 DCHECK_CURRENTLY_ON(BrowserThread::UI);
155 scoped_ptr<password_manager::BrowserSavePasswordProgressLogger> logger;
156 if (password_manager_ && password_manager_->client()->IsLoggingActive()) {
157 logger.reset(new password_manager::BrowserSavePasswordProgressLogger(
158 password_manager_->client()));
159 logger->LogMessage(
160 autofill::SavePasswordProgressLogger::STRING_SET_AUTH_METHOD);
163 bool already_handled = TestAndSetAuthHandled();
164 if (logger) {
165 logger->LogBoolean(
166 autofill::SavePasswordProgressLogger::STRING_AUTHENTICATION_HANDLED,
167 already_handled);
169 if (already_handled)
170 return;
172 // Tell the password manager the credentials were submitted / accepted.
173 if (password_manager_) {
174 password_form_.username_value = username;
175 password_form_.password_value = password;
176 password_manager_->ProvisionallySavePassword(password_form_);
177 if (logger) {
178 logger->LogPasswordForm(
179 autofill::SavePasswordProgressLogger::STRING_LOGINHANDLER_FORM,
180 password_form_);
184 // Calling NotifyAuthSupplied() directly instead of posting a task
185 // allows other LoginHandler instances to queue their
186 // CloseContentsDeferred() before ours. Closing dialogs in the
187 // opposite order as they were created avoids races where remaining
188 // dialogs in the same tab may be briefly displayed to the user
189 // before they are removed.
190 NotifyAuthSupplied(username, password);
192 BrowserThread::PostTask(
193 BrowserThread::UI, FROM_HERE,
194 base::Bind(&LoginHandler::CloseContentsDeferred, this));
195 BrowserThread::PostTask(
196 BrowserThread::IO, FROM_HERE,
197 base::Bind(&LoginHandler::SetAuthDeferred, this, username, password));
200 void LoginHandler::CancelAuth() {
201 if (TestAndSetAuthHandled())
202 return;
204 // Similar to how we deal with notifications above in SetAuth()
205 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
206 NotifyAuthCancelled();
207 } else {
208 BrowserThread::PostTask(
209 BrowserThread::UI, FROM_HERE,
210 base::Bind(&LoginHandler::NotifyAuthCancelled, this));
213 BrowserThread::PostTask(
214 BrowserThread::UI, FROM_HERE,
215 base::Bind(&LoginHandler::CloseContentsDeferred, this));
216 BrowserThread::PostTask(
217 BrowserThread::IO, FROM_HERE,
218 base::Bind(&LoginHandler::CancelAuthDeferred, this));
222 void LoginHandler::Observe(int type,
223 const content::NotificationSource& source,
224 const content::NotificationDetails& details) {
225 DCHECK_CURRENTLY_ON(BrowserThread::UI);
226 DCHECK(type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
227 type == chrome::NOTIFICATION_AUTH_CANCELLED);
229 WebContents* requesting_contents = GetWebContentsForLogin();
230 if (!requesting_contents)
231 return;
233 // Break out early if we aren't interested in the notification.
234 if (WasAuthHandled())
235 return;
237 LoginNotificationDetails* login_details =
238 content::Details<LoginNotificationDetails>(details).ptr();
240 // WasAuthHandled() should always test positive before we publish
241 // AUTH_SUPPLIED or AUTH_CANCELLED notifications.
242 DCHECK(login_details->handler() != this);
244 // Only handle notification for the identical auth info.
245 if (!login_details->handler()->auth_info()->Equals(*auth_info()))
246 return;
248 // Ignore login notification events from other profiles.
249 if (login_details->handler()->http_network_session_ !=
250 http_network_session_)
251 return;
253 // Set or cancel the auth in this handler.
254 if (type == chrome::NOTIFICATION_AUTH_SUPPLIED) {
255 AuthSuppliedLoginNotificationDetails* supplied_details =
256 content::Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
257 SetAuth(supplied_details->username(), supplied_details->password());
258 } else {
259 DCHECK(type == chrome::NOTIFICATION_AUTH_CANCELLED);
260 CancelAuth();
264 // Returns whether authentication had been handled (SetAuth or CancelAuth).
265 bool LoginHandler::WasAuthHandled() const {
266 base::AutoLock lock(handled_auth_lock_);
267 bool was_handled = handled_auth_;
268 return was_handled;
271 LoginHandler::~LoginHandler() {
272 SetModel(NULL);
275 void LoginHandler::SetModel(password_manager::LoginModel* model) {
276 if (login_model_)
277 login_model_->RemoveObserver(this);
278 login_model_ = model;
279 if (login_model_)
280 login_model_->AddObserver(this);
283 void LoginHandler::NotifyAuthNeeded() {
284 DCHECK_CURRENTLY_ON(BrowserThread::UI);
285 if (WasAuthHandled())
286 return;
288 content::NotificationService* service =
289 content::NotificationService::current();
290 NavigationController* controller = NULL;
292 WebContents* requesting_contents = GetWebContentsForLogin();
293 if (requesting_contents)
294 controller = &requesting_contents->GetController();
296 LoginNotificationDetails details(this);
298 service->Notify(chrome::NOTIFICATION_AUTH_NEEDED,
299 content::Source<NavigationController>(controller),
300 content::Details<LoginNotificationDetails>(&details));
303 void LoginHandler::ReleaseSoon() {
304 if (!TestAndSetAuthHandled()) {
305 BrowserThread::PostTask(
306 BrowserThread::IO, FROM_HERE,
307 base::Bind(&LoginHandler::CancelAuthDeferred, this));
308 BrowserThread::PostTask(
309 BrowserThread::UI, FROM_HERE,
310 base::Bind(&LoginHandler::NotifyAuthCancelled, this));
313 BrowserThread::PostTask(
314 BrowserThread::UI, FROM_HERE,
315 base::Bind(&LoginHandler::RemoveObservers, this));
317 // Delete this object once all InvokeLaters have been called.
318 BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this);
321 void LoginHandler::AddObservers() {
322 DCHECK_CURRENTLY_ON(BrowserThread::UI);
324 // This is probably OK; we need to listen to everything and we break out of
325 // the Observe() if we aren't handling the same auth_info().
326 registrar_.reset(new content::NotificationRegistrar);
327 registrar_->Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED,
328 content::NotificationService::AllBrowserContextsAndSources());
329 registrar_->Add(this, chrome::NOTIFICATION_AUTH_CANCELLED,
330 content::NotificationService::AllBrowserContextsAndSources());
333 void LoginHandler::RemoveObservers() {
334 DCHECK_CURRENTLY_ON(BrowserThread::UI);
336 registrar_.reset();
339 void LoginHandler::NotifyAuthSupplied(const base::string16& username,
340 const base::string16& password) {
341 DCHECK_CURRENTLY_ON(BrowserThread::UI);
342 DCHECK(WasAuthHandled());
344 WebContents* requesting_contents = GetWebContentsForLogin();
345 if (!requesting_contents)
346 return;
348 content::NotificationService* service =
349 content::NotificationService::current();
350 NavigationController* controller =
351 &requesting_contents->GetController();
352 AuthSuppliedLoginNotificationDetails details(this, username, password);
354 service->Notify(
355 chrome::NOTIFICATION_AUTH_SUPPLIED,
356 content::Source<NavigationController>(controller),
357 content::Details<AuthSuppliedLoginNotificationDetails>(&details));
360 void LoginHandler::NotifyAuthCancelled() {
361 DCHECK_CURRENTLY_ON(BrowserThread::UI);
362 DCHECK(WasAuthHandled());
364 content::NotificationService* service =
365 content::NotificationService::current();
366 NavigationController* controller = NULL;
368 WebContents* requesting_contents = GetWebContentsForLogin();
369 if (requesting_contents)
370 controller = &requesting_contents->GetController();
372 LoginNotificationDetails details(this);
374 service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED,
375 content::Source<NavigationController>(controller),
376 content::Details<LoginNotificationDetails>(&details));
379 // Marks authentication as handled and returns the previous handled state.
380 bool LoginHandler::TestAndSetAuthHandled() {
381 base::AutoLock lock(handled_auth_lock_);
382 bool was_handled = handled_auth_;
383 handled_auth_ = true;
384 return was_handled;
387 // Calls SetAuth from the IO loop.
388 void LoginHandler::SetAuthDeferred(const base::string16& username,
389 const base::string16& password) {
390 DCHECK_CURRENTLY_ON(BrowserThread::IO);
392 if (request_) {
393 request_->SetAuth(net::AuthCredentials(username, password));
394 ResetLoginHandlerForRequest(request_);
398 // Calls CancelAuth from the IO loop.
399 void LoginHandler::CancelAuthDeferred() {
400 DCHECK_CURRENTLY_ON(BrowserThread::IO);
402 if (request_) {
403 request_->CancelAuth();
404 // Verify that CancelAuth doesn't destroy the request via our delegate.
405 DCHECK(request_ != NULL);
406 ResetLoginHandlerForRequest(request_);
410 // Closes the view_contents from the UI loop.
411 void LoginHandler::CloseContentsDeferred() {
412 DCHECK_CURRENTLY_ON(BrowserThread::UI);
414 CloseDialog();
416 WebContents* requesting_contents = GetWebContentsForLogin();
417 if (!requesting_contents)
418 return;
419 // If a (blank) login interstitial was displayed, proceed so that the
420 // navigation is committed.
421 content::InterstitialPage* interstitial_page =
422 requesting_contents->GetInterstitialPage();
423 if (interstitial_page)
424 interstitial_page->Proceed();
427 // Helper to create a PasswordForm and stuff it into a vector as input
428 // for PasswordManager::PasswordFormsParsed, the hook into PasswordManager.
429 void MakeInputForPasswordManager(
430 const GURL& request_url,
431 net::AuthChallengeInfo* auth_info,
432 LoginHandler* handler,
433 std::vector<PasswordForm>* password_manager_input) {
434 PasswordForm dialog_form;
435 if (base::LowerCaseEqualsASCII(auth_info->scheme, "basic")) {
436 dialog_form.scheme = PasswordForm::SCHEME_BASIC;
437 } else if (base::LowerCaseEqualsASCII(auth_info->scheme, "digest")) {
438 dialog_form.scheme = PasswordForm::SCHEME_DIGEST;
439 } else {
440 dialog_form.scheme = PasswordForm::SCHEME_OTHER;
442 std::string host_and_port(auth_info->challenger.ToString());
443 if (auth_info->is_proxy) {
444 std::string origin = host_and_port;
445 // We don't expect this to already start with http:// or https://.
446 DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0);
447 origin = std::string("http://") + origin;
448 dialog_form.origin = GURL(origin);
449 } else if (!auth_info->challenger.Equals(
450 net::HostPortPair::FromURL(request_url))) {
451 dialog_form.origin = GURL();
452 NOTREACHED(); // crbug.com/32718
453 } else {
454 dialog_form.origin = GURL(request_url.scheme() + "://" + host_and_port);
456 dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info);
457 password_manager_input->push_back(dialog_form);
458 // Set the password form for the handler (by copy).
459 handler->SetPasswordForm(dialog_form);
462 void ShowLoginPrompt(const GURL& request_url,
463 net::AuthChallengeInfo* auth_info,
464 LoginHandler* handler) {
465 DCHECK_CURRENTLY_ON(BrowserThread::UI);
466 WebContents* parent_contents = handler->GetWebContentsForLogin();
467 if (!parent_contents)
468 return;
469 prerender::PrerenderContents* prerender_contents =
470 prerender::PrerenderContents::FromWebContents(parent_contents);
471 if (prerender_contents) {
472 prerender_contents->Destroy(prerender::FINAL_STATUS_AUTH_NEEDED);
473 return;
476 password_manager::ContentPasswordManagerDriver* driver =
477 handler->GetPasswordManagerDriverForLogin();
479 // The realm is controlled by the remote server, so there is no reason
480 // to believe it is of a reasonable length.
481 base::string16 elided_realm;
482 gfx::ElideString(base::UTF8ToUTF16(auth_info->realm), 120, &elided_realm);
484 base::string16 host_and_port = base::ASCIIToUTF16(
485 request_url.scheme() + "://" + auth_info->challenger.ToString());
486 base::string16 explanation = elided_realm.empty() ?
487 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM,
488 host_and_port) :
489 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION,
490 host_and_port,
491 elided_realm);
493 if (!driver) {
494 #if defined(ENABLE_EXTENSIONS)
495 // A WebContents in a <webview> (a GuestView type) does not have a password
496 // manager, but still needs to be able to show login prompts.
497 if (guest_view::GuestViewBase::FromWebContents(parent_contents)) {
498 handler->BuildViewForPasswordManager(nullptr, explanation);
499 return;
501 #endif
502 handler->CancelAuth();
503 return;
506 password_manager::PasswordManager* password_manager =
507 driver->GetPasswordManager();
508 if (password_manager && password_manager->client()->IsLoggingActive()) {
509 password_manager::BrowserSavePasswordProgressLogger logger(
510 password_manager->client());
511 logger.LogMessage(
512 autofill::SavePasswordProgressLogger::STRING_SHOW_LOGIN_PROMPT_METHOD);
515 // Tell the password manager to look for saved passwords.
516 std::vector<PasswordForm> v;
517 MakeInputForPasswordManager(request_url, auth_info, handler, &v);
518 driver->OnPasswordFormsParsedNoRenderCheck(v);
519 handler->SetPasswordManager(driver->GetPasswordManager());
521 handler->BuildViewForPasswordManager(driver->GetPasswordManager(),
522 explanation);
525 // This callback is run on the UI thread and creates a constrained window with
526 // a LoginView to prompt the user. If the prompt is triggered because of
527 // a cross origin navigation in the main frame, a blank interstitial is first
528 // created which in turn creates the LoginView. Otherwise, a LoginView is
529 // directly in this callback. In both cases, the response will be sent to
530 // LoginHandler, which then routes it to the net::URLRequest on the I/O thread.
531 void LoginDialogCallback(const GURL& request_url,
532 net::AuthChallengeInfo* auth_info,
533 LoginHandler* handler,
534 bool is_main_frame) {
535 DCHECK_CURRENTLY_ON(BrowserThread::UI);
536 WebContents* parent_contents = handler->GetWebContentsForLogin();
537 if (!parent_contents || handler->WasAuthHandled()) {
538 // The request may have been cancelled, or it may be for a renderer
539 // not hosted by a tab (e.g. an extension). Cancel just in case
540 // (cancelling twice is a no-op).
541 handler->CancelAuth();
542 return;
545 // Check if this is a main frame navigation and
546 // (a) if the request is cross origin or
547 // (b) if an interstitial is already being shown.
549 // For (a), there are two different ways the navigation can occur:
550 // 1- The user enters the resource URL in the omnibox.
551 // 2- The page redirects to the resource.
552 // In both cases, the last committed URL is different than the resource URL,
553 // so checking it is sufficient.
554 // Note that (1) will not be true once site isolation is enabled, as any
555 // navigation could cause a cross-process swap, including link clicks.
557 // For (b), the login interstitial should always replace an existing
558 // interstitial. This is because |LoginHandler::CloseContentsDeferred| tries
559 // to proceed whatever interstitial is being shown when the login dialog is
560 // closed, so that interstitial should only be a login interstitial.
561 if (is_main_frame && (parent_contents->ShowingInterstitialPage() ||
562 parent_contents->GetLastCommittedURL().GetOrigin() !=
563 request_url.GetOrigin())) {
564 // Show a blank interstitial for main-frame, cross origin requests
565 // so that the correct URL is shown in the omnibox.
566 base::Closure callback = base::Bind(&ShowLoginPrompt,
567 request_url,
568 make_scoped_refptr(auth_info),
569 make_scoped_refptr(handler));
570 // This is owned by the interstitial it creates. It cancels any existing
571 // interstitial.
572 new LoginInterstitialDelegate(parent_contents,
573 request_url,
574 callback);
575 } else {
576 ShowLoginPrompt(request_url,
577 auth_info,
578 handler);
582 // ----------------------------------------------------------------------------
583 // Public API
585 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
586 net::URLRequest* request) {
587 bool is_main_frame = (request->load_flags() & net::LOAD_MAIN_FRAME) != 0;
588 LoginHandler* handler = LoginHandler::Create(auth_info, request);
589 BrowserThread::PostTask(
590 BrowserThread::UI, FROM_HERE,
591 base::Bind(&LoginDialogCallback, request->url(),
592 make_scoped_refptr(auth_info), make_scoped_refptr(handler),
593 is_main_frame));
594 return handler;