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