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