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
38using autofill::PasswordForm;
39using content::BrowserThread;
40using content::NavigationController;
41using content::RenderViewHost;
42using content::RenderViewHostDelegate;
43using content::ResourceDispatcherHost;
44using content::ResourceRequestInfo;
45using content::WebContents;
46
47class 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.
52void 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.
65std::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
83LoginHandler::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
110void 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
121void LoginHandler::SetPasswordForm(const autofill::PasswordForm& form) {
122  password_form_ = form;
123}
124
125void LoginHandler::SetPasswordManager(
126    password_manager::PasswordManager* password_manager) {
127  password_manager_ = password_manager;
128}
129
130WebContents* 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
138void 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
187void 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
209void 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).
252bool LoginHandler::WasAuthHandled() const {
253  base::AutoLock lock(handled_auth_lock_);
254  bool was_handled = handled_auth_;
255  return was_handled;
256}
257
258LoginHandler::~LoginHandler() {
259  SetModel(NULL);
260}
261
262void 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
270void 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
290void 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
308void 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
320void LoginHandler::RemoveObservers() {
321  DCHECK_CURRENTLY_ON(BrowserThread::UI);
322
323  registrar_.reset();
324}
325
326void 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
347void 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.
367bool 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.
375void 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.
386void 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.
398void 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.
416void 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
449void 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.
499void 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
544LoginHandler* 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