login_prompt.cc revision ddb351dbec246cf1fab5ec20d2d5520909041de1
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
30using webkit_glue::PasswordForm;
31
32class 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.
37void 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.
55std::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
73LoginHandler::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
99LoginHandler::~LoginHandler() {
100  SetModel(NULL);
101}
102
103void LoginHandler::SetPasswordForm(const webkit_glue::PasswordForm& form) {
104  password_form_ = form;
105}
106
107void LoginHandler::SetPasswordManager(PasswordManager* password_manager) {
108  password_manager_ = password_manager;
109}
110
111TabContents* 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
118RenderViewHostDelegate* 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
127void 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
158void 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
179void 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
190void 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
199void 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
210void 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
247void 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
255void LoginHandler::SetDialog(ConstrainedWindow* dialog) {
256  dialog_ = dialog;
257}
258
259void 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
278void 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
296void 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
314void 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).
333bool 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.
340bool 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.
348void 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.
359void 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.
371void 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.
385class LoginDialogTask : public Task {
386 public:
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  }
392  virtual ~LoginDialogTask() {
393  }
394
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.
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
475LoginHandler* 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