[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / login / login_prompt.cc
blobc15bd1866755af020808a15738c443acd61ded13
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"
7 #include <vector>
9 #include "base/bind.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/prerender/prerender_contents.h"
16 #include "chrome/browser/tab_contents/tab_util.h"
17 #include "components/password_manager/core/browser/browser_save_password_progress_logger.h"
18 #include "components/password_manager/core/browser/password_manager.h"
19 #include "content/public/browser/browser_thread.h"
20 #include "content/public/browser/notification_registrar.h"
21 #include "content/public/browser/notification_service.h"
22 #include "content/public/browser/render_frame_host.h"
23 #include "content/public/browser/resource_dispatcher_host.h"
24 #include "content/public/browser/resource_request_info.h"
25 #include "content/public/browser/web_contents.h"
26 #include "grit/generated_resources.h"
27 #include "net/base/auth.h"
28 #include "net/base/net_util.h"
29 #include "net/http/http_transaction_factory.h"
30 #include "net/url_request/url_request.h"
31 #include "net/url_request/url_request_context.h"
32 #include "ui/base/l10n/l10n_util.h"
33 #include "ui/gfx/text_elider.h"
35 using autofill::PasswordForm;
36 using content::BrowserThread;
37 using content::NavigationController;
38 using content::RenderViewHost;
39 using content::RenderViewHostDelegate;
40 using content::ResourceDispatcherHost;
41 using content::ResourceRequestInfo;
42 using content::WebContents;
44 class LoginHandlerImpl;
46 // Helper to remove the ref from an net::URLRequest to the LoginHandler.
47 // Should only be called from the IO thread, since it accesses an
48 // net::URLRequest.
49 void ResetLoginHandlerForRequest(net::URLRequest* request) {
50 ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request);
53 // Get the signon_realm under which this auth info should be stored.
55 // The format of the signon_realm for proxy auth is:
56 // proxy-host/auth-realm
57 // The format of the signon_realm for server auth is:
58 // url-scheme://url-host[:url-port]/auth-realm
60 // Be careful when changing this function, since you could make existing
61 // saved logins un-retrievable.
62 std::string GetSignonRealm(const GURL& url,
63 const net::AuthChallengeInfo& auth_info) {
64 std::string signon_realm;
65 if (auth_info.is_proxy) {
66 signon_realm = auth_info.challenger.ToString();
67 signon_realm.append("/");
68 } else {
69 // Take scheme, host, and port from the url.
70 signon_realm = url.GetOrigin().spec();
71 // This ends with a "/".
73 signon_realm.append(auth_info.realm);
74 return signon_realm;
77 // ----------------------------------------------------------------------------
78 // LoginHandler
80 LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
81 net::URLRequest* request)
82 : handled_auth_(false),
83 auth_info_(auth_info),
84 request_(request),
85 http_network_session_(
86 request_->context()->http_transaction_factory()->GetSession()),
87 password_manager_(NULL),
88 login_model_(NULL) {
89 // This constructor is called on the I/O thread, so we cannot load the nib
90 // here. BuildViewForPasswordManager() will be invoked on the UI thread
91 // later, so wait with loading the nib until then.
92 DCHECK(request_) << "LoginHandler constructed with NULL request";
93 DCHECK(auth_info_.get()) << "LoginHandler constructed with NULL auth info";
95 AddRef(); // matched by LoginHandler::ReleaseSoon().
97 BrowserThread::PostTask(
98 BrowserThread::UI, FROM_HERE,
99 base::Bind(&LoginHandler::AddObservers, this));
101 if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame(
102 &render_process_host_id_, &render_frame_id_)) {
103 NOTREACHED();
107 void LoginHandler::OnRequestCancelled() {
108 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) <<
109 "Why is OnRequestCancelled called from the UI thread?";
111 // Reference is no longer valid.
112 request_ = NULL;
114 // Give up on auth if the request was cancelled.
115 CancelAuth();
118 void LoginHandler::SetPasswordForm(const autofill::PasswordForm& form) {
119 password_form_ = form;
122 void LoginHandler::SetPasswordManager(
123 password_manager::PasswordManager* password_manager) {
124 password_manager_ = password_manager;
127 WebContents* LoginHandler::GetWebContentsForLogin() const {
128 DCHECK_CURRENTLY_ON(BrowserThread::UI);
130 content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
131 render_process_host_id_, render_frame_id_);
132 return WebContents::FromRenderFrameHost(rfh);
135 void LoginHandler::SetAuth(const base::string16& username,
136 const base::string16& password) {
137 DCHECK_CURRENTLY_ON(BrowserThread::UI);
139 scoped_ptr<password_manager::BrowserSavePasswordProgressLogger> logger;
140 if (password_manager_ && password_manager_->client()->IsLoggingActive()) {
141 logger.reset(new password_manager::BrowserSavePasswordProgressLogger(
142 password_manager_->client()));
143 logger->LogMessage(
144 autofill::SavePasswordProgressLogger::STRING_SET_AUTH_METHOD);
147 bool already_handled = TestAndSetAuthHandled();
148 if (logger) {
149 logger->LogBoolean(
150 autofill::SavePasswordProgressLogger::STRING_AUTHENTICATION_HANDLED,
151 already_handled);
153 if (already_handled)
154 return;
156 // Tell the password manager the credentials were submitted / accepted.
157 if (password_manager_) {
158 password_form_.username_value = username;
159 password_form_.password_value = password;
160 password_manager_->ProvisionallySavePassword(password_form_);
161 if (logger) {
162 logger->LogPasswordForm(
163 autofill::SavePasswordProgressLogger::STRING_LOGINHANDLER_FORM,
164 password_form_);
168 // Calling NotifyAuthSupplied() directly instead of posting a task
169 // allows other LoginHandler instances to queue their
170 // CloseContentsDeferred() before ours. Closing dialogs in the
171 // opposite order as they were created avoids races where remaining
172 // dialogs in the same tab may be briefly displayed to the user
173 // before they are removed.
174 NotifyAuthSupplied(username, password);
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::SetAuthDeferred, this, username, password));
184 void LoginHandler::CancelAuth() {
185 if (TestAndSetAuthHandled())
186 return;
188 // Similar to how we deal with notifications above in SetAuth()
189 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
190 NotifyAuthCancelled();
191 } else {
192 BrowserThread::PostTask(
193 BrowserThread::UI, FROM_HERE,
194 base::Bind(&LoginHandler::NotifyAuthCancelled, this));
197 BrowserThread::PostTask(
198 BrowserThread::UI, FROM_HERE,
199 base::Bind(&LoginHandler::CloseContentsDeferred, this));
200 BrowserThread::PostTask(
201 BrowserThread::IO, FROM_HERE,
202 base::Bind(&LoginHandler::CancelAuthDeferred, this));
206 void LoginHandler::Observe(int type,
207 const content::NotificationSource& source,
208 const content::NotificationDetails& details) {
209 DCHECK_CURRENTLY_ON(BrowserThread::UI);
210 DCHECK(type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
211 type == chrome::NOTIFICATION_AUTH_CANCELLED);
213 WebContents* requesting_contents = GetWebContentsForLogin();
214 if (!requesting_contents)
215 return;
217 // Break out early if we aren't interested in the notification.
218 if (WasAuthHandled())
219 return;
221 LoginNotificationDetails* login_details =
222 content::Details<LoginNotificationDetails>(details).ptr();
224 // WasAuthHandled() should always test positive before we publish
225 // AUTH_SUPPLIED or AUTH_CANCELLED notifications.
226 DCHECK(login_details->handler() != this);
228 // Only handle notification for the identical auth info.
229 if (!login_details->handler()->auth_info()->Equals(*auth_info()))
230 return;
232 // Ignore login notification events from other profiles.
233 if (login_details->handler()->http_network_session_ !=
234 http_network_session_)
235 return;
237 // Set or cancel the auth in this handler.
238 if (type == chrome::NOTIFICATION_AUTH_SUPPLIED) {
239 AuthSuppliedLoginNotificationDetails* supplied_details =
240 content::Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
241 SetAuth(supplied_details->username(), supplied_details->password());
242 } else {
243 DCHECK(type == chrome::NOTIFICATION_AUTH_CANCELLED);
244 CancelAuth();
248 // Returns whether authentication had been handled (SetAuth or CancelAuth).
249 bool LoginHandler::WasAuthHandled() const {
250 base::AutoLock lock(handled_auth_lock_);
251 bool was_handled = handled_auth_;
252 return was_handled;
255 LoginHandler::~LoginHandler() {
256 SetModel(NULL);
259 void LoginHandler::SetModel(password_manager::LoginModel* model) {
260 if (login_model_)
261 login_model_->RemoveObserver(this);
262 login_model_ = model;
263 if (login_model_)
264 login_model_->AddObserver(this);
267 void LoginHandler::NotifyAuthNeeded() {
268 DCHECK_CURRENTLY_ON(BrowserThread::UI);
269 if (WasAuthHandled())
270 return;
272 content::NotificationService* service =
273 content::NotificationService::current();
274 NavigationController* controller = NULL;
276 WebContents* requesting_contents = GetWebContentsForLogin();
277 if (requesting_contents)
278 controller = &requesting_contents->GetController();
280 LoginNotificationDetails details(this);
282 service->Notify(chrome::NOTIFICATION_AUTH_NEEDED,
283 content::Source<NavigationController>(controller),
284 content::Details<LoginNotificationDetails>(&details));
287 void LoginHandler::ReleaseSoon() {
288 if (!TestAndSetAuthHandled()) {
289 BrowserThread::PostTask(
290 BrowserThread::IO, FROM_HERE,
291 base::Bind(&LoginHandler::CancelAuthDeferred, this));
292 BrowserThread::PostTask(
293 BrowserThread::UI, FROM_HERE,
294 base::Bind(&LoginHandler::NotifyAuthCancelled, this));
297 BrowserThread::PostTask(
298 BrowserThread::UI, FROM_HERE,
299 base::Bind(&LoginHandler::RemoveObservers, this));
301 // Delete this object once all InvokeLaters have been called.
302 BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this);
305 void LoginHandler::AddObservers() {
306 DCHECK_CURRENTLY_ON(BrowserThread::UI);
308 // This is probably OK; we need to listen to everything and we break out of
309 // the Observe() if we aren't handling the same auth_info().
310 registrar_.reset(new content::NotificationRegistrar);
311 registrar_->Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED,
312 content::NotificationService::AllBrowserContextsAndSources());
313 registrar_->Add(this, chrome::NOTIFICATION_AUTH_CANCELLED,
314 content::NotificationService::AllBrowserContextsAndSources());
317 void LoginHandler::RemoveObservers() {
318 DCHECK_CURRENTLY_ON(BrowserThread::UI);
320 registrar_.reset();
323 void LoginHandler::NotifyAuthSupplied(const base::string16& username,
324 const base::string16& password) {
325 DCHECK_CURRENTLY_ON(BrowserThread::UI);
326 DCHECK(WasAuthHandled());
328 WebContents* requesting_contents = GetWebContentsForLogin();
329 if (!requesting_contents)
330 return;
332 content::NotificationService* service =
333 content::NotificationService::current();
334 NavigationController* controller =
335 &requesting_contents->GetController();
336 AuthSuppliedLoginNotificationDetails details(this, username, password);
338 service->Notify(
339 chrome::NOTIFICATION_AUTH_SUPPLIED,
340 content::Source<NavigationController>(controller),
341 content::Details<AuthSuppliedLoginNotificationDetails>(&details));
344 void LoginHandler::NotifyAuthCancelled() {
345 DCHECK_CURRENTLY_ON(BrowserThread::UI);
346 DCHECK(WasAuthHandled());
348 content::NotificationService* service =
349 content::NotificationService::current();
350 NavigationController* controller = NULL;
352 WebContents* requesting_contents = GetWebContentsForLogin();
353 if (requesting_contents)
354 controller = &requesting_contents->GetController();
356 LoginNotificationDetails details(this);
358 service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED,
359 content::Source<NavigationController>(controller),
360 content::Details<LoginNotificationDetails>(&details));
363 // Marks authentication as handled and returns the previous handled state.
364 bool LoginHandler::TestAndSetAuthHandled() {
365 base::AutoLock lock(handled_auth_lock_);
366 bool was_handled = handled_auth_;
367 handled_auth_ = true;
368 return was_handled;
371 // Calls SetAuth from the IO loop.
372 void LoginHandler::SetAuthDeferred(const base::string16& username,
373 const base::string16& password) {
374 DCHECK_CURRENTLY_ON(BrowserThread::IO);
376 if (request_) {
377 request_->SetAuth(net::AuthCredentials(username, password));
378 ResetLoginHandlerForRequest(request_);
382 // Calls CancelAuth from the IO loop.
383 void LoginHandler::CancelAuthDeferred() {
384 DCHECK_CURRENTLY_ON(BrowserThread::IO);
386 if (request_) {
387 request_->CancelAuth();
388 // Verify that CancelAuth doesn't destroy the request via our delegate.
389 DCHECK(request_ != NULL);
390 ResetLoginHandlerForRequest(request_);
394 // Closes the view_contents from the UI loop.
395 void LoginHandler::CloseContentsDeferred() {
396 DCHECK_CURRENTLY_ON(BrowserThread::UI);
398 CloseDialog();
401 // Helper to create a PasswordForm and stuff it into a vector as input
402 // for PasswordManager::PasswordFormsParsed, the hook into PasswordManager.
403 void MakeInputForPasswordManager(
404 const GURL& request_url,
405 net::AuthChallengeInfo* auth_info,
406 LoginHandler* handler,
407 std::vector<PasswordForm>* password_manager_input) {
408 PasswordForm dialog_form;
409 if (LowerCaseEqualsASCII(auth_info->scheme, "basic")) {
410 dialog_form.scheme = PasswordForm::SCHEME_BASIC;
411 } else if (LowerCaseEqualsASCII(auth_info->scheme, "digest")) {
412 dialog_form.scheme = PasswordForm::SCHEME_DIGEST;
413 } else {
414 dialog_form.scheme = PasswordForm::SCHEME_OTHER;
416 std::string host_and_port(auth_info->challenger.ToString());
417 if (auth_info->is_proxy) {
418 std::string origin = host_and_port;
419 // We don't expect this to already start with http:// or https://.
420 DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0);
421 origin = std::string("http://") + origin;
422 dialog_form.origin = GURL(origin);
423 } else if (!auth_info->challenger.Equals(
424 net::HostPortPair::FromURL(request_url))) {
425 dialog_form.origin = GURL();
426 NOTREACHED(); // crbug.com/32718
427 } else {
428 dialog_form.origin = GURL(request_url.scheme() + "://" + host_and_port);
430 dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info);
431 password_manager_input->push_back(dialog_form);
432 // Set the password form for the handler (by copy).
433 handler->SetPasswordForm(dialog_form);
436 // This callback is run on the UI thread and creates a constrained window with
437 // a LoginView to prompt the user. The response will be sent to LoginHandler,
438 // which then routes it to the net::URLRequest on the I/O thread.
439 void LoginDialogCallback(const GURL& request_url,
440 net::AuthChallengeInfo* auth_info,
441 LoginHandler* handler) {
442 WebContents* parent_contents = handler->GetWebContentsForLogin();
443 if (!parent_contents || handler->WasAuthHandled()) {
444 // The request may have been cancelled, or it may be for a renderer
445 // not hosted by a tab (e.g. an extension). Cancel just in case
446 // (cancelling twice is a no-op).
447 handler->CancelAuth();
448 return;
451 prerender::PrerenderContents* prerender_contents =
452 prerender::PrerenderContents::FromWebContents(parent_contents);
453 if (prerender_contents) {
454 prerender_contents->Destroy(prerender::FINAL_STATUS_AUTH_NEEDED);
455 return;
458 password_manager::PasswordManager* password_manager =
459 ChromePasswordManagerClient::GetManagerFromWebContents(parent_contents);
460 if (!password_manager) {
461 // Same logic as above.
462 handler->CancelAuth();
463 return;
466 // Tell the password manager to look for saved passwords.
467 std::vector<PasswordForm> v;
468 MakeInputForPasswordManager(request_url, auth_info, handler, &v);
469 password_manager->OnPasswordFormsParsed(v);
470 handler->SetPasswordManager(password_manager);
472 // The realm is controlled by the remote server, so there is no reason
473 // to believe it is of a reasonable length.
474 base::string16 elided_realm;
475 gfx::ElideString(base::UTF8ToUTF16(auth_info->realm), 120, &elided_realm);
477 base::string16 host_and_port = base::ASCIIToUTF16(
478 request_url.scheme() + "://" + auth_info->challenger.ToString());
479 base::string16 explanation = elided_realm.empty() ?
480 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM,
481 host_and_port) :
482 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION,
483 host_and_port,
484 elided_realm);
485 handler->BuildViewForPasswordManager(password_manager, explanation);
488 // ----------------------------------------------------------------------------
489 // Public API
491 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
492 net::URLRequest* request) {
493 LoginHandler* handler = LoginHandler::Create(auth_info, request);
494 BrowserThread::PostTask(
495 BrowserThread::UI, FROM_HERE,
496 base::Bind(&LoginDialogCallback, request->url(),
497 make_scoped_refptr(auth_info), make_scoped_refptr(handler)));
498 return handler;