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/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 "chrome/browser/ui/login/login_interstitial_delegate.h"
18 #include "components/password_manager/core/browser/browser_save_password_progress_logger.h"
19 #include "components/password_manager/core/browser/password_manager.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/browser/notification_registrar.h"
22 #include "content/public/browser/notification_service.h"
23 #include "content/public/browser/render_frame_host.h"
24 #include "content/public/browser/resource_dispatcher_host.h"
25 #include "content/public/browser/resource_request_info.h"
26 #include "content/public/browser/web_contents.h"
27 #include "grit/generated_resources.h"
28 #include "net/base/auth.h"
29 #include "net/base/load_flags.h"
30 #include "net/base/net_util.h"
31 #include "net/http/http_transaction_factory.h"
32 #include "net/url_request/url_request.h"
33 #include "net/url_request/url_request_context.h"
34 #include "ui/base/l10n/l10n_util.h"
35 #include "ui/gfx/text_elider.h"
36
37 using autofill::PasswordForm;
38 using content::BrowserThread;
39 using content::NavigationController;
40 using content::RenderViewHost;
41 using content::RenderViewHostDelegate;
42 using content::ResourceDispatcherHost;
43 using content::ResourceRequestInfo;
44 using content::WebContents;
45
46 class LoginHandlerImpl;
47
48 // Helper to remove the ref from an net::URLRequest to the LoginHandler.
49 // Should only be called from the IO thread, since it accesses an
50 // net::URLRequest.
ResetLoginHandlerForRequest(net::URLRequest * request)51 void ResetLoginHandlerForRequest(net::URLRequest* request) {
52 ResourceDispatcherHost::Get()->ClearLoginDelegateForRequest(request);
53 }
54
55 // Get the signon_realm under which this auth info should be stored.
56 //
57 // The format of the signon_realm for proxy auth is:
58 // proxy-host/auth-realm
59 // The format of the signon_realm for server auth is:
60 // url-scheme://url-host[:url-port]/auth-realm
61 //
62 // Be careful when changing this function, since you could make existing
63 // saved logins un-retrievable.
GetSignonRealm(const GURL & url,const net::AuthChallengeInfo & auth_info)64 std::string GetSignonRealm(const GURL& url,
65 const net::AuthChallengeInfo& auth_info) {
66 std::string signon_realm;
67 if (auth_info.is_proxy) {
68 signon_realm = auth_info.challenger.ToString();
69 signon_realm.append("/");
70 } else {
71 // Take scheme, host, and port from the url.
72 signon_realm = url.GetOrigin().spec();
73 // This ends with a "/".
74 }
75 signon_realm.append(auth_info.realm);
76 return signon_realm;
77 }
78
79 // ----------------------------------------------------------------------------
80 // LoginHandler
81
LoginHandler(net::AuthChallengeInfo * auth_info,net::URLRequest * request)82 LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
83 net::URLRequest* request)
84 : handled_auth_(false),
85 auth_info_(auth_info),
86 request_(request),
87 http_network_session_(
88 request_->context()->http_transaction_factory()->GetSession()),
89 password_manager_(NULL),
90 login_model_(NULL) {
91 // This constructor is called on the I/O thread, so we cannot load the nib
92 // here. BuildViewForPasswordManager() will be invoked on the UI thread
93 // later, so wait with loading the nib until then.
94 DCHECK(request_) << "LoginHandler constructed with NULL request";
95 DCHECK(auth_info_.get()) << "LoginHandler constructed with NULL auth info";
96
97 AddRef(); // matched by LoginHandler::ReleaseSoon().
98
99 BrowserThread::PostTask(
100 BrowserThread::UI, FROM_HERE,
101 base::Bind(&LoginHandler::AddObservers, this));
102
103 if (!ResourceRequestInfo::ForRequest(request_)->GetAssociatedRenderFrame(
104 &render_process_host_id_, &render_frame_id_)) {
105 NOTREACHED();
106 }
107 }
108
OnRequestCancelled()109 void LoginHandler::OnRequestCancelled() {
110 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) <<
111 "Why is OnRequestCancelled called from the UI thread?";
112
113 // Reference is no longer valid.
114 request_ = NULL;
115
116 // Give up on auth if the request was cancelled.
117 CancelAuth();
118 }
119
SetPasswordForm(const autofill::PasswordForm & form)120 void LoginHandler::SetPasswordForm(const autofill::PasswordForm& form) {
121 password_form_ = form;
122 }
123
SetPasswordManager(password_manager::PasswordManager * password_manager)124 void LoginHandler::SetPasswordManager(
125 password_manager::PasswordManager* password_manager) {
126 password_manager_ = password_manager;
127 }
128
GetWebContentsForLogin() const129 WebContents* LoginHandler::GetWebContentsForLogin() const {
130 DCHECK_CURRENTLY_ON(BrowserThread::UI);
131
132 content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
133 render_process_host_id_, render_frame_id_);
134 return WebContents::FromRenderFrameHost(rfh);
135 }
136
SetAuth(const base::string16 & username,const base::string16 & password)137 void LoginHandler::SetAuth(const base::string16& username,
138 const base::string16& password) {
139 DCHECK_CURRENTLY_ON(BrowserThread::UI);
140
141 scoped_ptr<password_manager::BrowserSavePasswordProgressLogger> logger;
142 if (password_manager_ && password_manager_->client()->IsLoggingActive()) {
143 logger.reset(new password_manager::BrowserSavePasswordProgressLogger(
144 password_manager_->client()));
145 logger->LogMessage(
146 autofill::SavePasswordProgressLogger::STRING_SET_AUTH_METHOD);
147 }
148
149 bool already_handled = TestAndSetAuthHandled();
150 if (logger) {
151 logger->LogBoolean(
152 autofill::SavePasswordProgressLogger::STRING_AUTHENTICATION_HANDLED,
153 already_handled);
154 }
155 if (already_handled)
156 return;
157
158 // Tell the password manager the credentials were submitted / accepted.
159 if (password_manager_) {
160 password_form_.username_value = username;
161 password_form_.password_value = password;
162 password_manager_->ProvisionallySavePassword(password_form_);
163 if (logger) {
164 logger->LogPasswordForm(
165 autofill::SavePasswordProgressLogger::STRING_LOGINHANDLER_FORM,
166 password_form_);
167 }
168 }
169
170 // Calling NotifyAuthSupplied() directly instead of posting a task
171 // allows other LoginHandler instances to queue their
172 // CloseContentsDeferred() before ours. Closing dialogs in the
173 // opposite order as they were created avoids races where remaining
174 // dialogs in the same tab may be briefly displayed to the user
175 // before they are removed.
176 NotifyAuthSupplied(username, password);
177
178 BrowserThread::PostTask(
179 BrowserThread::UI, FROM_HERE,
180 base::Bind(&LoginHandler::CloseContentsDeferred, this));
181 BrowserThread::PostTask(
182 BrowserThread::IO, FROM_HERE,
183 base::Bind(&LoginHandler::SetAuthDeferred, this, username, password));
184 }
185
CancelAuth()186 void LoginHandler::CancelAuth() {
187 if (TestAndSetAuthHandled())
188 return;
189
190 // Similar to how we deal with notifications above in SetAuth()
191 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
192 NotifyAuthCancelled();
193 } else {
194 BrowserThread::PostTask(
195 BrowserThread::UI, FROM_HERE,
196 base::Bind(&LoginHandler::NotifyAuthCancelled, this));
197 }
198
199 BrowserThread::PostTask(
200 BrowserThread::UI, FROM_HERE,
201 base::Bind(&LoginHandler::CloseContentsDeferred, this));
202 BrowserThread::PostTask(
203 BrowserThread::IO, FROM_HERE,
204 base::Bind(&LoginHandler::CancelAuthDeferred, this));
205 }
206
207
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)208 void LoginHandler::Observe(int type,
209 const content::NotificationSource& source,
210 const content::NotificationDetails& details) {
211 DCHECK_CURRENTLY_ON(BrowserThread::UI);
212 DCHECK(type == chrome::NOTIFICATION_AUTH_SUPPLIED ||
213 type == chrome::NOTIFICATION_AUTH_CANCELLED);
214
215 WebContents* requesting_contents = GetWebContentsForLogin();
216 if (!requesting_contents)
217 return;
218
219 // Break out early if we aren't interested in the notification.
220 if (WasAuthHandled())
221 return;
222
223 LoginNotificationDetails* login_details =
224 content::Details<LoginNotificationDetails>(details).ptr();
225
226 // WasAuthHandled() should always test positive before we publish
227 // AUTH_SUPPLIED or AUTH_CANCELLED notifications.
228 DCHECK(login_details->handler() != this);
229
230 // Only handle notification for the identical auth info.
231 if (!login_details->handler()->auth_info()->Equals(*auth_info()))
232 return;
233
234 // Ignore login notification events from other profiles.
235 if (login_details->handler()->http_network_session_ !=
236 http_network_session_)
237 return;
238
239 // Set or cancel the auth in this handler.
240 if (type == chrome::NOTIFICATION_AUTH_SUPPLIED) {
241 AuthSuppliedLoginNotificationDetails* supplied_details =
242 content::Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
243 SetAuth(supplied_details->username(), supplied_details->password());
244 } else {
245 DCHECK(type == chrome::NOTIFICATION_AUTH_CANCELLED);
246 CancelAuth();
247 }
248 }
249
250 // Returns whether authentication had been handled (SetAuth or CancelAuth).
WasAuthHandled() const251 bool LoginHandler::WasAuthHandled() const {
252 base::AutoLock lock(handled_auth_lock_);
253 bool was_handled = handled_auth_;
254 return was_handled;
255 }
256
~LoginHandler()257 LoginHandler::~LoginHandler() {
258 SetModel(NULL);
259 }
260
SetModel(password_manager::LoginModel * model)261 void LoginHandler::SetModel(password_manager::LoginModel* model) {
262 if (login_model_)
263 login_model_->RemoveObserver(this);
264 login_model_ = model;
265 if (login_model_)
266 login_model_->AddObserver(this);
267 }
268
NotifyAuthNeeded()269 void LoginHandler::NotifyAuthNeeded() {
270 DCHECK_CURRENTLY_ON(BrowserThread::UI);
271 if (WasAuthHandled())
272 return;
273
274 content::NotificationService* service =
275 content::NotificationService::current();
276 NavigationController* controller = NULL;
277
278 WebContents* requesting_contents = GetWebContentsForLogin();
279 if (requesting_contents)
280 controller = &requesting_contents->GetController();
281
282 LoginNotificationDetails details(this);
283
284 service->Notify(chrome::NOTIFICATION_AUTH_NEEDED,
285 content::Source<NavigationController>(controller),
286 content::Details<LoginNotificationDetails>(&details));
287 }
288
ReleaseSoon()289 void LoginHandler::ReleaseSoon() {
290 if (!TestAndSetAuthHandled()) {
291 BrowserThread::PostTask(
292 BrowserThread::IO, FROM_HERE,
293 base::Bind(&LoginHandler::CancelAuthDeferred, this));
294 BrowserThread::PostTask(
295 BrowserThread::UI, FROM_HERE,
296 base::Bind(&LoginHandler::NotifyAuthCancelled, this));
297 }
298
299 BrowserThread::PostTask(
300 BrowserThread::UI, FROM_HERE,
301 base::Bind(&LoginHandler::RemoveObservers, this));
302
303 // Delete this object once all InvokeLaters have been called.
304 BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this);
305 }
306
AddObservers()307 void LoginHandler::AddObservers() {
308 DCHECK_CURRENTLY_ON(BrowserThread::UI);
309
310 // This is probably OK; we need to listen to everything and we break out of
311 // the Observe() if we aren't handling the same auth_info().
312 registrar_.reset(new content::NotificationRegistrar);
313 registrar_->Add(this, chrome::NOTIFICATION_AUTH_SUPPLIED,
314 content::NotificationService::AllBrowserContextsAndSources());
315 registrar_->Add(this, chrome::NOTIFICATION_AUTH_CANCELLED,
316 content::NotificationService::AllBrowserContextsAndSources());
317 }
318
RemoveObservers()319 void LoginHandler::RemoveObservers() {
320 DCHECK_CURRENTLY_ON(BrowserThread::UI);
321
322 registrar_.reset();
323 }
324
NotifyAuthSupplied(const base::string16 & username,const base::string16 & password)325 void LoginHandler::NotifyAuthSupplied(const base::string16& username,
326 const base::string16& password) {
327 DCHECK_CURRENTLY_ON(BrowserThread::UI);
328 DCHECK(WasAuthHandled());
329
330 WebContents* requesting_contents = GetWebContentsForLogin();
331 if (!requesting_contents)
332 return;
333
334 content::NotificationService* service =
335 content::NotificationService::current();
336 NavigationController* controller =
337 &requesting_contents->GetController();
338 AuthSuppliedLoginNotificationDetails details(this, username, password);
339
340 service->Notify(
341 chrome::NOTIFICATION_AUTH_SUPPLIED,
342 content::Source<NavigationController>(controller),
343 content::Details<AuthSuppliedLoginNotificationDetails>(&details));
344 }
345
NotifyAuthCancelled()346 void LoginHandler::NotifyAuthCancelled() {
347 DCHECK_CURRENTLY_ON(BrowserThread::UI);
348 DCHECK(WasAuthHandled());
349
350 content::NotificationService* service =
351 content::NotificationService::current();
352 NavigationController* controller = NULL;
353
354 WebContents* requesting_contents = GetWebContentsForLogin();
355 if (requesting_contents)
356 controller = &requesting_contents->GetController();
357
358 LoginNotificationDetails details(this);
359
360 service->Notify(chrome::NOTIFICATION_AUTH_CANCELLED,
361 content::Source<NavigationController>(controller),
362 content::Details<LoginNotificationDetails>(&details));
363 }
364
365 // Marks authentication as handled and returns the previous handled state.
TestAndSetAuthHandled()366 bool LoginHandler::TestAndSetAuthHandled() {
367 base::AutoLock lock(handled_auth_lock_);
368 bool was_handled = handled_auth_;
369 handled_auth_ = true;
370 return was_handled;
371 }
372
373 // Calls SetAuth from the IO loop.
SetAuthDeferred(const base::string16 & username,const base::string16 & password)374 void LoginHandler::SetAuthDeferred(const base::string16& username,
375 const base::string16& password) {
376 DCHECK_CURRENTLY_ON(BrowserThread::IO);
377
378 if (request_) {
379 request_->SetAuth(net::AuthCredentials(username, password));
380 ResetLoginHandlerForRequest(request_);
381 }
382 }
383
384 // Calls CancelAuth from the IO loop.
CancelAuthDeferred()385 void LoginHandler::CancelAuthDeferred() {
386 DCHECK_CURRENTLY_ON(BrowserThread::IO);
387
388 if (request_) {
389 request_->CancelAuth();
390 // Verify that CancelAuth doesn't destroy the request via our delegate.
391 DCHECK(request_ != NULL);
392 ResetLoginHandlerForRequest(request_);
393 }
394 }
395
396 // Closes the view_contents from the UI loop.
CloseContentsDeferred()397 void LoginHandler::CloseContentsDeferred() {
398 DCHECK_CURRENTLY_ON(BrowserThread::UI);
399
400 CloseDialog();
401
402 WebContents* requesting_contents = GetWebContentsForLogin();
403 if (!requesting_contents)
404 return;
405 // If a (blank) login interstitial was displayed, proceed so that the
406 // navigation is committed.
407 content::InterstitialPage* interstitial_page =
408 requesting_contents->GetInterstitialPage();
409 if (interstitial_page)
410 interstitial_page->Proceed();
411 }
412
413 // Helper to create a PasswordForm and stuff it into a vector as input
414 // for PasswordManager::PasswordFormsParsed, the hook into PasswordManager.
MakeInputForPasswordManager(const GURL & request_url,net::AuthChallengeInfo * auth_info,LoginHandler * handler,std::vector<PasswordForm> * password_manager_input)415 void MakeInputForPasswordManager(
416 const GURL& request_url,
417 net::AuthChallengeInfo* auth_info,
418 LoginHandler* handler,
419 std::vector<PasswordForm>* password_manager_input) {
420 PasswordForm dialog_form;
421 if (LowerCaseEqualsASCII(auth_info->scheme, "basic")) {
422 dialog_form.scheme = PasswordForm::SCHEME_BASIC;
423 } else if (LowerCaseEqualsASCII(auth_info->scheme, "digest")) {
424 dialog_form.scheme = PasswordForm::SCHEME_DIGEST;
425 } else {
426 dialog_form.scheme = PasswordForm::SCHEME_OTHER;
427 }
428 std::string host_and_port(auth_info->challenger.ToString());
429 if (auth_info->is_proxy) {
430 std::string origin = host_and_port;
431 // We don't expect this to already start with http:// or https://.
432 DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0);
433 origin = std::string("http://") + origin;
434 dialog_form.origin = GURL(origin);
435 } else if (!auth_info->challenger.Equals(
436 net::HostPortPair::FromURL(request_url))) {
437 dialog_form.origin = GURL();
438 NOTREACHED(); // crbug.com/32718
439 } else {
440 dialog_form.origin = GURL(request_url.scheme() + "://" + host_and_port);
441 }
442 dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info);
443 password_manager_input->push_back(dialog_form);
444 // Set the password form for the handler (by copy).
445 handler->SetPasswordForm(dialog_form);
446 }
447
ShowLoginPrompt(const GURL & request_url,net::AuthChallengeInfo * auth_info,LoginHandler * handler)448 void ShowLoginPrompt(const GURL& request_url,
449 net::AuthChallengeInfo* auth_info,
450 LoginHandler* handler) {
451 DCHECK_CURRENTLY_ON(BrowserThread::UI);
452 WebContents* parent_contents = handler->GetWebContentsForLogin();
453 if (!parent_contents)
454 return;
455 prerender::PrerenderContents* prerender_contents =
456 prerender::PrerenderContents::FromWebContents(parent_contents);
457 if (prerender_contents) {
458 prerender_contents->Destroy(prerender::FINAL_STATUS_AUTH_NEEDED);
459 return;
460 }
461
462 password_manager::PasswordManager* password_manager =
463 ChromePasswordManagerClient::GetManagerFromWebContents(parent_contents);
464 if (!password_manager) {
465 // Same logic as above.
466 handler->CancelAuth();
467 return;
468 }
469
470 // Tell the password manager to look for saved passwords.
471 std::vector<PasswordForm> v;
472 MakeInputForPasswordManager(request_url, auth_info, handler, &v);
473 password_manager->OnPasswordFormsParsed(v);
474 handler->SetPasswordManager(password_manager);
475
476 // The realm is controlled by the remote server, so there is no reason
477 // to believe it is of a reasonable length.
478 base::string16 elided_realm;
479 gfx::ElideString(base::UTF8ToUTF16(auth_info->realm), 120, &elided_realm);
480
481 base::string16 host_and_port = base::ASCIIToUTF16(
482 request_url.scheme() + "://" + auth_info->challenger.ToString());
483 base::string16 explanation = elided_realm.empty() ?
484 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM,
485 host_and_port) :
486 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION,
487 host_and_port,
488 elided_realm);
489 handler->BuildViewForPasswordManager(password_manager, explanation);
490 }
491
492 // This callback is run on the UI thread and creates a constrained window with
493 // a LoginView to prompt the user. If the prompt is triggered because of
494 // a cross origin navigation in the main frame, a blank interstitial is first
495 // created which in turn creates the LoginView. Otherwise, a LoginView is
496 // directly in this callback. In both cases, the response will be sent to
497 // LoginHandler, which then routes it to the net::URLRequest on the I/O thread.
LoginDialogCallback(const GURL & request_url,net::AuthChallengeInfo * auth_info,LoginHandler * handler,bool is_main_frame)498 void LoginDialogCallback(const GURL& request_url,
499 net::AuthChallengeInfo* auth_info,
500 LoginHandler* handler,
501 bool is_main_frame) {
502 DCHECK_CURRENTLY_ON(BrowserThread::UI);
503 WebContents* parent_contents = handler->GetWebContentsForLogin();
504 if (!parent_contents || handler->WasAuthHandled()) {
505 // The request may have been cancelled, or it may be for a renderer
506 // not hosted by a tab (e.g. an extension). Cancel just in case
507 // (cancelling twice is a no-op).
508 handler->CancelAuth();
509 return;
510 }
511
512 if (is_main_frame &&
513 parent_contents->GetVisibleURL().GetOrigin() != request_url.GetOrigin()) {
514 // Show a blank interstitial for main-frame, cross origin requests
515 // so that the correct URL is shown in the omnibox.
516 base::Closure callback = base::Bind(&ShowLoginPrompt,
517 request_url,
518 make_scoped_refptr(auth_info),
519 make_scoped_refptr(handler));
520 // This is owned by the interstitial it creates.
521 new LoginInterstitialDelegate(parent_contents,
522 request_url,
523 callback);
524 } else {
525 ShowLoginPrompt(request_url,
526 auth_info,
527 handler);
528 }
529 }
530
531 // ----------------------------------------------------------------------------
532 // Public API
533
CreateLoginPrompt(net::AuthChallengeInfo * auth_info,net::URLRequest * request)534 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
535 net::URLRequest* request) {
536 bool is_main_frame = (request->load_flags() & net::LOAD_MAIN_FRAME) != 0;
537 LoginHandler* handler = LoginHandler::Create(auth_info, request);
538 BrowserThread::PostTask(
539 BrowserThread::UI, FROM_HERE,
540 base::Bind(&LoginDialogCallback, request->url(),
541 make_scoped_refptr(auth_info), make_scoped_refptr(handler),
542 is_main_frame));
543 return handler;
544 }
545