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"
10 #include "base/command_line.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/synchronization/lock.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
15 #include "chrome/browser/password_manager/password_manager.h"
16 #include "chrome/browser/prerender/prerender_contents.h"
17 #include "chrome/browser/tab_contents/tab_util.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/notification_registrar.h"
20 #include "content/public/browser/notification_service.h"
21 #include "content/public/browser/render_frame_host.h"
22 #include "content/public/browser/resource_dispatcher_host.h"
23 #include "content/public/browser/resource_request_info.h"
24 #include "content/public/browser/web_contents.h"
25 #include "grit/generated_resources.h"
26 #include "net/base/auth.h"
27 #include "net/base/net_util.h"
28 #include "net/http/http_transaction_factory.h"
29 #include "net/url_request/url_request.h"
30 #include "net/url_request/url_request_context.h"
31 #include "ui/base/l10n/l10n_util.h"
32 #include "ui/gfx/text_elider.h"
34 using autofill::PasswordForm;
35 using content::BrowserThread;
36 using content::NavigationController;
37 using content::RenderViewHost;
38 using content::RenderViewHostDelegate;
39 using content::ResourceDispatcherHost;
40 using content::ResourceRequestInfo;
41 using content::WebContents;
43 class LoginHandlerImpl;
45 // Helper to remove the ref from an net::URLRequest to the LoginHandler.
46 // Should only be called from the IO thread, since it accesses an
48 void ResetLoginHandlerForRequest(net::URLRequest* request) {
49 ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request);
52 // Get the signon_realm under which this auth info should be stored.
54 // The format of the signon_realm for proxy auth is:
55 // proxy-host/auth-realm
56 // The format of the signon_realm for server auth is:
57 // url-scheme://url-host[:url-port]/auth-realm
59 // Be careful when changing this function, since you could make existing
60 // saved logins un-retrievable.
61 std::string GetSignonRealm(const GURL& url,
62 const net::AuthChallengeInfo& auth_info) {
63 std::string signon_realm;
64 if (auth_info.is_proxy) {
65 signon_realm = auth_info.challenger.ToString();
66 signon_realm.append("/");
68 // Take scheme, host, and port from the url.
69 signon_realm = url.GetOrigin().spec();
70 // This ends with a "/".
72 signon_realm.append(auth_info.realm);
76 // ----------------------------------------------------------------------------
79 LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
80 net::URLRequest* request)
81 : handled_auth_(false),
82 auth_info_(auth_info),
84 http_network_session_(
85 request_->context()->http_transaction_factory()->GetSession()),
86 password_manager_(NULL),
88 // This constructor is called on the I/O thread, so we cannot load the nib
89 // here. BuildViewForPasswordManager() will be invoked on the UI thread
90 // later, so wait with loading the nib until then.
91 DCHECK(request_) << "LoginHandler constructed with NULL request";
92 DCHECK(auth_info_.get()) << "LoginHandler constructed with NULL auth info";
94 AddRef(); // matched by LoginHandler::ReleaseSoon().
96 BrowserThread::PostTask(
97 BrowserThread::UI, FROM_HERE,
98 base::Bind(&LoginHandler::AddObservers, this));
100 if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame(
101 &render_process_host_id_, &render_frame_id_)) {
106 void LoginHandler::OnRequestCancelled() {
107 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) <<
108 "Why is OnRequestCancelled called from the UI thread?";
110 // Reference is no longer valid.
113 // Give up on auth if the request was cancelled.
117 void LoginHandler::SetPasswordForm(const autofill::PasswordForm& form) {
118 password_form_ = form;
121 void LoginHandler::SetPasswordManager(PasswordManager* password_manager) {
122 password_manager_ = password_manager;
125 WebContents* LoginHandler::GetWebContentsForLogin() const {
126 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
128 content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
129 render_process_host_id_, render_frame_id_);
130 return WebContents::FromRenderFrameHost(rfh);
133 void LoginHandler::SetAuth(const base::string16& username,
134 const base::string16& password) {
135 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
137 if (TestAndSetAuthHandled())
140 // Tell the password manager the credentials were submitted / accepted.
141 if (password_manager_) {
142 password_form_.username_value = username;
143 password_form_.password_value = password;
144 password_manager_->ProvisionallySavePassword(password_form_);
147 // Calling NotifyAuthSupplied() directly instead of posting a task
148 // allows other LoginHandler instances to queue their
149 // CloseContentsDeferred() before ours. Closing dialogs in the
150 // opposite order as they were created avoids races where remaining
151 // dialogs in the same tab may be briefly displayed to the user
152 // before they are removed.
153 NotifyAuthSupplied(username, password);
155 BrowserThread::PostTask(
156 BrowserThread::UI, FROM_HERE,
157 base::Bind(&LoginHandler::CloseContentsDeferred, this));
158 BrowserThread::PostTask(
159 BrowserThread::IO, FROM_HERE,
160 base::Bind(&LoginHandler::SetAuthDeferred, this, username, password));
163 void LoginHandler::CancelAuth() {
164 if (TestAndSetAuthHandled())
167 // Similar to how we deal with notifications above in SetAuth()
168 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
169 NotifyAuthCancelled();
171 BrowserThread::PostTask(
172 BrowserThread::UI, FROM_HERE,
173 base::Bind(&LoginHandler::NotifyAuthCancelled, this));
176 BrowserThread::PostTask(
177 BrowserThread::UI, FROM_HERE,
178 base::Bind(&LoginHandler::CloseContentsDeferred, this));
179 BrowserThread::PostTask(
180 BrowserThread::IO, FROM_HERE,
181 base::Bind(&LoginHandler::CancelAuthDeferred, this));
185 void LoginHandler::Observe(int type,
186 const content::NotificationSource& source,
187 const content::NotificationDetails& details) {
188 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
189 DCHECK(type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
190 type == chrome::NOTIFICATION_AUTH_CANCELLED);
192 WebContents* requesting_contents = GetWebContentsForLogin();
193 if (!requesting_contents)
196 // Break out early if we aren't interested in the notification.
197 if (WasAuthHandled())
200 LoginNotificationDetails* login_details =
201 content::Details<LoginNotificationDetails>(details).ptr();
203 // WasAuthHandled() should always test positive before we publish
204 // AUTH_SUPPLIED or AUTH_CANCELLED notifications.
205 DCHECK(login_details->handler() != this);
207 // Only handle notification for the identical auth info.
208 if (!login_details->handler()->auth_info()->Equals(*auth_info()))
211 // Ignore login notification events from other profiles.
212 if (login_details->handler()->http_network_session_ !=
213 http_network_session_)
216 // Set or cancel the auth in this handler.
217 if (type == chrome::NOTIFICATION_AUTH_SUPPLIED) {
218 AuthSuppliedLoginNotificationDetails* supplied_details =
219 content::Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
220 SetAuth(supplied_details->username(), supplied_details->password());
222 DCHECK(type == chrome::NOTIFICATION_AUTH_CANCELLED);
227 // Returns whether authentication had been handled (SetAuth or CancelAuth).
228 bool LoginHandler::WasAuthHandled() const {
229 base::AutoLock lock(handled_auth_lock_);
230 bool was_handled = handled_auth_;
234 LoginHandler::~LoginHandler() {
238 void LoginHandler::SetModel(LoginModel* model) {
240 login_model_->RemoveObserver(this);
241 login_model_ = model;
243 login_model_->AddObserver(this);
246 void LoginHandler::NotifyAuthNeeded() {
247 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
248 if (WasAuthHandled())
251 content::NotificationService* service =
252 content::NotificationService::current();
253 NavigationController* controller = NULL;
255 WebContents* requesting_contents = GetWebContentsForLogin();
256 if (requesting_contents)
257 controller = &requesting_contents->GetController();
259 LoginNotificationDetails details(this);
261 service->Notify(chrome::NOTIFICATION_AUTH_NEEDED,
262 content::Source<NavigationController>(controller),
263 content::Details<LoginNotificationDetails>(&details));
266 void LoginHandler::ReleaseSoon() {
267 if (!TestAndSetAuthHandled()) {
268 BrowserThread::PostTask(
269 BrowserThread::IO, FROM_HERE,
270 base::Bind(&LoginHandler::CancelAuthDeferred, this));
271 BrowserThread::PostTask(
272 BrowserThread::UI, FROM_HERE,
273 base::Bind(&LoginHandler::NotifyAuthCancelled, this));
276 BrowserThread::PostTask(
277 BrowserThread::UI, FROM_HERE,
278 base::Bind(&LoginHandler::RemoveObservers, this));
280 // Delete this object once all InvokeLaters have been called.
281 BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this);
284 void LoginHandler::AddObservers() {
285 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
287 // This is probably OK; we need to listen to everything and we break out of
288 // the Observe() if we aren't handling the same auth_info().
289 registrar_.reset(new content::NotificationRegistrar);
290 registrar_->Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED,
291 content::NotificationService::AllBrowserContextsAndSources());
292 registrar_->Add(this, chrome::NOTIFICATION_AUTH_CANCELLED,
293 content::NotificationService::AllBrowserContextsAndSources());
296 void LoginHandler::RemoveObservers() {
297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
302 void LoginHandler::NotifyAuthSupplied(const base::string16& username,
303 const base::string16& password) {
304 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
305 DCHECK(WasAuthHandled());
307 WebContents* requesting_contents = GetWebContentsForLogin();
308 if (!requesting_contents)
311 content::NotificationService* service =
312 content::NotificationService::current();
313 NavigationController* controller =
314 &requesting_contents->GetController();
315 AuthSuppliedLoginNotificationDetails details(this, username, password);
318 chrome::NOTIFICATION_AUTH_SUPPLIED,
319 content::Source<NavigationController>(controller),
320 content::Details<AuthSuppliedLoginNotificationDetails>(&details));
323 void LoginHandler::NotifyAuthCancelled() {
324 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
325 DCHECK(WasAuthHandled());
327 content::NotificationService* service =
328 content::NotificationService::current();
329 NavigationController* controller = NULL;
331 WebContents* requesting_contents = GetWebContentsForLogin();
332 if (requesting_contents)
333 controller = &requesting_contents->GetController();
335 LoginNotificationDetails details(this);
337 service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED,
338 content::Source<NavigationController>(controller),
339 content::Details<LoginNotificationDetails>(&details));
342 // Marks authentication as handled and returns the previous handled state.
343 bool LoginHandler::TestAndSetAuthHandled() {
344 base::AutoLock lock(handled_auth_lock_);
345 bool was_handled = handled_auth_;
346 handled_auth_ = true;
350 // Calls SetAuth from the IO loop.
351 void LoginHandler::SetAuthDeferred(const base::string16& username,
352 const base::string16& password) {
353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
356 request_->SetAuth(net::AuthCredentials(username, password));
357 ResetLoginHandlerForRequest(request_);
361 // Calls CancelAuth from the IO loop.
362 void LoginHandler::CancelAuthDeferred() {
363 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
366 request_->CancelAuth();
367 // Verify that CancelAuth doesn't destroy the request via our delegate.
368 DCHECK(request_ != NULL);
369 ResetLoginHandlerForRequest(request_);
373 // Closes the view_contents from the UI loop.
374 void LoginHandler::CloseContentsDeferred() {
375 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
380 // Helper to create a PasswordForm and stuff it into a vector as input
381 // for PasswordManager::PasswordFormsParsed, the hook into PasswordManager.
382 void MakeInputForPasswordManager(
383 const GURL& request_url,
384 net::AuthChallengeInfo* auth_info,
385 LoginHandler* handler,
386 std::vector<PasswordForm>* password_manager_input) {
387 PasswordForm dialog_form;
388 if (LowerCaseEqualsASCII(auth_info->scheme, "basic")) {
389 dialog_form.scheme = PasswordForm::SCHEME_BASIC;
390 } else if (LowerCaseEqualsASCII(auth_info->scheme, "digest")) {
391 dialog_form.scheme = PasswordForm::SCHEME_DIGEST;
393 dialog_form.scheme = PasswordForm::SCHEME_OTHER;
395 std::string host_and_port(auth_info->challenger.ToString());
396 if (auth_info->is_proxy) {
397 std::string origin = host_and_port;
398 // We don't expect this to already start with http:// or https://.
399 DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0);
400 origin = std::string("http://") + origin;
401 dialog_form.origin = GURL(origin);
402 } else if (!auth_info->challenger.Equals(
403 net::HostPortPair::FromURL(request_url))) {
404 dialog_form.origin = GURL();
405 NOTREACHED(); // crbug.com/32718
407 dialog_form.origin = GURL(request_url.scheme() + "://" + host_and_port);
409 dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info);
410 password_manager_input->push_back(dialog_form);
411 // Set the password form for the handler (by copy).
412 handler->SetPasswordForm(dialog_form);
415 // This callback is run on the UI thread and creates a constrained window with
416 // a LoginView to prompt the user. The response will be sent to LoginHandler,
417 // which then routes it to the net::URLRequest on the I/O thread.
418 void LoginDialogCallback(const GURL& request_url,
419 net::AuthChallengeInfo* auth_info,
420 LoginHandler* handler) {
421 WebContents* parent_contents = handler->GetWebContentsForLogin();
422 if (!parent_contents || handler->WasAuthHandled()) {
423 // The request may have been cancelled, or it may be for a renderer
424 // not hosted by a tab (e.g. an extension). Cancel just in case
425 // (cancelling twice is a no-op).
426 handler->CancelAuth();
430 prerender::PrerenderContents* prerender_contents =
431 prerender::PrerenderContents::FromWebContents(parent_contents);
432 if (prerender_contents) {
433 prerender_contents->Destroy(prerender::FINAL_STATUS_AUTH_NEEDED);
437 PasswordManager* password_manager =
438 ChromePasswordManagerClient::GetManagerFromWebContents(parent_contents);
439 if (!password_manager) {
440 // Same logic as above.
441 handler->CancelAuth();
445 // Tell the password manager to look for saved passwords.
446 std::vector<PasswordForm> v;
447 MakeInputForPasswordManager(request_url, auth_info, handler, &v);
448 password_manager->OnPasswordFormsParsed(v);
449 handler->SetPasswordManager(password_manager);
451 // The realm is controlled by the remote server, so there is no reason
452 // to believe it is of a reasonable length.
453 base::string16 elided_realm;
454 gfx::ElideString(base::UTF8ToUTF16(auth_info->realm), 120, &elided_realm);
456 base::string16 host_and_port = base::ASCIIToUTF16(
457 request_url.scheme() + "://" + auth_info->challenger.ToString());
458 base::string16 explanation = elided_realm.empty() ?
459 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM,
461 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION,
464 handler->BuildViewForPasswordManager(password_manager, explanation);
467 // ----------------------------------------------------------------------------
470 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
471 net::URLRequest* request) {
472 LoginHandler* handler = LoginHandler::Create(auth_info, request);
473 BrowserThread::PostTask(
474 BrowserThread::UI, FROM_HERE,
475 base::Bind(&LoginDialogCallback, request->url(),
476 make_scoped_refptr(auth_info), make_scoped_refptr(handler)));