• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
ResetLoginHandlerForRequest(net::URLRequest * request)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.
GetSignonRealm(const GURL & url,const net::AuthChallengeInfo & auth_info)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 
LoginHandler(net::AuthChallengeInfo * auth_info,net::URLRequest * request)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 
~LoginHandler()99 LoginHandler::~LoginHandler() {
100   SetModel(NULL);
101 }
102 
SetPasswordForm(const webkit_glue::PasswordForm & form)103 void LoginHandler::SetPasswordForm(const webkit_glue::PasswordForm& form) {
104   password_form_ = form;
105 }
106 
SetPasswordManager(PasswordManager * password_manager)107 void LoginHandler::SetPasswordManager(PasswordManager* password_manager) {
108   password_manager_ = password_manager;
109 }
110 
GetTabContentsForLogin() const111 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 
GetRenderViewHostDelegate() const118 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 
SetAuth(const string16 & username,const string16 & password)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 
CancelAuth()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 
OnRequestCancelled()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 
AddObservers()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 
RemoveObservers()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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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 
SetModel(LoginModel * model)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 
SetDialog(ConstrainedWindow * dialog)255 void LoginHandler::SetDialog(ConstrainedWindow* dialog) {
256   dialog_ = dialog;
257 }
258 
NotifyAuthNeeded()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 
NotifyAuthCancelled()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 
NotifyAuthSupplied(const string16 & username,const string16 & password)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 
ReleaseSoon()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).
WasAuthHandled() const333 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.
TestAndSetAuthHandled()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.
SetAuthDeferred(const string16 & username,const string16 & password)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.
CancelAuthDeferred()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.
CloseContentsDeferred()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:
LoginDialogTask(const GURL & request_url,net::AuthChallengeInfo * auth_info,LoginHandler * handler)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   }
~LoginDialogTask()392   virtual ~LoginDialogTask() {
393   }
394 
Run()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.
MakeInputForPasswordManager(std::vector<PasswordForm> * password_manager_input)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 
CreateLoginPrompt(net::AuthChallengeInfo * auth_info,net::URLRequest * request)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