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