1 // Copyright (c) 2011 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/command_line.h"
10 #include "base/synchronization/lock.h"
11 #include "base/utf_string_conversions.h"
12 #include "chrome/browser/password_manager/password_manager.h"
13 #include "chrome/browser/tab_contents/tab_util.h"
14 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
15 #include "content/browser/browser_thread.h"
16 #include "content/browser/renderer_host/render_process_host.h"
17 #include "content/browser/renderer_host/render_view_host.h"
18 #include "content/browser/renderer_host/render_view_host_delegate.h"
19 #include "content/browser/renderer_host/resource_dispatcher_host.h"
20 #include "content/browser/renderer_host/resource_dispatcher_host_request_info.h"
21 #include "content/browser/tab_contents/constrained_window.h"
22 #include "content/browser/tab_contents/tab_contents.h"
23 #include "content/common/notification_service.h"
24 #include "grit/generated_resources.h"
25 #include "net/base/auth.h"
26 #include "net/base/net_util.h"
27 #include "net/url_request/url_request.h"
28 #include "ui/base/l10n/l10n_util.h"
29
30 using webkit_glue::PasswordForm;
31
32 class LoginHandlerImpl;
33
34 // Helper to remove the ref from an net::URLRequest to the LoginHandler.
35 // Should only be called from the IO thread, since it accesses an
36 // net::URLRequest.
ResetLoginHandlerForRequest(net::URLRequest * request)37 void ResetLoginHandlerForRequest(net::URLRequest* request) {
38 ResourceDispatcherHostRequestInfo* info =
39 ResourceDispatcherHost::InfoForRequest(request);
40 if (!info)
41 return;
42
43 info->set_login_handler(NULL);
44 }
45
46 // Get the signon_realm under which this auth info should be stored.
47 //
48 // The format of the signon_realm for proxy auth is:
49 // proxy-host/auth-realm
50 // The format of the signon_realm for server auth is:
51 // url-scheme://url-host[:url-port]/auth-realm
52 //
53 // Be careful when changing this function, since you could make existing
54 // saved logins un-retrievable.
GetSignonRealm(const GURL & url,const net::AuthChallengeInfo & auth_info)55 std::string GetSignonRealm(const GURL& url,
56 const net::AuthChallengeInfo& auth_info) {
57 std::string signon_realm;
58 if (auth_info.is_proxy) {
59 signon_realm = WideToASCII(auth_info.host_and_port);
60 signon_realm.append("/");
61 } else {
62 // Take scheme, host, and port from the url.
63 signon_realm = url.GetOrigin().spec();
64 // This ends with a "/".
65 }
66 signon_realm.append(WideToUTF8(auth_info.realm));
67 return signon_realm;
68 }
69
70 // ----------------------------------------------------------------------------
71 // LoginHandler
72
LoginHandler(net::AuthChallengeInfo * auth_info,net::URLRequest * request)73 LoginHandler::LoginHandler(net::AuthChallengeInfo* auth_info,
74 net::URLRequest* request)
75 : handled_auth_(false),
76 dialog_(NULL),
77 auth_info_(auth_info),
78 request_(request),
79 password_manager_(NULL),
80 login_model_(NULL) {
81 // This constructor is called on the I/O thread, so we cannot load the nib
82 // here. BuildViewForPasswordManager() will be invoked on the UI thread
83 // later, so wait with loading the nib until then.
84 DCHECK(request_) << "LoginHandler constructed with NULL request";
85 DCHECK(auth_info_) << "LoginHandler constructed with NULL auth info";
86
87 AddRef(); // matched by LoginHandler::ReleaseSoon().
88
89 BrowserThread::PostTask(
90 BrowserThread::UI, FROM_HERE,
91 NewRunnableMethod(this, &LoginHandler::AddObservers));
92
93 if (!ResourceDispatcherHost::RenderViewForRequest(
94 request_, &render_process_host_id_, &tab_contents_id_)) {
95 NOTREACHED();
96 }
97 }
98
~LoginHandler()99 LoginHandler::~LoginHandler() {
100 SetModel(NULL);
101 }
102
SetPasswordForm(const webkit_glue::PasswordForm & form)103 void LoginHandler::SetPasswordForm(const webkit_glue::PasswordForm& form) {
104 password_form_ = form;
105 }
106
SetPasswordManager(PasswordManager * password_manager)107 void LoginHandler::SetPasswordManager(PasswordManager* password_manager) {
108 password_manager_ = password_manager;
109 }
110
GetTabContentsForLogin() const111 TabContents* LoginHandler::GetTabContentsForLogin() const {
112 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
113
114 return tab_util::GetTabContentsByID(render_process_host_id_,
115 tab_contents_id_);
116 }
117
GetRenderViewHostDelegate() const118 RenderViewHostDelegate* LoginHandler::GetRenderViewHostDelegate() const {
119 RenderViewHost* rvh = RenderViewHost::FromID(render_process_host_id_,
120 tab_contents_id_);
121 if (!rvh)
122 return NULL;
123
124 return rvh->delegate();
125 }
126
SetAuth(const string16 & username,const string16 & password)127 void LoginHandler::SetAuth(const string16& username,
128 const string16& password) {
129 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
130
131 if (TestAndSetAuthHandled())
132 return;
133
134 // Tell the password manager the credentials were submitted / accepted.
135 if (password_manager_) {
136 password_form_.username_value = username;
137 password_form_.password_value = password;
138 password_manager_->ProvisionallySavePassword(password_form_);
139 }
140
141 // Calling NotifyAuthSupplied() directly instead of posting a task
142 // allows other LoginHandler instances to queue their
143 // CloseContentsDeferred() before ours. Closing dialogs in the
144 // opposite order as they were created avoids races where remaining
145 // dialogs in the same tab may be briefly displayed to the user
146 // before they are removed.
147 NotifyAuthSupplied(username, password);
148
149 BrowserThread::PostTask(
150 BrowserThread::UI, FROM_HERE,
151 NewRunnableMethod(this, &LoginHandler::CloseContentsDeferred));
152 BrowserThread::PostTask(
153 BrowserThread::IO, FROM_HERE,
154 NewRunnableMethod(
155 this, &LoginHandler::SetAuthDeferred, username, password));
156 }
157
CancelAuth()158 void LoginHandler::CancelAuth() {
159 if (TestAndSetAuthHandled())
160 return;
161
162 // Similar to how we deal with notifications above in SetAuth()
163 if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
164 NotifyAuthCancelled();
165 } else {
166 BrowserThread::PostTask(
167 BrowserThread::UI, FROM_HERE,
168 NewRunnableMethod(this, &LoginHandler::NotifyAuthCancelled));
169 }
170
171 BrowserThread::PostTask(
172 BrowserThread::UI, FROM_HERE,
173 NewRunnableMethod(this, &LoginHandler::CloseContentsDeferred));
174 BrowserThread::PostTask(
175 BrowserThread::IO, FROM_HERE,
176 NewRunnableMethod(this, &LoginHandler::CancelAuthDeferred));
177 }
178
OnRequestCancelled()179 void LoginHandler::OnRequestCancelled() {
180 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)) <<
181 "Why is OnRequestCancelled called from the UI thread?";
182
183 // Reference is no longer valid.
184 request_ = NULL;
185
186 // Give up on auth if the request was cancelled.
187 CancelAuth();
188 }
189
AddObservers()190 void LoginHandler::AddObservers() {
191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
192
193 registrar_.Add(this, NotificationType::AUTH_SUPPLIED,
194 NotificationService::AllSources());
195 registrar_.Add(this, NotificationType::AUTH_CANCELLED,
196 NotificationService::AllSources());
197 }
198
RemoveObservers()199 void LoginHandler::RemoveObservers() {
200 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
201
202 registrar_.Remove(this, NotificationType::AUTH_SUPPLIED,
203 NotificationService::AllSources());
204 registrar_.Remove(this, NotificationType::AUTH_CANCELLED,
205 NotificationService::AllSources());
206
207 DCHECK(registrar_.IsEmpty());
208 }
209
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)210 void LoginHandler::Observe(NotificationType type,
211 const NotificationSource& source,
212 const NotificationDetails& details) {
213 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
214 DCHECK(type == NotificationType::AUTH_SUPPLIED ||
215 type == NotificationType::AUTH_CANCELLED);
216
217 TabContents* requesting_contents = GetTabContentsForLogin();
218 if (!requesting_contents)
219 return;
220
221 // Break out early if we aren't interested in the notification.
222 if (WasAuthHandled())
223 return;
224
225 LoginNotificationDetails* login_details =
226 Details<LoginNotificationDetails>(details).ptr();
227
228 // WasAuthHandled() should always test positive before we publish
229 // AUTH_SUPPLIED or AUTH_CANCELLED notifications.
230 DCHECK(login_details->handler() != this);
231
232 // Only handle notification for the identical auth info.
233 if (*login_details->handler()->auth_info() != *auth_info())
234 return;
235
236 // Set or cancel the auth in this handler.
237 if (type == NotificationType::AUTH_SUPPLIED) {
238 AuthSuppliedLoginNotificationDetails* supplied_details =
239 Details<AuthSuppliedLoginNotificationDetails>(details).ptr();
240 SetAuth(supplied_details->username(), supplied_details->password());
241 } else {
242 DCHECK(type == NotificationType::AUTH_CANCELLED);
243 CancelAuth();
244 }
245 }
246
SetModel(LoginModel * model)247 void LoginHandler::SetModel(LoginModel* model) {
248 if (login_model_)
249 login_model_->SetObserver(NULL);
250 login_model_ = model;
251 if (login_model_)
252 login_model_->SetObserver(this);
253 }
254
SetDialog(ConstrainedWindow * dialog)255 void LoginHandler::SetDialog(ConstrainedWindow* dialog) {
256 dialog_ = dialog;
257 }
258
NotifyAuthNeeded()259 void LoginHandler::NotifyAuthNeeded() {
260 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
261 if (WasAuthHandled())
262 return;
263
264 NotificationService* service = NotificationService::current();
265 NavigationController* controller = NULL;
266
267 TabContents* requesting_contents = GetTabContentsForLogin();
268 if (requesting_contents)
269 controller = &requesting_contents->controller();
270
271 LoginNotificationDetails details(this);
272
273 service->Notify(NotificationType::AUTH_NEEDED,
274 Source<NavigationController>(controller),
275 Details<LoginNotificationDetails>(&details));
276 }
277
NotifyAuthCancelled()278 void LoginHandler::NotifyAuthCancelled() {
279 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
280 DCHECK(WasAuthHandled());
281
282 NotificationService* service = NotificationService::current();
283 NavigationController* controller = NULL;
284
285 TabContents* requesting_contents = GetTabContentsForLogin();
286 if (requesting_contents)
287 controller = &requesting_contents->controller();
288
289 LoginNotificationDetails details(this);
290
291 service->Notify(NotificationType::AUTH_CANCELLED,
292 Source<NavigationController>(controller),
293 Details<LoginNotificationDetails>(&details));
294 }
295
NotifyAuthSupplied(const string16 & username,const string16 & password)296 void LoginHandler::NotifyAuthSupplied(const string16& username,
297 const string16& password) {
298 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
299 DCHECK(WasAuthHandled());
300
301 TabContents* requesting_contents = GetTabContentsForLogin();
302 if (!requesting_contents)
303 return;
304
305 NotificationService* service = NotificationService::current();
306 NavigationController* controller = &requesting_contents->controller();
307 AuthSuppliedLoginNotificationDetails details(this, username, password);
308
309 service->Notify(NotificationType::AUTH_SUPPLIED,
310 Source<NavigationController>(controller),
311 Details<AuthSuppliedLoginNotificationDetails>(&details));
312 }
313
ReleaseSoon()314 void LoginHandler::ReleaseSoon() {
315 if (!TestAndSetAuthHandled()) {
316 BrowserThread::PostTask(
317 BrowserThread::IO, FROM_HERE,
318 NewRunnableMethod(this, &LoginHandler::CancelAuthDeferred));
319 BrowserThread::PostTask(
320 BrowserThread::UI, FROM_HERE,
321 NewRunnableMethod(this, &LoginHandler::NotifyAuthCancelled));
322 }
323
324 BrowserThread::PostTask(
325 BrowserThread::UI, FROM_HERE,
326 NewRunnableMethod(this, &LoginHandler::RemoveObservers));
327
328 // Delete this object once all InvokeLaters have been called.
329 BrowserThread::ReleaseSoon(BrowserThread::IO, FROM_HERE, this);
330 }
331
332 // Returns whether authentication had been handled (SetAuth or CancelAuth).
WasAuthHandled() const333 bool LoginHandler::WasAuthHandled() const {
334 base::AutoLock lock(handled_auth_lock_);
335 bool was_handled = handled_auth_;
336 return was_handled;
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 string16 & username,const string16 & password)348 void LoginHandler::SetAuthDeferred(const string16& username,
349 const string16& password) {
350 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
351
352 if (request_) {
353 request_->SetAuth(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 // The hosting ConstrainedWindow may have been freed.
375 if (dialog_)
376 dialog_->CloseConstrainedWindow();
377 }
378
379 // ----------------------------------------------------------------------------
380 // LoginDialogTask
381
382 // This task is run on the UI thread and creates a constrained window with
383 // a LoginView to prompt the user. The response will be sent to LoginHandler,
384 // which then routes it to the net::URLRequest on the I/O thread.
385 class LoginDialogTask : public Task {
386 public:
LoginDialogTask(const GURL & request_url,net::AuthChallengeInfo * auth_info,LoginHandler * handler)387 LoginDialogTask(const GURL& request_url,
388 net::AuthChallengeInfo* auth_info,
389 LoginHandler* handler)
390 : request_url_(request_url), auth_info_(auth_info), handler_(handler) {
391 }
~LoginDialogTask()392 virtual ~LoginDialogTask() {
393 }
394
Run()395 void Run() {
396 TabContents* parent_contents = handler_->GetTabContentsForLogin();
397 if (!parent_contents || handler_->WasAuthHandled()) {
398 // The request may have been cancelled, or it may be for a renderer
399 // not hosted by a tab (e.g. an extension). Cancel just in case
400 // (cancelling twice is a no-op).
401 handler_->CancelAuth();
402 return;
403 }
404
405 // Tell the password manager to look for saved passwords.
406 TabContentsWrapper* wrapper =
407 TabContentsWrapper::GetCurrentWrapperForContents(parent_contents);
408 if (!wrapper)
409 return;
410 PasswordManager* password_manager = wrapper->password_manager();
411 std::vector<PasswordForm> v;
412 MakeInputForPasswordManager(&v);
413 password_manager->OnPasswordFormsFound(v);
414 handler_->SetPasswordManager(password_manager);
415
416 string16 host_and_port_hack16 = WideToUTF16Hack(auth_info_->host_and_port);
417 string16 realm_hack16 = WideToUTF16Hack(auth_info_->realm);
418 string16 explanation = realm_hack16.empty() ?
419 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION_NO_REALM,
420 host_and_port_hack16) :
421 l10n_util::GetStringFUTF16(IDS_LOGIN_DIALOG_DESCRIPTION,
422 host_and_port_hack16,
423 realm_hack16);
424 handler_->BuildViewForPasswordManager(password_manager, explanation);
425 }
426
427 private:
428 // Helper to create a PasswordForm and stuff it into a vector as input
429 // for PasswordManager::PasswordFormsFound, the hook into PasswordManager.
MakeInputForPasswordManager(std::vector<PasswordForm> * password_manager_input)430 void MakeInputForPasswordManager(
431 std::vector<PasswordForm>* password_manager_input) {
432 PasswordForm dialog_form;
433 if (LowerCaseEqualsASCII(auth_info_->scheme, "basic")) {
434 dialog_form.scheme = PasswordForm::SCHEME_BASIC;
435 } else if (LowerCaseEqualsASCII(auth_info_->scheme, "digest")) {
436 dialog_form.scheme = PasswordForm::SCHEME_DIGEST;
437 } else {
438 dialog_form.scheme = PasswordForm::SCHEME_OTHER;
439 }
440 std::string host_and_port(WideToASCII(auth_info_->host_and_port));
441 if (auth_info_->is_proxy) {
442 std::string origin = host_and_port;
443 // We don't expect this to already start with http:// or https://.
444 DCHECK(origin.find("http://") != 0 && origin.find("https://") != 0);
445 origin = std::string("http://") + origin;
446 dialog_form.origin = GURL(origin);
447 } else if (net::GetHostAndPort(request_url_) != host_and_port) {
448 dialog_form.origin = GURL();
449 NOTREACHED(); // crbug.com/32718
450 } else {
451 dialog_form.origin = GURL(request_url_.scheme() + "://" + host_and_port);
452 }
453 dialog_form.signon_realm = GetSignonRealm(dialog_form.origin, *auth_info_);
454 password_manager_input->push_back(dialog_form);
455 // Set the password form for the handler (by copy).
456 handler_->SetPasswordForm(dialog_form);
457 }
458
459 // The url from the net::URLRequest initiating the auth challenge.
460 GURL request_url_;
461
462 // Info about who/where/what is asking for authentication.
463 scoped_refptr<net::AuthChallengeInfo> auth_info_;
464
465 // Where to send the authentication when obtained.
466 // This is owned by the ResourceDispatcherHost that invoked us.
467 LoginHandler* handler_;
468
469 DISALLOW_COPY_AND_ASSIGN(LoginDialogTask);
470 };
471
472 // ----------------------------------------------------------------------------
473 // Public API
474
CreateLoginPrompt(net::AuthChallengeInfo * auth_info,net::URLRequest * request)475 LoginHandler* CreateLoginPrompt(net::AuthChallengeInfo* auth_info,
476 net::URLRequest* request) {
477 LoginHandler* handler = LoginHandler::Create(auth_info, request);
478 BrowserThread::PostTask(
479 BrowserThread::UI, FROM_HERE, new LoginDialogTask(
480 request->url(), auth_info, handler));
481 return handler;
482 }
483