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