Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / ui / login / login_prompt.cc
blob8051d83138dad0e8f008a3f5163e32310c236cce
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 <string>
8 #include <vector>
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/synchronization/lock.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
18 #include "chrome/browser/prerender/prerender_contents.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/tab_contents/tab_util.h"
21 #include "chrome/browser/ui/login/login_interstitial_delegate.h"
22 #include "chrome/common/pref_names.h"
23 #include "chrome/grit/generated_resources.h"
24 #include "components/password_manager/content/browser/content_password_manager_driver.h"
25 #include "components/password_manager/core/browser/browser_save_password_progress_logger.h"
26 #include "components/password_manager/core/browser/password_manager.h"
27 #include "components/url_formatter/elide_url.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/notification_registrar.h"
30 #include "content/public/browser/notification_service.h"
31 #include "content/public/browser/render_frame_host.h"
32 #include "content/public/browser/resource_dispatcher_host.h"
33 #include "content/public/browser/resource_request_info.h"
34 #include "content/public/browser/web_contents.h"
35 #include "net/base/auth.h"
36 #include "net/base/load_flags.h"
37 #include "net/base/net_util.h"
38 #include "net/http/http_transaction_factory.h"
39 #include "net/url_request/url_request.h"
40 #include "net/url_request/url_request_context.h"
41 #include "ui/base/l10n/l10n_util.h"
42 #include "ui/gfx/text_elider.h"
44 #if defined(ENABLE_EXTENSIONS)
45 #include "components/guest_view/browser/guest_view_base.h"
46 #endif
48 using autofill::PasswordForm;
49 using content::BrowserThread;
50 using content::NavigationController;
51 using content::RenderViewHost;
52 using content::RenderViewHostDelegate;
53 using content::ResourceDispatcherHost;
54 using content::ResourceRequestInfo;
55 using content::WebContents;
57 class LoginHandlerImpl;
59 // Helper to remove the ref from an net::URLRequest to the LoginHandler.
60 // Should only be called from the IO thread, since it accesses an
61 // net::URLRequest.
62 void ResetLoginHandlerForRequest(net::URLRequest* request) {
63 ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request);
66 // Get the signon_realm under which this auth info should be stored.
68 // The format of the signon_realm for proxy auth is:
69 // proxy-host/auth-realm
70 // The format of the signon_realm for server auth is:
71 // url-scheme://url-host[:url-port]/auth-realm
73 // Be careful when changing this function, since you could make existing
74 // saved logins un-retrievable.
75 std::string GetSignonRealm(const GURL& url,
76 const net::AuthChallengeInfo& auth_info) {
77 std::string signon_realm;
78 if (auth_info.is_proxy) {
79 signon_realm = auth_info.challenger.ToString();
80 signon_realm.append("/");
81 } else {
82 // Take scheme, host, and port from the url.
83 signon_realm = url.GetOrigin().spec();
84 // This ends with a "/".
86 signon_realm.append(auth_info.realm);
87 return signon_realm;
90 // ----------------------------------------------------------------------------
91 // LoginHandler
93 LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
94 net::URLRequest* request)
95 : handled_auth_(false),
96 auth_info_(auth_info),
97 request_(request),
98 http_network_session_(
99 request_->context()->http_transaction_factory()->GetSession()),
100 password_manager_(NULL),
101 login_model_(NULL) {
102 // This constructor is called on the I/O thread, so we cannot load the nib
103 // here. BuildViewForPasswordManager() will be invoked on the UI thread
104 // later, so wait with loading the nib until then.
105 DCHECK(request_) << "LoginHandler constructed with NULL request";
106 DCHECK(auth_info_.get()) << "LoginHandler constructed with NULL auth info";
108 AddRef(); // matched by LoginHandler::ReleaseSoon().
110 BrowserThread::PostTask(
111 BrowserThread::UI, FROM_HERE,
112 base::Bind(&LoginHandler::AddObservers, this));
114 if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame(
115 &render_process_host_id_, &render_frame_id_)) {
116 NOTREACHED();
120 void LoginHandler::OnRequestCancelled() {
121 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) <<
122 "Why is OnRequestCancelled called from the UI thread?";
124 // Reference is no longer valid.
125 request_ = NULL;
127 // Give up on auth if the request was cancelled.
128 CancelAuth();
131 void LoginHandler::SetPasswordForm(const autofill::PasswordForm& form) {
132 password_form_ = form;
135 void LoginHandler::SetPasswordManager(
136 password_manager::PasswordManager* password_manager) {
137 password_manager_ = password_manager;
140 WebContents* LoginHandler::GetWebContentsForLogin() const {
141 DCHECK_CURRENTLY_ON(BrowserThread::UI);
143 content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
144 render_process_host_id_, render_frame_id_);
145 return WebContents::FromRenderFrameHost(rfh);
148 password_manager::ContentPasswordManagerDriver*
149 LoginHandler::GetPasswordManagerDriverForLogin() {
150 content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
151 render_process_host_id_, render_frame_id_);
152 return password_manager::ContentPasswordManagerDriver::GetForRenderFrameHost(
153 rfh);
156 void LoginHandler::SetAuth(const base::string16& username,
157 const base::string16& password) {
158 DCHECK_CURRENTLY_ON(BrowserThread::UI);
160 scoped_ptr<password_manager::BrowserSavePasswordProgressLogger> logger;
161 if (password_manager_ && password_manager_->client()->IsLoggingActive()) {
162 logger.reset(new password_manager::BrowserSavePasswordProgressLogger(
163 password_manager_->client()));
164 logger->LogMessage(
165 autofill::SavePasswordProgressLogger::STRING_SET_AUTH_METHOD);
168 bool already_handled = TestAndSetAuthHandled();
169 if (logger) {
170 logger->LogBoolean(
171 autofill::SavePasswordProgressLogger::STRING_AUTHENTICATION_HANDLED,
172 already_handled);
174 if (already_handled)
175 return;
177 // Tell the password manager the credentials were submitted / accepted.
178 if (password_manager_) {
179 password_form_.username_value = username;
180 password_form_.password_value = password;
181 password_manager_->ProvisionallySavePassword(password_form_);
182 if (logger) {
183 logger->LogPasswordForm(
184 autofill::SavePasswordProgressLogger::STRING_LOGINHANDLER_FORM,
185 password_form_);
189 // Calling NotifyAuthSupplied() directly instead of posting a task
190 // allows other LoginHandler instances to queue their
191 // CloseContentsDeferred() before ours. Closing dialogs in the
192 // opposite order as they were created avoids races where remaining
193 // dialogs in the same tab may be briefly displayed to the user
194 // before they are removed.
195 NotifyAuthSupplied(username, password);
197 BrowserThread::PostTask(
198 BrowserThread::UI, FROM_HERE,
199 base::Bind(&LoginHandler::CloseContentsDeferred, this));
200 BrowserThread::PostTask(
201 BrowserThread::IO, FROM_HERE,
202 base::Bind(&LoginHandler::SetAuthDeferred, this, username, password));
205 void LoginHandler::CancelAuth() {
206 if (TestAndSetAuthHandled())
207 return;
209 // Similar to how we deal with notifications above in SetAuth()
210 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
211 NotifyAuthCancelled();
212 } else {
213 BrowserThread::PostTask(
214 BrowserThread::UI, FROM_HERE,
215 base::Bind(&LoginHandler::NotifyAuthCancelled, this));
218 BrowserThread::PostTask(
219 BrowserThread::UI, FROM_HERE,
220 base::Bind(&LoginHandler::CloseContentsDeferred, this));
221 BrowserThread::PostTask(
222 BrowserThread::IO, FROM_HERE,
223 base::Bind(&LoginHandler::CancelAuthDeferred, this));
227 void LoginHandler::Observe(int type,
228 const content::NotificationSource& source,
229 const content::NotificationDetails& details) {
230 DCHECK_CURRENTLY_ON(BrowserThread::UI);
231 DCHECK(type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
232 type == chrome::NOTIFICATION_AUTH_CANCELLED);
234 WebContents* requesting_contents = GetWebContentsForLogin();
235 if (!requesting_contents)
236 return;
238 // Break out early if we aren't interested in the notification.
239 if (WasAuthHandled())
240 return;
242 LoginNotificationDetails* login_details =
243 content::Details<LoginNotificationDetails>(details).ptr();
245 // WasAuthHandled() should always test positive before we publish
246 // AUTH_SUPPLIED or AUTH_CANCELLED notifications.
247 DCHECK(login_details->handler() != this);
249 // Only handle notification for the identical auth info.
250 if (!login_details->handler()->auth_info()->Equals(*auth_info()))
251 return;
253 // Ignore login notification events from other profiles.
254 if (login_details->handler()->http_network_session_ !=
255 http_network_session_)
256 return;
258 // Set or cancel the auth in this handler.
259 if (type == chrome::NOTIFICATION_AUTH_SUPPLIED) {
260 AuthSuppliedLoginNotificationDetails* supplied_details =
261 content::Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
262 SetAuth(supplied_details->username(), supplied_details->password());
263 } else {
264 DCHECK(type == chrome::NOTIFICATION_AUTH_CANCELLED);
265 CancelAuth();
269 // Returns whether authentication had been handled (SetAuth or CancelAuth).
270 bool LoginHandler::WasAuthHandled() const {
271 base::AutoLock lock(handled_auth_lock_);
272 bool was_handled = handled_auth_;
273 return was_handled;
276 LoginHandler::~LoginHandler() {
277 SetModel(NULL);
280 void LoginHandler::SetModel(password_manager::LoginModel* model) {
281 if (login_model_)
282 login_model_->RemoveObserver(this);
283 login_model_ = model;
284 if (login_model_)
285 login_model_->AddObserver(this);
288 void LoginHandler::NotifyAuthNeeded() {
289 DCHECK_CURRENTLY_ON(BrowserThread::UI);
290 if (WasAuthHandled())
291 return;
293 content::NotificationService* service =
294 content::NotificationService::current();
295 NavigationController* controller = NULL;
297 WebContents* requesting_contents = GetWebContentsForLogin();
298 if (requesting_contents)
299 controller = &requesting_contents->GetController();
301 LoginNotificationDetails details(this);
303 service->Notify(chrome::NOTIFICATION_AUTH_NEEDED,
304 content::Source<NavigationController>(controller),
305 content::Details<LoginNotificationDetails>(&details));
308 void LoginHandler::ReleaseSoon() {
309 if (!TestAndSetAuthHandled()) {
310 BrowserThread::PostTask(
311 BrowserThread::IO, FROM_HERE,
312 base::Bind(&LoginHandler::CancelAuthDeferred, this));
313 BrowserThread::PostTask(
314 BrowserThread::UI, FROM_HERE,
315 base::Bind(&LoginHandler::NotifyAuthCancelled, this));
318 BrowserThread::PostTask(
319 BrowserThread::UI, FROM_HERE,
320 base::Bind(&LoginHandler::RemoveObservers, this));
322 // Delete this object once all InvokeLaters have been called.
323 BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this);
326 void LoginHandler::AddObservers() {
327 DCHECK_CURRENTLY_ON(BrowserThread::UI);
329 // This is probably OK; we need to listen to everything and we break out of
330 // the Observe() if we aren't handling the same auth_info().
331 registrar_.reset(new content::NotificationRegistrar);
332 registrar_->Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED,
333 content::NotificationService::AllBrowserContextsAndSources());
334 registrar_->Add(this, chrome::NOTIFICATION_AUTH_CANCELLED,
335 content::NotificationService::AllBrowserContextsAndSources());
338 void LoginHandler::RemoveObservers() {
339 DCHECK_CURRENTLY_ON(BrowserThread::UI);
341 registrar_.reset();
344 void LoginHandler::NotifyAuthSupplied(const base::string16& username,
345 const base::string16& password) {
346 DCHECK_CURRENTLY_ON(BrowserThread::UI);
347 DCHECK(WasAuthHandled());
349 WebContents* requesting_contents = GetWebContentsForLogin();
350 if (!requesting_contents)
351 return;
353 content::NotificationService* service =
354 content::NotificationService::current();
355 NavigationController* controller =
356 &requesting_contents->GetController();
357 AuthSuppliedLoginNotificationDetails details(this, username, password);
359 service->Notify(
360 chrome::NOTIFICATION_AUTH_SUPPLIED,
361 content::Source<NavigationController>(controller),
362 content::Details<AuthSuppliedLoginNotificationDetails>(&details));
365 void LoginHandler::NotifyAuthCancelled() {
366 DCHECK_CURRENTLY_ON(BrowserThread::UI);
367 DCHECK(WasAuthHandled());
369 content::NotificationService* service =
370 content::NotificationService::current();
371 NavigationController* controller = NULL;
373 WebContents* requesting_contents = GetWebContentsForLogin();
374 if (requesting_contents)
375 controller = &requesting_contents->GetController();
377 LoginNotificationDetails details(this);
379 service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED,
380 content::Source<NavigationController>(controller),
381 content::Details<LoginNotificationDetails>(&details));
384 // Marks authentication as handled and returns the previous handled state.
385 bool LoginHandler::TestAndSetAuthHandled() {
386 base::AutoLock lock(handled_auth_lock_);
387 bool was_handled = handled_auth_;
388 handled_auth_ = true;
389 return was_handled;
392 // Calls SetAuth from the IO loop.
393 void LoginHandler::SetAuthDeferred(const base::string16& username,
394 const base::string16& password) {
395 DCHECK_CURRENTLY_ON(BrowserThread::IO);
397 if (request_) {
398 request_->SetAuth(net::AuthCredentials(username, password));
399 ResetLoginHandlerForRequest(request_);
403 // Calls CancelAuth from the IO loop.
404 void LoginHandler::CancelAuthDeferred() {
405 DCHECK_CURRENTLY_ON(BrowserThread::IO);
407 if (request_) {
408 request_->CancelAuth();
409 // Verify that CancelAuth doesn't destroy the request via our delegate.
410 DCHECK(request_ != NULL);
411 ResetLoginHandlerForRequest(request_);
415 // Closes the view_contents from the UI loop.
416 void LoginHandler::CloseContentsDeferred() {
417 DCHECK_CURRENTLY_ON(BrowserThread::UI);
419 CloseDialog();
421 WebContents* requesting_contents = GetWebContentsForLogin();
422 if (!requesting_contents)
423 return;
424 // If a (blank) login interstitial was displayed, proceed so that the
425 // navigation is committed.
426 content::InterstitialPage* interstitial_page =
427 requesting_contents->GetInterstitialPage();
428 if (interstitial_page)
429 interstitial_page->Proceed();
432 // Helper to create a PasswordForm and stuff it into a vector as input
433 // for PasswordManager::PasswordFormsParsed, the hook into PasswordManager.
434 void MakeInputForPasswordManager(
435 const GURL& request_url,
436 net::AuthChallengeInfo* auth_info,
437 LoginHandler* handler,
438 std::vector<PasswordForm>* password_manager_input) {
439 PasswordForm dialog_form;
440 if (base::LowerCaseEqualsASCII(auth_info->scheme, "basic")) {
441 dialog_form.scheme = PasswordForm::SCHEME_BASIC;
442 } else if (base::LowerCaseEqualsASCII(auth_info->scheme, "digest")) {
443 dialog_form.scheme = PasswordForm::SCHEME_DIGEST;
444 } else {
445 dialog_form.scheme = PasswordForm::SCHEME_OTHER;
447 std::string host_and_port(auth_info->challenger.ToString());
448 if (auth_info->is_proxy) {
449 std::string origin = host_and_port;
450 // We don't expect this to already start with http:// or https://.
451 DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0);
452 origin = std::string("http://") + origin;
453 dialog_form.origin = GURL(origin);
454 } else if (!auth_info->challenger.Equals(
455 net::HostPortPair::FromURL(request_url))) {
456 dialog_form.origin = GURL();
457 NOTREACHED(); // crbug.com/32718
458 } else {
459 dialog_form.origin = GURL(request_url.scheme() + "://" + host_and_port);
461 dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info);
462 password_manager_input->push_back(dialog_form);
463 // Set the password form for the handler (by copy).
464 handler->SetPasswordForm(dialog_form);
467 void ShowLoginPrompt(const GURL& request_url,
468 net::AuthChallengeInfo* auth_info,
469 LoginHandler* handler) {
470 DCHECK_CURRENTLY_ON(BrowserThread::UI);
471 WebContents* parent_contents = handler->GetWebContentsForLogin();
472 if (!parent_contents)
473 return;
474 prerender::PrerenderContents* prerender_contents =
475 prerender::PrerenderContents::FromWebContents(parent_contents);
476 if (prerender_contents) {
477 prerender_contents->Destroy(prerender::FINAL_STATUS_AUTH_NEEDED);
478 return;
481 password_manager::ContentPasswordManagerDriver* driver =
482 handler->GetPasswordManagerDriverForLogin();
484 // The realm is controlled by the remote server, so there is no reason
485 // to believe it is of a reasonable length.
486 base::string16 elided_realm;
487 gfx::ElideString(base::UTF8ToUTF16(auth_info->realm), 120, &elided_realm);
489 std::string languages;
490 content::WebContents* web_contents = handler->GetWebContentsForLogin();
491 if (web_contents) {
492 Profile* profile =
493 Profile::FromBrowserContext(web_contents->GetBrowserContext());
494 if (profile)
495 languages = profile->GetPrefs()->GetString(prefs::kAcceptLanguages);
498 base::string16 authority =
499 url_formatter::FormatUrlForSecurityDisplay(request_url, languages);
500 base::string16 explanation =
501 elided_realm.empty()
502 ? l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM,
503 authority)
504 : l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION, authority,
505 elided_realm);
507 if (!driver) {
508 #if defined(ENABLE_EXTENSIONS)
509 // A WebContents in a <webview> (a GuestView type) does not have a password
510 // manager, but still needs to be able to show login prompts.
511 if (guest_view::GuestViewBase::FromWebContents(parent_contents)) {
512 handler->BuildViewForPasswordManager(nullptr, explanation);
513 return;
515 #endif
516 handler->CancelAuth();
517 return;
520 password_manager::PasswordManager* password_manager =
521 driver->GetPasswordManager();
522 if (password_manager && password_manager->client()->IsLoggingActive()) {
523 password_manager::BrowserSavePasswordProgressLogger logger(
524 password_manager->client());
525 logger.LogMessage(
526 autofill::SavePasswordProgressLogger::STRING_SHOW_LOGIN_PROMPT_METHOD);
529 // Tell the password manager to look for saved passwords.
530 std::vector<PasswordForm> v;
531 MakeInputForPasswordManager(request_url, auth_info, handler, &v);
532 driver->OnPasswordFormsParsedNoRenderCheck(v);
533 handler->SetPasswordManager(driver->GetPasswordManager());
535 handler->BuildViewForPasswordManager(driver->GetPasswordManager(),
536 explanation);
539 // This callback is run on the UI thread and creates a constrained window with
540 // a LoginView to prompt the user. If the prompt is triggered because of
541 // a cross origin navigation in the main frame, a blank interstitial is first
542 // created which in turn creates the LoginView. Otherwise, a LoginView is
543 // directly in this callback. In both cases, the response will be sent to
544 // LoginHandler, which then routes it to the net::URLRequest on the I/O thread.
545 void LoginDialogCallback(const GURL& request_url,
546 net::AuthChallengeInfo* auth_info,
547 LoginHandler* handler,
548 bool is_main_frame) {
549 DCHECK_CURRENTLY_ON(BrowserThread::UI);
550 WebContents* parent_contents = handler->GetWebContentsForLogin();
551 if (!parent_contents || handler->WasAuthHandled()) {
552 // The request may have been canceled, or it may be for a renderer not
553 // hosted by a tab (e.g. an extension). Cancel just in case (canceling twice
554 // is a no-op).
555 handler->CancelAuth();
556 return;
559 // Check if this is a main frame navigation and
560 // (a) if the request is cross origin or
561 // (b) if an interstitial is already being shown.
563 // For (a), there are two different ways the navigation can occur:
564 // 1- The user enters the resource URL in the omnibox.
565 // 2- The page redirects to the resource.
566 // In both cases, the last committed URL is different than the resource URL,
567 // so checking it is sufficient.
568 // Note that (1) will not be true once site isolation is enabled, as any
569 // navigation could cause a cross-process swap, including link clicks.
571 // For (b), the login interstitial should always replace an existing
572 // interstitial. This is because |LoginHandler::CloseContentsDeferred| tries
573 // to proceed whatever interstitial is being shown when the login dialog is
574 // closed, so that interstitial should only be a login interstitial.
575 if (is_main_frame && (parent_contents->ShowingInterstitialPage() ||
576 parent_contents->GetLastCommittedURL().GetOrigin() !=
577 request_url.GetOrigin())) {
578 // Show a blank interstitial for main-frame, cross origin requests
579 // so that the correct URL is shown in the omnibox.
580 base::Closure callback = base::Bind(&ShowLoginPrompt,
581 request_url,
582 make_scoped_refptr(auth_info),
583 make_scoped_refptr(handler));
584 // This is owned by the interstitial it creates. It cancels any existing
585 // interstitial.
586 new LoginInterstitialDelegate(parent_contents,
587 request_url,
588 callback);
589 } else {
590 ShowLoginPrompt(request_url,
591 auth_info,
592 handler);
596 // ----------------------------------------------------------------------------
597 // Public API
599 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
600 net::URLRequest* request) {
601 bool is_main_frame = (request->load_flags() & net::LOAD_MAIN_FRAME) != 0;
602 LoginHandler* handler = LoginHandler::Create(auth_info, request);
603 BrowserThread::PostTask(
604 BrowserThread::UI, FROM_HERE,
605 base::Bind(&LoginDialogCallback, request->url(),
606 make_scoped_refptr(auth_info), make_scoped_refptr(handler),
607 is_main_frame));
608 return handler;