Disable TabDragController tests that fail with a real compositor.
[chromium-blink-merge.git] / chrome / browser / ui / login / login_prompt.cc
blob393fce0c886ef726ad649fb1fb1f672ee3097085
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/password_manager.h"
15 #include "chrome/browser/prerender/prerender_contents.h"
16 #include "chrome/browser/tab_contents/tab_util.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/notification_registrar.h"
19 #include "content/public/browser/notification_service.h"
20 #include "content/public/browser/render_frame_host.h"
21 #include "content/public/browser/resource_dispatcher_host.h"
22 #include "content/public/browser/resource_request_info.h"
23 #include "content/public/browser/web_contents.h"
24 #include "grit/generated_resources.h"
25 #include "net/base/auth.h"
26 #include "net/base/net_util.h"
27 #include "net/http/http_transaction_factory.h"
28 #include "net/url_request/url_request.h"
29 #include "net/url_request/url_request_context.h"
30 #include "ui/base/l10n/l10n_util.h"
31 #include "ui/gfx/text_elider.h"
33 using autofill::PasswordForm;
34 using content::BrowserThread;
35 using content::NavigationController;
36 using content::RenderViewHost;
37 using content::RenderViewHostDelegate;
38 using content::ResourceDispatcherHost;
39 using content::ResourceRequestInfo;
40 using content::WebContents;
42 class LoginHandlerImpl;
44 // Helper to remove the ref from an net::URLRequest to the LoginHandler.
45 // Should only be called from the IO thread, since it accesses an
46 // net::URLRequest.
47 void ResetLoginHandlerForRequest(net::URLRequest* request) {
48 ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request);
51 // Get the signon_realm under which this auth info should be stored.
53 // The format of the signon_realm for proxy auth is:
54 // proxy-host/auth-realm
55 // The format of the signon_realm for server auth is:
56 // url-scheme://url-host[:url-port]/auth-realm
58 // Be careful when changing this function, since you could make existing
59 // saved logins un-retrievable.
60 std::string GetSignonRealm(const GURL& url,
61 const net::AuthChallengeInfo& auth_info) {
62 std::string signon_realm;
63 if (auth_info.is_proxy) {
64 signon_realm = auth_info.challenger.ToString();
65 signon_realm.append("/");
66 } else {
67 // Take scheme, host, and port from the url.
68 signon_realm = url.GetOrigin().spec();
69 // This ends with a "/".
71 signon_realm.append(auth_info.realm);
72 return signon_realm;
75 // ----------------------------------------------------------------------------
76 // LoginHandler
78 LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
79 net::URLRequest* request)
80 : handled_auth_(false),
81 auth_info_(auth_info),
82 request_(request),
83 http_network_session_(
84 request_->context()->http_transaction_factory()->GetSession()),
85 password_manager_(NULL),
86 login_model_(NULL) {
87 // This constructor is called on the I/O thread, so we cannot load the nib
88 // here. BuildViewForPasswordManager() will be invoked on the UI thread
89 // later, so wait with loading the nib until then.
90 DCHECK(request_) << "LoginHandler constructed with NULL request";
91 DCHECK(auth_info_.get()) << "LoginHandler constructed with NULL auth info";
93 AddRef(); // matched by LoginHandler::ReleaseSoon().
95 BrowserThread::PostTask(
96 BrowserThread::UI, FROM_HERE,
97 base::Bind(&LoginHandler::AddObservers, this));
99 if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame(
100 &render_process_host_id_, &render_frame_id_)) {
101 NOTREACHED();
105 void LoginHandler::OnRequestCancelled() {
106 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) <<
107 "Why is OnRequestCancelled called from the UI thread?";
109 // Reference is no longer valid.
110 request_ = NULL;
112 // Give up on auth if the request was cancelled.
113 CancelAuth();
116 void LoginHandler::SetPasswordForm(const autofill::PasswordForm& form) {
117 password_form_ = form;
120 void LoginHandler::SetPasswordManager(PasswordManager* password_manager) {
121 password_manager_ = password_manager;
124 WebContents* LoginHandler::GetWebContentsForLogin() const {
125 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
127 content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
128 render_process_host_id_, render_frame_id_);
129 if (!rfh)
130 return NULL;
131 return WebContents::FromRenderFrameHost(rfh);
134 void LoginHandler::SetAuth(const base::string16& username,
135 const base::string16& password) {
136 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
138 if (TestAndSetAuthHandled())
139 return;
141 // Tell the password manager the credentials were submitted / accepted.
142 if (password_manager_) {
143 password_form_.username_value = username;
144 password_form_.password_value = password;
145 password_manager_->ProvisionallySavePassword(password_form_);
148 // Calling NotifyAuthSupplied() directly instead of posting a task
149 // allows other LoginHandler instances to queue their
150 // CloseContentsDeferred() before ours. Closing dialogs in the
151 // opposite order as they were created avoids races where remaining
152 // dialogs in the same tab may be briefly displayed to the user
153 // before they are removed.
154 NotifyAuthSupplied(username, password);
156 BrowserThread::PostTask(
157 BrowserThread::UI, FROM_HERE,
158 base::Bind(&LoginHandler::CloseContentsDeferred, this));
159 BrowserThread::PostTask(
160 BrowserThread::IO, FROM_HERE,
161 base::Bind(&LoginHandler::SetAuthDeferred, this, username, password));
164 void LoginHandler::CancelAuth() {
165 if (TestAndSetAuthHandled())
166 return;
168 // Similar to how we deal with notifications above in SetAuth()
169 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
170 NotifyAuthCancelled();
171 } else {
172 BrowserThread::PostTask(
173 BrowserThread::UI, FROM_HERE,
174 base::Bind(&LoginHandler::NotifyAuthCancelled, this));
177 BrowserThread::PostTask(
178 BrowserThread::UI, FROM_HERE,
179 base::Bind(&LoginHandler::CloseContentsDeferred, this));
180 BrowserThread::PostTask(
181 BrowserThread::IO, FROM_HERE,
182 base::Bind(&LoginHandler::CancelAuthDeferred, this));
186 void LoginHandler::Observe(int type,
187 const content::NotificationSource& source,
188 const content::NotificationDetails& details) {
189 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
190 DCHECK(type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
191 type == chrome::NOTIFICATION_AUTH_CANCELLED);
193 WebContents* requesting_contents = GetWebContentsForLogin();
194 if (!requesting_contents)
195 return;
197 // Break out early if we aren't interested in the notification.
198 if (WasAuthHandled())
199 return;
201 LoginNotificationDetails* login_details =
202 content::Details<LoginNotificationDetails>(details).ptr();
204 // WasAuthHandled() should always test positive before we publish
205 // AUTH_SUPPLIED or AUTH_CANCELLED notifications.
206 DCHECK(login_details->handler() != this);
208 // Only handle notification for the identical auth info.
209 if (!login_details->handler()->auth_info()->Equals(*auth_info()))
210 return;
212 // Ignore login notification events from other profiles.
213 if (login_details->handler()->http_network_session_ !=
214 http_network_session_)
215 return;
217 // Set or cancel the auth in this handler.
218 if (type == chrome::NOTIFICATION_AUTH_SUPPLIED) {
219 AuthSuppliedLoginNotificationDetails* supplied_details =
220 content::Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
221 SetAuth(supplied_details->username(), supplied_details->password());
222 } else {
223 DCHECK(type == chrome::NOTIFICATION_AUTH_CANCELLED);
224 CancelAuth();
228 // Returns whether authentication had been handled (SetAuth or CancelAuth).
229 bool LoginHandler::WasAuthHandled() const {
230 base::AutoLock lock(handled_auth_lock_);
231 bool was_handled = handled_auth_;
232 return was_handled;
235 LoginHandler::~LoginHandler() {
236 SetModel(NULL);
239 void LoginHandler::SetModel(LoginModel* model) {
240 if (login_model_)
241 login_model_->RemoveObserver(this);
242 login_model_ = model;
243 if (login_model_)
244 login_model_->AddObserver(this);
247 void LoginHandler::NotifyAuthNeeded() {
248 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
249 if (WasAuthHandled())
250 return;
252 content::NotificationService* service =
253 content::NotificationService::current();
254 NavigationController* controller = NULL;
256 WebContents* requesting_contents = GetWebContentsForLogin();
257 if (requesting_contents)
258 controller = &requesting_contents->GetController();
260 LoginNotificationDetails details(this);
262 service->Notify(chrome::NOTIFICATION_AUTH_NEEDED,
263 content::Source<NavigationController>(controller),
264 content::Details<LoginNotificationDetails>(&details));
267 void LoginHandler::ReleaseSoon() {
268 if (!TestAndSetAuthHandled()) {
269 BrowserThread::PostTask(
270 BrowserThread::IO, FROM_HERE,
271 base::Bind(&LoginHandler::CancelAuthDeferred, this));
272 BrowserThread::PostTask(
273 BrowserThread::UI, FROM_HERE,
274 base::Bind(&LoginHandler::NotifyAuthCancelled, this));
277 BrowserThread::PostTask(
278 BrowserThread::UI, FROM_HERE,
279 base::Bind(&LoginHandler::RemoveObservers, this));
281 // Delete this object once all InvokeLaters have been called.
282 BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this);
285 void LoginHandler::AddObservers() {
286 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
288 // This is probably OK; we need to listen to everything and we break out of
289 // the Observe() if we aren't handling the same auth_info().
290 registrar_.reset(new content::NotificationRegistrar);
291 registrar_->Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED,
292 content::NotificationService::AllBrowserContextsAndSources());
293 registrar_->Add(this, chrome::NOTIFICATION_AUTH_CANCELLED,
294 content::NotificationService::AllBrowserContextsAndSources());
297 void LoginHandler::RemoveObservers() {
298 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
300 registrar_.reset();
303 void LoginHandler::NotifyAuthSupplied(const base::string16& username,
304 const base::string16& password) {
305 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
306 DCHECK(WasAuthHandled());
308 WebContents* requesting_contents = GetWebContentsForLogin();
309 if (!requesting_contents)
310 return;
312 content::NotificationService* service =
313 content::NotificationService::current();
314 NavigationController* controller =
315 &requesting_contents->GetController();
316 AuthSuppliedLoginNotificationDetails details(this, username, password);
318 service->Notify(
319 chrome::NOTIFICATION_AUTH_SUPPLIED,
320 content::Source<NavigationController>(controller),
321 content::Details<AuthSuppliedLoginNotificationDetails>(&details));
324 void LoginHandler::NotifyAuthCancelled() {
325 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
326 DCHECK(WasAuthHandled());
328 content::NotificationService* service =
329 content::NotificationService::current();
330 NavigationController* controller = NULL;
332 WebContents* requesting_contents = GetWebContentsForLogin();
333 if (requesting_contents)
334 controller = &requesting_contents->GetController();
336 LoginNotificationDetails details(this);
338 service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED,
339 content::Source<NavigationController>(controller),
340 content::Details<LoginNotificationDetails>(&details));
343 // Marks authentication as handled and returns the previous handled state.
344 bool LoginHandler::TestAndSetAuthHandled() {
345 base::AutoLock lock(handled_auth_lock_);
346 bool was_handled = handled_auth_;
347 handled_auth_ = true;
348 return was_handled;
351 // Calls SetAuth from the IO loop.
352 void LoginHandler::SetAuthDeferred(const base::string16& username,
353 const base::string16& password) {
354 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
356 if (request_) {
357 request_->SetAuth(net::AuthCredentials(username, password));
358 ResetLoginHandlerForRequest(request_);
362 // Calls CancelAuth from the IO loop.
363 void LoginHandler::CancelAuthDeferred() {
364 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
366 if (request_) {
367 request_->CancelAuth();
368 // Verify that CancelAuth doesn't destroy the request via our delegate.
369 DCHECK(request_ != NULL);
370 ResetLoginHandlerForRequest(request_);
374 // Closes the view_contents from the UI loop.
375 void LoginHandler::CloseContentsDeferred() {
376 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
378 CloseDialog();
381 // Helper to create a PasswordForm and stuff it into a vector as input
382 // for PasswordManager::PasswordFormsParsed, the hook into PasswordManager.
383 void MakeInputForPasswordManager(
384 const GURL& request_url,
385 net::AuthChallengeInfo* auth_info,
386 LoginHandler* handler,
387 std::vector<PasswordForm>* password_manager_input) {
388 PasswordForm dialog_form;
389 if (LowerCaseEqualsASCII(auth_info->scheme, "basic")) {
390 dialog_form.scheme = PasswordForm::SCHEME_BASIC;
391 } else if (LowerCaseEqualsASCII(auth_info->scheme, "digest")) {
392 dialog_form.scheme = PasswordForm::SCHEME_DIGEST;
393 } else {
394 dialog_form.scheme = PasswordForm::SCHEME_OTHER;
396 std::string host_and_port(auth_info->challenger.ToString());
397 if (auth_info->is_proxy) {
398 std::string origin = host_and_port;
399 // We don't expect this to already start with http:// or https://.
400 DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0);
401 origin = std::string("http://") + origin;
402 dialog_form.origin = GURL(origin);
403 } else if (!auth_info->challenger.Equals(
404 net::HostPortPair::FromURL(request_url))) {
405 dialog_form.origin = GURL();
406 NOTREACHED(); // crbug.com/32718
407 } else {
408 dialog_form.origin = GURL(request_url.scheme() + "://" + host_and_port);
410 dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info);
411 password_manager_input->push_back(dialog_form);
412 // Set the password form for the handler (by copy).
413 handler->SetPasswordForm(dialog_form);
416 // This callback is run on the UI thread and creates a constrained window with
417 // a LoginView to prompt the user. The response will be sent to LoginHandler,
418 // which then routes it to the net::URLRequest on the I/O thread.
419 void LoginDialogCallback(const GURL& request_url,
420 net::AuthChallengeInfo* auth_info,
421 LoginHandler* handler) {
422 WebContents* parent_contents = handler->GetWebContentsForLogin();
423 if (!parent_contents || handler->WasAuthHandled()) {
424 // The request may have been cancelled, or it may be for a renderer
425 // not hosted by a tab (e.g. an extension). Cancel just in case
426 // (cancelling twice is a no-op).
427 handler->CancelAuth();
428 return;
431 prerender::PrerenderContents* prerender_contents =
432 prerender::PrerenderContents::FromWebContents(parent_contents);
433 if (prerender_contents) {
434 prerender_contents->Destroy(prerender::FINAL_STATUS_AUTH_NEEDED);
435 return;
438 PasswordManager* password_manager =
439 PasswordManager::FromWebContents(parent_contents);
440 if (!password_manager) {
441 // Same logic as above.
442 handler->CancelAuth();
443 return;
446 // Tell the password manager to look for saved passwords.
447 std::vector<PasswordForm> v;
448 MakeInputForPasswordManager(request_url, auth_info, handler, &v);
449 password_manager->OnPasswordFormsParsed(v);
450 handler->SetPasswordManager(password_manager);
452 // The realm is controlled by the remote server, so there is no reason
453 // to believe it is of a reasonable length.
454 base::string16 elided_realm;
455 gfx::ElideString(base::UTF8ToUTF16(auth_info->realm), 120, &elided_realm);
457 base::string16 host_and_port = base::ASCIIToUTF16(
458 request_url.scheme() + "://" + auth_info->challenger.ToString());
459 base::string16 explanation = elided_realm.empty() ?
460 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM,
461 host_and_port) :
462 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION,
463 host_and_port,
464 elided_realm);
465 handler->BuildViewForPasswordManager(password_manager, explanation);
468 // ----------------------------------------------------------------------------
469 // Public API
471 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
472 net::URLRequest* request) {
473 LoginHandler* handler = LoginHandler::Create(auth_info, request);
474 BrowserThread::PostTask(
475 BrowserThread::UI, FROM_HERE,
476 base::Bind(&LoginDialogCallback, request->url(),
477 make_scoped_refptr(auth_info), make_scoped_refptr(handler)));
478 return handler;