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