OLD | NEW |
| (Empty) |
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/sync/one_click_signin_helper.h" | |
6 | |
7 #include <algorithm> | |
8 #include <functional> | |
9 #include <utility> | |
10 #include <vector> | |
11 | |
12 #include "base/bind.h" | |
13 #include "base/callback_forward.h" | |
14 #include "base/callback_helpers.h" | |
15 #include "base/compiler_specific.h" | |
16 #include "base/memory/scoped_ptr.h" | |
17 #include "base/message_loop/message_loop_proxy.h" | |
18 #include "base/metrics/field_trial.h" | |
19 #include "base/metrics/histogram.h" | |
20 #include "base/prefs/pref_service.h" | |
21 #include "base/prefs/scoped_user_pref_update.h" | |
22 #include "base/strings/string_split.h" | |
23 #include "base/strings/string_util.h" | |
24 #include "base/strings/utf_string_conversions.h" | |
25 #include "base/supports_user_data.h" | |
26 #include "base/values.h" | |
27 #include "chrome/browser/browser_process.h" | |
28 #include "chrome/browser/chrome_notification_types.h" | |
29 #include "chrome/browser/defaults.h" | |
30 #include "chrome/browser/history/history_service.h" | |
31 #include "chrome/browser/history/history_service_factory.h" | |
32 #include "chrome/browser/password_manager/chrome_password_manager_client.h" | |
33 #include "chrome/browser/profiles/profile.h" | |
34 #include "chrome/browser/profiles/profile_info_cache.h" | |
35 #include "chrome/browser/profiles/profile_io_data.h" | |
36 #include "chrome/browser/profiles/profile_manager.h" | |
37 #include "chrome/browser/search/search.h" | |
38 #include "chrome/browser/signin/chrome_signin_client.h" | |
39 #include "chrome/browser/signin/chrome_signin_client_factory.h" | |
40 #include "chrome/browser/signin/signin_error_controller_factory.h" | |
41 #include "chrome/browser/signin/signin_manager_factory.h" | |
42 #include "chrome/browser/signin/signin_names_io_thread.h" | |
43 #include "chrome/browser/sync/profile_sync_service.h" | |
44 #include "chrome/browser/sync/profile_sync_service_factory.h" | |
45 #include "chrome/browser/tab_contents/tab_util.h" | |
46 #include "chrome/browser/ui/browser_finder.h" | |
47 #include "chrome/browser/ui/browser_list.h" | |
48 #include "chrome/browser/ui/browser_tabstrip.h" | |
49 #include "chrome/browser/ui/browser_window.h" | |
50 #include "chrome/browser/ui/chrome_pages.h" | |
51 #include "chrome/browser/ui/sync/one_click_signin_sync_observer.h" | |
52 #include "chrome/browser/ui/sync/one_click_signin_sync_starter.h" | |
53 #include "chrome/browser/ui/tab_modal_confirm_dialog.h" | |
54 #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h" | |
55 #include "chrome/browser/ui/tabs/tab_strip_model.h" | |
56 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" | |
57 #include "chrome/common/chrome_version_info.h" | |
58 #include "chrome/common/net/url_util.h" | |
59 #include "chrome/common/pref_names.h" | |
60 #include "chrome/common/url_constants.h" | |
61 #include "chrome/grit/chromium_strings.h" | |
62 #include "chrome/grit/generated_resources.h" | |
63 #include "components/autofill/core/common/password_form.h" | |
64 #include "components/google/core/browser/google_util.h" | |
65 #include "components/password_manager/core/browser/password_manager.h" | |
66 #include "components/signin/core/browser/signin_client.h" | |
67 #include "components/signin/core/browser/signin_error_controller.h" | |
68 #include "components/signin/core/browser/signin_manager.h" | |
69 #include "components/signin/core/browser/signin_manager_cookie_helper.h" | |
70 #include "components/signin/core/browser/signin_metrics.h" | |
71 #include "components/signin/core/common/profile_management_switches.h" | |
72 #include "components/sync_driver/sync_prefs.h" | |
73 #include "content/public/browser/browser_thread.h" | |
74 #include "content/public/browser/navigation_entry.h" | |
75 #include "content/public/browser/page_navigator.h" | |
76 #include "content/public/browser/render_frame_host.h" | |
77 #include "content/public/browser/render_process_host.h" | |
78 #include "content/public/browser/web_contents.h" | |
79 #include "content/public/browser/web_contents_delegate.h" | |
80 #include "content/public/common/frame_navigate_params.h" | |
81 #include "google_apis/gaia/gaia_auth_util.h" | |
82 #include "google_apis/gaia/gaia_urls.h" | |
83 #include "grit/components_strings.h" | |
84 #include "ipc/ipc_message_macros.h" | |
85 #include "net/base/url_util.h" | |
86 #include "net/cookies/cookie_monster.h" | |
87 #include "net/url_request/url_request.h" | |
88 #include "ui/base/l10n/l10n_util.h" | |
89 #include "ui/base/page_transition_types.h" | |
90 #include "url/gurl.h" | |
91 | |
92 | |
93 namespace { | |
94 | |
95 // ConfirmEmailDialogDelegate ------------------------------------------------- | |
96 | |
97 class ConfirmEmailDialogDelegate : public TabModalConfirmDialogDelegate { | |
98 public: | |
99 enum Action { | |
100 CREATE_NEW_USER, | |
101 START_SYNC, | |
102 CLOSE | |
103 }; | |
104 | |
105 // Callback indicating action performed by the user. | |
106 typedef base::Callback<void(Action)> Callback; | |
107 | |
108 // Ask the user for confirmation before starting to sync. | |
109 static void AskForConfirmation(content::WebContents* contents, | |
110 const std::string& last_email, | |
111 const std::string& email, | |
112 Callback callback); | |
113 | |
114 private: | |
115 ConfirmEmailDialogDelegate(content::WebContents* contents, | |
116 const std::string& last_email, | |
117 const std::string& email, | |
118 Callback callback); | |
119 ~ConfirmEmailDialogDelegate() override; | |
120 | |
121 // TabModalConfirmDialogDelegate: | |
122 base::string16 GetTitle() override; | |
123 base::string16 GetDialogMessage() override; | |
124 base::string16 GetAcceptButtonTitle() override; | |
125 base::string16 GetCancelButtonTitle() override; | |
126 base::string16 GetLinkText() const override; | |
127 void OnAccepted() override; | |
128 void OnCanceled() override; | |
129 void OnClosed() override; | |
130 void OnLinkClicked(WindowOpenDisposition disposition) override; | |
131 | |
132 std::string last_email_; | |
133 std::string email_; | |
134 Callback callback_; | |
135 | |
136 // Web contents from which the "Learn more" link should be opened. | |
137 content::WebContents* web_contents_; | |
138 | |
139 DISALLOW_COPY_AND_ASSIGN(ConfirmEmailDialogDelegate); | |
140 }; | |
141 | |
142 // static | |
143 void ConfirmEmailDialogDelegate::AskForConfirmation( | |
144 content::WebContents* contents, | |
145 const std::string& last_email, | |
146 const std::string& email, | |
147 Callback callback) { | |
148 TabModalConfirmDialog::Create( | |
149 new ConfirmEmailDialogDelegate(contents, last_email, email, | |
150 callback), contents); | |
151 } | |
152 | |
153 ConfirmEmailDialogDelegate::ConfirmEmailDialogDelegate( | |
154 content::WebContents* contents, | |
155 const std::string& last_email, | |
156 const std::string& email, | |
157 Callback callback) | |
158 : TabModalConfirmDialogDelegate(contents), | |
159 last_email_(last_email), | |
160 email_(email), | |
161 callback_(callback), | |
162 web_contents_(contents) { | |
163 } | |
164 | |
165 ConfirmEmailDialogDelegate::~ConfirmEmailDialogDelegate() { | |
166 } | |
167 | |
168 base::string16 ConfirmEmailDialogDelegate::GetTitle() { | |
169 return l10n_util::GetStringUTF16( | |
170 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_TITLE); | |
171 } | |
172 | |
173 base::string16 ConfirmEmailDialogDelegate::GetDialogMessage() { | |
174 return l10n_util::GetStringFUTF16( | |
175 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_MESSAGE, | |
176 base::UTF8ToUTF16(last_email_), base::UTF8ToUTF16(email_)); | |
177 } | |
178 | |
179 base::string16 ConfirmEmailDialogDelegate::GetAcceptButtonTitle() { | |
180 return l10n_util::GetStringUTF16( | |
181 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_OK_BUTTON); | |
182 } | |
183 | |
184 base::string16 ConfirmEmailDialogDelegate::GetCancelButtonTitle() { | |
185 return l10n_util::GetStringUTF16( | |
186 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_CANCEL_BUTTON); | |
187 } | |
188 | |
189 base::string16 ConfirmEmailDialogDelegate::GetLinkText() const { | |
190 return l10n_util::GetStringUTF16(IDS_LEARN_MORE); | |
191 } | |
192 | |
193 void ConfirmEmailDialogDelegate::OnAccepted() { | |
194 base::ResetAndReturn(&callback_).Run(CREATE_NEW_USER); | |
195 } | |
196 | |
197 void ConfirmEmailDialogDelegate::OnCanceled() { | |
198 base::ResetAndReturn(&callback_).Run(START_SYNC); | |
199 } | |
200 | |
201 void ConfirmEmailDialogDelegate::OnClosed() { | |
202 base::ResetAndReturn(&callback_).Run(CLOSE); | |
203 } | |
204 | |
205 void ConfirmEmailDialogDelegate::OnLinkClicked( | |
206 WindowOpenDisposition disposition) { | |
207 content::OpenURLParams params( | |
208 GURL(chrome::kChromeSyncMergeTroubleshootingURL), | |
209 content::Referrer(), | |
210 NEW_POPUP, | |
211 ui::PAGE_TRANSITION_AUTO_TOPLEVEL, | |
212 false); | |
213 // It is guaranteed that |web_contents_| is valid here because when it's | |
214 // deleted, the dialog is immediately closed and no further action can be | |
215 // performed. | |
216 web_contents_->OpenURL(params); | |
217 } | |
218 | |
219 | |
220 // Helpers -------------------------------------------------------------------- | |
221 | |
222 // Add a specific email to the list of emails rejected for one-click | |
223 // sign-in, for this profile. | |
224 void AddEmailToOneClickRejectedList(Profile* profile, | |
225 const std::string& email) { | |
226 ListPrefUpdate updater(profile->GetPrefs(), | |
227 prefs::kReverseAutologinRejectedEmailList); | |
228 updater->AppendIfNotPresent(new base::StringValue(email)); | |
229 } | |
230 | |
231 // Start syncing with the given user information. | |
232 void StartSync(const OneClickSigninHelper::StartSyncArgs& args, | |
233 OneClickSigninSyncStarter::StartSyncMode start_mode) { | |
234 if (start_mode == OneClickSigninSyncStarter::UNDO_SYNC) { | |
235 OneClickSigninHelper::LogHistogramValue(signin_metrics::HISTOGRAM_UNDO); | |
236 return; | |
237 } | |
238 | |
239 // The wrapper deletes itself once it's done. | |
240 OneClickSigninHelper::SyncStarterWrapper* wrapper = | |
241 new OneClickSigninHelper::SyncStarterWrapper(args, start_mode); | |
242 wrapper->Start(); | |
243 | |
244 int action = signin_metrics::HISTOGRAM_MAX; | |
245 switch (args.auto_accept) { | |
246 case OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT: | |
247 break; | |
248 case OneClickSigninHelper::AUTO_ACCEPT_ACCEPTED: | |
249 action = | |
250 start_mode == OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS ? | |
251 signin_metrics::HISTOGRAM_AUTO_WITH_DEFAULTS : | |
252 signin_metrics::HISTOGRAM_AUTO_WITH_ADVANCED; | |
253 break; | |
254 case OneClickSigninHelper::AUTO_ACCEPT_CONFIGURE: | |
255 DCHECK(start_mode == OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST); | |
256 action = signin_metrics::HISTOGRAM_AUTO_WITH_ADVANCED; | |
257 break; | |
258 default: | |
259 NOTREACHED() << "Invalid auto_accept: " << args.auto_accept; | |
260 break; | |
261 } | |
262 if (action != signin_metrics::HISTOGRAM_MAX) | |
263 OneClickSigninHelper::LogHistogramValue(action); | |
264 } | |
265 | |
266 void StartExplicitSync(const OneClickSigninHelper::StartSyncArgs& args, | |
267 content::WebContents* contents, | |
268 OneClickSigninSyncStarter::StartSyncMode start_mode, | |
269 ConfirmEmailDialogDelegate::Action action) { | |
270 if (action == ConfirmEmailDialogDelegate::START_SYNC) { | |
271 StartSync(args, start_mode); | |
272 } else { | |
273 // Perform a redirection to the NTP/Apps page to hide the blank page when | |
274 // the action is CLOSE or CREATE_NEW_USER. The redirection is useful when | |
275 // the action is CREATE_NEW_USER because the "Create new user" page might | |
276 // be opened in a different tab that is already showing settings. | |
277 args.callback.Run(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE); | |
278 if (action == ConfirmEmailDialogDelegate::CREATE_NEW_USER) { | |
279 chrome::ShowSettingsSubPage(args.browser, | |
280 std::string(chrome::kCreateProfileSubPage)); | |
281 } | |
282 } | |
283 } | |
284 | |
285 void ClearPendingEmailOnIOThread(content::ResourceContext* context) { | |
286 ProfileIOData* io_data = ProfileIOData::FromResourceContext(context); | |
287 DCHECK(io_data); | |
288 io_data->set_reverse_autologin_pending_email(std::string()); | |
289 } | |
290 | |
291 // Determines the source of the sign in and the continue URL. It's either one | |
292 // of the known sign-in access points (first run, NTP, Apps page, menu, or | |
293 // settings) or it's an implicit sign in via another Google property. In the | |
294 // former case, "service" is also checked to make sure its "chromiumsync". | |
295 signin_metrics::Source GetSigninSource(const GURL& url, GURL* continue_url) { | |
296 DCHECK(url.is_valid()); | |
297 std::string value; | |
298 net::GetValueForKeyInQuery(url, "service", &value); | |
299 bool possibly_an_explicit_signin = value == "chromiumsync"; | |
300 | |
301 // Find the final continue URL for this sign in. In some cases, Gaia can | |
302 // continue to itself, with the original continue URL buried under a couple | |
303 // of layers of indirection. Peel those layers away. The final destination | |
304 // can also be "IsGaiaSignonRealm" so stop if we get to the end (but be sure | |
305 // we always extract at least one "continue" value). | |
306 GURL local_continue_url = signin::GetNextPageURLForPromoURL(url); | |
307 while (gaia::IsGaiaSignonRealm(local_continue_url.GetOrigin())) { | |
308 GURL next_continue_url = | |
309 signin::GetNextPageURLForPromoURL(local_continue_url); | |
310 if (!next_continue_url.is_valid()) | |
311 break; | |
312 local_continue_url = next_continue_url; | |
313 } | |
314 | |
315 if (continue_url && local_continue_url.is_valid()) { | |
316 DCHECK(!continue_url->is_valid() || *continue_url == local_continue_url); | |
317 *continue_url = local_continue_url; | |
318 } | |
319 | |
320 return possibly_an_explicit_signin ? | |
321 signin::GetSourceForPromoURL(local_continue_url) : | |
322 signin_metrics::SOURCE_UNKNOWN; | |
323 } | |
324 | |
325 // Returns true if |url| is a valid URL that can occur during the sign in | |
326 // process. Valid URLs are of the form: | |
327 // | |
328 // https://accounts.google.{TLD}/... | |
329 // https://accounts.youtube.com/... | |
330 // https://accounts.blogger.com/... | |
331 // | |
332 // All special headers used by one click sign in occur on | |
333 // https://accounts.google.com URLs. However, the sign in process may redirect | |
334 // to intermediate Gaia URLs that do not end with .com. For example, an account | |
335 // that uses SMS 2-factor outside the US may redirect to country specific URLs. | |
336 // | |
337 // The sign in process may also redirect to youtube and blogger account URLs | |
338 // so that Gaia acts as a single signon service. | |
339 bool IsValidGaiaSigninRedirectOrResponseURL(const GURL& url) { | |
340 std::string hostname = url.host(); | |
341 if (google_util::IsGoogleHostname(hostname, google_util::ALLOW_SUBDOMAIN)) { | |
342 // Also using IsGaiaSignonRealm() to handle overriding with command line. | |
343 return gaia::IsGaiaSignonRealm(url.GetOrigin()) || | |
344 StartsWithASCII(hostname, "accounts.", false); | |
345 } | |
346 | |
347 GURL origin = url.GetOrigin(); | |
348 if (origin == GURL("https://accounts.youtube.com") || | |
349 origin == GURL("https://accounts.blogger.com")) | |
350 return true; | |
351 | |
352 return false; | |
353 } | |
354 | |
355 // Tells when we are in the process of showing either the signin to chrome page | |
356 // or the one click sign in to chrome page. | |
357 // NOTE: This should only be used for logging purposes since it relies on hard | |
358 // coded URLs that could change. | |
359 bool AreWeShowingSignin(GURL url, | |
360 signin_metrics::Source source, | |
361 std::string email) { | |
362 GURL::Replacements replacements; | |
363 replacements.ClearQuery(); | |
364 GURL clean_login_url = | |
365 GaiaUrls::GetInstance()->service_login_url().ReplaceComponents( | |
366 replacements); | |
367 | |
368 return (url.ReplaceComponents(replacements) == clean_login_url && | |
369 source != signin_metrics::SOURCE_UNKNOWN) || | |
370 (IsValidGaiaSigninRedirectOrResponseURL(url) && | |
371 url.spec().find("ChromeLoginPrompt") != std::string::npos && | |
372 !email.empty()); | |
373 } | |
374 | |
375 // If profile is valid then get signin scoped device id from signin client. | |
376 // Otherwise returns empty string. | |
377 std::string GetSigninScopedDeviceId(Profile* profile) { | |
378 std::string signin_scoped_device_id; | |
379 SigninClient* signin_client = | |
380 profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL; | |
381 if (signin_client) { | |
382 signin_scoped_device_id = signin_client->GetSigninScopedDeviceId(); | |
383 } | |
384 return signin_scoped_device_id; | |
385 } | |
386 | |
387 // CurrentHistoryCleaner ------------------------------------------------------ | |
388 | |
389 // Watch a webcontents and remove URL from the history once loading is complete. | |
390 // We have to delay the cleaning until the new URL has finished loading because | |
391 // we're not allowed to remove the last-loaded URL from the history. Objects | |
392 // of this type automatically self-destruct once they're finished their work. | |
393 class CurrentHistoryCleaner : public content::WebContentsObserver { | |
394 public: | |
395 explicit CurrentHistoryCleaner(content::WebContents* contents); | |
396 ~CurrentHistoryCleaner() override; | |
397 | |
398 // content::WebContentsObserver: | |
399 void WebContentsDestroyed() override; | |
400 void DidCommitProvisionalLoadForFrame( | |
401 content::RenderFrameHost* render_frame_host, | |
402 const GURL& url, | |
403 ui::PageTransition transition_type) override; | |
404 | |
405 private: | |
406 scoped_ptr<content::WebContents> contents_; | |
407 int history_index_to_remove_; | |
408 | |
409 DISALLOW_COPY_AND_ASSIGN(CurrentHistoryCleaner); | |
410 }; | |
411 | |
412 CurrentHistoryCleaner::CurrentHistoryCleaner(content::WebContents* contents) | |
413 : WebContentsObserver(contents) { | |
414 history_index_to_remove_ = | |
415 web_contents()->GetController().GetLastCommittedEntryIndex(); | |
416 } | |
417 | |
418 CurrentHistoryCleaner::~CurrentHistoryCleaner() { | |
419 } | |
420 | |
421 void CurrentHistoryCleaner::DidCommitProvisionalLoadForFrame( | |
422 content::RenderFrameHost* render_frame_host, | |
423 const GURL& url, | |
424 ui::PageTransition transition_type) { | |
425 // Return early if this is not top-level navigation. | |
426 if (render_frame_host->GetParent()) | |
427 return; | |
428 } | |
429 | |
430 void CurrentHistoryCleaner::WebContentsDestroyed() { | |
431 delete this; // Failure. | |
432 } | |
433 | |
434 } // namespace | |
435 | |
436 | |
437 // StartSyncArgs -------------------------------------------------------------- | |
438 | |
439 OneClickSigninHelper::StartSyncArgs::StartSyncArgs() | |
440 : profile(NULL), | |
441 browser(NULL), | |
442 auto_accept(AUTO_ACCEPT_NONE), | |
443 web_contents(NULL), | |
444 confirmation_required(OneClickSigninSyncStarter::NO_CONFIRMATION), | |
445 source(signin_metrics::SOURCE_UNKNOWN) {} | |
446 | |
447 OneClickSigninHelper::StartSyncArgs::StartSyncArgs( | |
448 Profile* profile, | |
449 Browser* browser, | |
450 OneClickSigninHelper::AutoAccept auto_accept, | |
451 const std::string& session_index, | |
452 const std::string& email, | |
453 const std::string& password, | |
454 const std::string& refresh_token, | |
455 content::WebContents* web_contents, | |
456 bool untrusted_confirmation_required, | |
457 signin_metrics::Source source, | |
458 OneClickSigninSyncStarter::Callback callback) | |
459 : profile(profile), | |
460 browser(browser), | |
461 auto_accept(auto_accept), | |
462 session_index(session_index), | |
463 email(email), | |
464 password(password), | |
465 refresh_token(refresh_token), | |
466 web_contents(web_contents), | |
467 source(source), | |
468 callback(callback) { | |
469 DCHECK(session_index.empty() != refresh_token.empty()); | |
470 if (untrusted_confirmation_required) { | |
471 confirmation_required = OneClickSigninSyncStarter::CONFIRM_UNTRUSTED_SIGNIN; | |
472 } else if (source == signin_metrics::SOURCE_SETTINGS) { | |
473 // Do not display a status confirmation for re-auth. | |
474 confirmation_required = OneClickSigninSyncStarter::NO_CONFIRMATION; | |
475 } else { | |
476 confirmation_required = OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN; | |
477 } | |
478 } | |
479 | |
480 OneClickSigninHelper::StartSyncArgs::~StartSyncArgs() {} | |
481 | |
482 // SyncStarterWrapper --------------------------------------------------------- | |
483 | |
484 OneClickSigninHelper::SyncStarterWrapper::SyncStarterWrapper( | |
485 const OneClickSigninHelper::StartSyncArgs& args, | |
486 OneClickSigninSyncStarter::StartSyncMode start_mode) | |
487 : args_(args), start_mode_(start_mode), weak_pointer_factory_(this) { | |
488 BrowserList::AddObserver(this); | |
489 | |
490 // Cache the parent desktop for the browser, so we can reuse that same | |
491 // desktop for any UI we want to display. | |
492 desktop_type_ = args_.browser ? args_.browser->host_desktop_type() | |
493 : chrome::GetActiveDesktop(); | |
494 } | |
495 | |
496 OneClickSigninHelper::SyncStarterWrapper::~SyncStarterWrapper() { | |
497 BrowserList::RemoveObserver(this); | |
498 } | |
499 | |
500 void OneClickSigninHelper::SyncStarterWrapper::Start() { | |
501 if (args_.refresh_token.empty()) { | |
502 if (args_.password.empty()) { | |
503 VerifyGaiaCookiesBeforeSignIn(); | |
504 } else { | |
505 StartSigninOAuthHelper(); | |
506 } | |
507 } else { | |
508 OnSigninOAuthInformationAvailable(args_.email, args_.email, | |
509 args_.refresh_token); | |
510 } | |
511 } | |
512 | |
513 void | |
514 OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationAvailable( | |
515 const std::string& email, | |
516 const std::string& display_email, | |
517 const std::string& refresh_token) { | |
518 if (!gaia::AreEmailsSame(display_email, args_.email)) { | |
519 DisplayErrorBubble( | |
520 GoogleServiceAuthError( | |
521 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString()); | |
522 } else { | |
523 StartOneClickSigninSyncStarter(email, refresh_token); | |
524 } | |
525 | |
526 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); | |
527 } | |
528 | |
529 void OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationFailure( | |
530 const GoogleServiceAuthError& error) { | |
531 DisplayErrorBubble(error.ToString()); | |
532 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); | |
533 } | |
534 | |
535 void OneClickSigninHelper::SyncStarterWrapper::OnBrowserRemoved( | |
536 Browser* browser) { | |
537 if (args_.browser == browser) | |
538 args_.browser = NULL; | |
539 } | |
540 | |
541 void OneClickSigninHelper::SyncStarterWrapper::VerifyGaiaCookiesBeforeSignIn() { | |
542 scoped_refptr<SigninManagerCookieHelper> cookie_helper( | |
543 new SigninManagerCookieHelper( | |
544 args_.profile->GetRequestContext(), | |
545 content::BrowserThread::GetMessageLoopProxyForThread( | |
546 content::BrowserThread::UI), | |
547 content::BrowserThread::GetMessageLoopProxyForThread( | |
548 content::BrowserThread::IO))); | |
549 cookie_helper->StartFetchingGaiaCookiesOnUIThread( | |
550 base::Bind(&SyncStarterWrapper::OnGaiaCookiesFetched, | |
551 weak_pointer_factory_.GetWeakPtr(), | |
552 args_.session_index)); | |
553 } | |
554 | |
555 void OneClickSigninHelper::SyncStarterWrapper::OnGaiaCookiesFetched( | |
556 const std::string session_index, const net::CookieList& cookie_list) { | |
557 net::CookieList::const_iterator it; | |
558 bool success = false; | |
559 for (it = cookie_list.begin(); it != cookie_list.end(); ++it) { | |
560 // Make sure the LSID cookie is set on the GAIA host, instead of a super- | |
561 // domain. | |
562 if (it->Name() == "LSID") { | |
563 if (it->IsHostCookie() && it->IsHttpOnly() && it->IsSecure()) { | |
564 // Found a valid LSID cookie. Continue loop to make sure we don't have | |
565 // invalid LSID cookies on any super-domain. | |
566 success = true; | |
567 } else { | |
568 success = false; | |
569 break; | |
570 } | |
571 } | |
572 } | |
573 | |
574 if (success) { | |
575 StartSigninOAuthHelper(); | |
576 } else { | |
577 DisplayErrorBubble( | |
578 GoogleServiceAuthError( | |
579 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString()); | |
580 base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); | |
581 } | |
582 } | |
583 | |
584 void OneClickSigninHelper::SyncStarterWrapper::DisplayErrorBubble( | |
585 const std::string& error_message) { | |
586 args_.browser = OneClickSigninSyncStarter::EnsureBrowser( | |
587 args_.browser, args_.profile, desktop_type_); | |
588 LoginUIServiceFactory::GetForProfile(args_.profile)->DisplayLoginResult( | |
589 args_.browser, base::UTF8ToUTF16(error_message)); | |
590 } | |
591 | |
592 void OneClickSigninHelper::SyncStarterWrapper::StartSigninOAuthHelper() { | |
593 std::string signin_scoped_device_id = GetSigninScopedDeviceId(args_.profile); | |
594 signin_oauth_helper_.reset( | |
595 new SigninOAuthHelper(args_.profile->GetRequestContext(), | |
596 args_.session_index, | |
597 signin_scoped_device_id, | |
598 this)); | |
599 } | |
600 | |
601 void | |
602 OneClickSigninHelper::SyncStarterWrapper::StartOneClickSigninSyncStarter( | |
603 const std::string& email, | |
604 const std::string& refresh_token) { | |
605 // The starter deletes itself once it's done. | |
606 new OneClickSigninSyncStarter(args_.profile, args_.browser, | |
607 email, args_.password, | |
608 refresh_token, start_mode_, | |
609 args_.web_contents, | |
610 args_.confirmation_required, | |
611 GURL(), | |
612 args_.callback); | |
613 } | |
614 | |
615 | |
616 // OneClickSigninHelper ------------------------------------------------------- | |
617 | |
618 DEFINE_WEB_CONTENTS_USER_DATA_KEY(OneClickSigninHelper); | |
619 | |
620 // static | |
621 const int OneClickSigninHelper::kMaxNavigationsSince = 10; | |
622 | |
623 OneClickSigninHelper::OneClickSigninHelper(content::WebContents* web_contents) | |
624 : content::WebContentsObserver(web_contents), | |
625 showing_signin_(false), | |
626 auto_accept_(AUTO_ACCEPT_NONE), | |
627 source_(signin_metrics::SOURCE_UNKNOWN), | |
628 switched_to_advanced_(false), | |
629 untrusted_navigations_since_signin_visit_(0), | |
630 untrusted_confirmation_required_(false), | |
631 do_not_clear_pending_email_(false), | |
632 do_not_start_sync_for_testing_(false), | |
633 weak_pointer_factory_(this) { | |
634 ChromePasswordManagerClient* client = | |
635 ChromePasswordManagerClient::FromWebContents(web_contents); | |
636 // May be NULL during testing. | |
637 if (client) { | |
638 client->GetPasswordManager()->AddSubmissionCallback( | |
639 base::Bind(&OneClickSigninHelper::PasswordSubmitted, | |
640 weak_pointer_factory_.GetWeakPtr())); | |
641 } | |
642 } | |
643 | |
644 OneClickSigninHelper::~OneClickSigninHelper() {} | |
645 | |
646 // static | |
647 void OneClickSigninHelper::LogHistogramValue(int action) { | |
648 UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action, | |
649 signin_metrics::HISTOGRAM_MAX); | |
650 } | |
651 | |
652 // static | |
653 bool OneClickSigninHelper::CanOffer(content::WebContents* web_contents, | |
654 CanOfferFor can_offer_for, | |
655 const std::string& email, | |
656 std::string* error_message) { | |
657 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
658 VLOG(1) << "OneClickSigninHelper::CanOffer"; | |
659 | |
660 if (error_message) | |
661 error_message->clear(); | |
662 | |
663 if (!web_contents) | |
664 return false; | |
665 | |
666 if (web_contents->GetBrowserContext()->IsOffTheRecord()) | |
667 return false; | |
668 | |
669 Profile* profile = | |
670 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | |
671 if (!profile) | |
672 return false; | |
673 | |
674 SigninManager* manager = | |
675 SigninManagerFactory::GetForProfile(profile); | |
676 if (manager && !manager->IsSigninAllowed()) | |
677 return false; | |
678 | |
679 if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY && | |
680 !profile->GetPrefs()->GetBoolean(prefs::kReverseAutologinEnabled)) | |
681 return false; | |
682 | |
683 if (!ChromeSigninClient::ProfileAllowsSigninCookies(profile)) | |
684 return false; | |
685 | |
686 if (!email.empty()) { | |
687 if (!manager) | |
688 return false; | |
689 | |
690 // Make sure this username is not prohibited by policy. | |
691 if (!manager->IsAllowedUsername(email)) { | |
692 if (error_message) { | |
693 error_message->assign( | |
694 l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED)); | |
695 } | |
696 return false; | |
697 } | |
698 | |
699 if (can_offer_for != CAN_OFFER_FOR_SECONDARY_ACCOUNT) { | |
700 // If the signin manager already has an authenticated name, then this is a | |
701 // re-auth scenario. Make sure the email just signed in corresponds to | |
702 // the one sign in manager expects. | |
703 std::string current_email = manager->GetAuthenticatedUsername(); | |
704 const bool same_email = gaia::AreEmailsSame(current_email, email); | |
705 if (!current_email.empty() && !same_email) { | |
706 UMA_HISTOGRAM_ENUMERATION("Signin.Reauth", | |
707 signin_metrics::HISTOGRAM_ACCOUNT_MISSMATCH, | |
708 signin_metrics::HISTOGRAM_MAX); | |
709 if (error_message) { | |
710 error_message->assign( | |
711 l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL, | |
712 base::UTF8ToUTF16(current_email))); | |
713 } | |
714 return false; | |
715 } | |
716 | |
717 // If some profile, not just the current one, is already connected to this | |
718 // account, don't show the infobar. | |
719 if (g_browser_process && !same_email) { | |
720 ProfileManager* manager = g_browser_process->profile_manager(); | |
721 if (manager) { | |
722 ProfileInfoCache& cache = manager->GetProfileInfoCache(); | |
723 for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) { | |
724 std::string current_email = | |
725 base::UTF16ToUTF8(cache.GetUserNameOfProfileAtIndex(i)); | |
726 if (gaia::AreEmailsSame(email, current_email)) { | |
727 if (error_message) { | |
728 error_message->assign( | |
729 l10n_util::GetStringUTF8(IDS_SYNC_USER_NAME_IN_USE_ERROR)); | |
730 } | |
731 return false; | |
732 } | |
733 } | |
734 } | |
735 } | |
736 } | |
737 | |
738 // If email was already rejected by this profile for one-click sign-in. | |
739 if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY) { | |
740 const base::ListValue* rejected_emails = profile->GetPrefs()->GetList( | |
741 prefs::kReverseAutologinRejectedEmailList); | |
742 if (!rejected_emails->empty()) { | |
743 base::ListValue::const_iterator iter = rejected_emails->Find( | |
744 base::StringValue(email)); | |
745 if (iter != rejected_emails->end()) | |
746 return false; | |
747 } | |
748 } | |
749 } | |
750 | |
751 VLOG(1) << "OneClickSigninHelper::CanOffer: yes we can"; | |
752 return true; | |
753 } | |
754 | |
755 // static | |
756 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThread( | |
757 net::URLRequest* request, | |
758 ProfileIOData* io_data) { | |
759 return CanOfferOnIOThreadImpl(request->url(), request, io_data); | |
760 } | |
761 | |
762 // static | |
763 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThreadImpl( | |
764 const GURL& url, | |
765 base::SupportsUserData* request, | |
766 ProfileIOData* io_data) { | |
767 if (!gaia::IsGaiaSignonRealm(url.GetOrigin())) | |
768 return IGNORE_REQUEST; | |
769 | |
770 if (!io_data) | |
771 return DONT_OFFER; | |
772 | |
773 // Check for incognito before other parts of the io_data, since those | |
774 // members may not be initalized. | |
775 if (io_data->IsOffTheRecord()) | |
776 return DONT_OFFER; | |
777 | |
778 if (!io_data->signin_allowed()->GetValue()) | |
779 return DONT_OFFER; | |
780 | |
781 if (!io_data->reverse_autologin_enabled()->GetValue()) | |
782 return DONT_OFFER; | |
783 | |
784 if (!io_data->google_services_username()->GetValue().empty()) | |
785 return DONT_OFFER; | |
786 | |
787 if (!ChromeSigninClient::SettingsAllowSigninCookies( | |
788 io_data->GetCookieSettings())) | |
789 return DONT_OFFER; | |
790 | |
791 // The checks below depend on chrome already knowing what account the user | |
792 // signed in with. This happens only after receiving the response containing | |
793 // the Google-Accounts-SignIn header. Until then, if there is even a chance | |
794 // that we want to connect the profile, chrome needs to tell Gaia that | |
795 // it should offer the interstitial. Therefore missing one click data on | |
796 // the request means can offer is true. | |
797 const std::string& pending_email = io_data->reverse_autologin_pending_email(); | |
798 if (!pending_email.empty()) { | |
799 if (!SigninManager::IsUsernameAllowedByPolicy(pending_email, | |
800 io_data->google_services_username_pattern()->GetValue())) { | |
801 return DONT_OFFER; | |
802 } | |
803 | |
804 std::vector<std::string> rejected_emails = | |
805 io_data->one_click_signin_rejected_email_list()->GetValue(); | |
806 if (std::count_if(rejected_emails.begin(), rejected_emails.end(), | |
807 std::bind2nd(std::equal_to<std::string>(), | |
808 pending_email)) > 0) { | |
809 return DONT_OFFER; | |
810 } | |
811 | |
812 if (io_data->signin_names()->GetEmails().count( | |
813 base::UTF8ToUTF16(pending_email)) > 0) { | |
814 return DONT_OFFER; | |
815 } | |
816 } | |
817 | |
818 return CAN_OFFER; | |
819 } | |
820 | |
821 // static | |
822 void OneClickSigninHelper::ShowInfoBarIfPossible(net::URLRequest* request, | |
823 ProfileIOData* io_data, | |
824 int child_id, | |
825 int route_id) { | |
826 std::string google_chrome_signin_value; | |
827 std::string google_accounts_signin_value; | |
828 request->GetResponseHeaderByName("Google-Chrome-SignIn", | |
829 &google_chrome_signin_value); | |
830 request->GetResponseHeaderByName("Google-Accounts-SignIn", | |
831 &google_accounts_signin_value); | |
832 | |
833 if (!google_accounts_signin_value.empty() || | |
834 !google_chrome_signin_value.empty()) { | |
835 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:" | |
836 << " g-a-s='" << google_accounts_signin_value << "'" | |
837 << " g-c-s='" << google_chrome_signin_value << "'"; | |
838 } | |
839 | |
840 if (!gaia::IsGaiaSignonRealm(request->url().GetOrigin())) | |
841 return; | |
842 | |
843 // Parse Google-Accounts-SignIn. | |
844 base::StringPairs pairs; | |
845 base::SplitStringIntoKeyValuePairs(google_accounts_signin_value, '=', ',', | |
846 &pairs); | |
847 std::string session_index; | |
848 std::string email; | |
849 for (size_t i = 0; i < pairs.size(); ++i) { | |
850 const std::pair<std::string, std::string>& pair = pairs[i]; | |
851 const std::string& key = pair.first; | |
852 const std::string& value = pair.second; | |
853 if (key == "email") { | |
854 base::TrimString(value, "\"", &email); | |
855 } else if (key == "sessionindex") { | |
856 session_index = value; | |
857 } | |
858 } | |
859 | |
860 // Later in the chain of this request, we'll need to check the email address | |
861 // in the IO thread (see CanOfferOnIOThread). So save the email address as | |
862 // user data on the request (only for web-based flow). | |
863 if (!email.empty()) | |
864 io_data->set_reverse_autologin_pending_email(email); | |
865 | |
866 if (!email.empty() || !session_index.empty()) { | |
867 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:" | |
868 << " email=" << email | |
869 << " sessionindex=" << session_index; | |
870 } | |
871 | |
872 // Parse Google-Chrome-SignIn. | |
873 AutoAccept auto_accept = AUTO_ACCEPT_NONE; | |
874 signin_metrics::Source source = signin_metrics::SOURCE_UNKNOWN; | |
875 GURL continue_url; | |
876 std::vector<std::string> tokens; | |
877 base::SplitString(google_chrome_signin_value, ',', &tokens); | |
878 for (size_t i = 0; i < tokens.size(); ++i) { | |
879 const std::string& token = tokens[i]; | |
880 if (token == "accepted") { | |
881 auto_accept = AUTO_ACCEPT_ACCEPTED; | |
882 } else if (token == "configure") { | |
883 auto_accept = AUTO_ACCEPT_CONFIGURE; | |
884 } else if (token == "rejected-for-profile") { | |
885 auto_accept = AUTO_ACCEPT_REJECTED_FOR_PROFILE; | |
886 } | |
887 } | |
888 | |
889 // If this is an explicit sign in (i.e., first run, NTP, Apps page, menu, | |
890 // settings) then force the auto accept type to explicit. | |
891 source = GetSigninSource(request->url(), &continue_url); | |
892 if (source != signin_metrics::SOURCE_UNKNOWN) | |
893 auto_accept = AUTO_ACCEPT_EXPLICIT; | |
894 | |
895 if (auto_accept != AUTO_ACCEPT_NONE) { | |
896 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:" | |
897 << " auto_accept=" << auto_accept; | |
898 } | |
899 | |
900 // If |session_index|, |email|, |auto_accept|, and |continue_url| all have | |
901 // their default value, don't bother posting a task to the UI thread. | |
902 // It will be a noop anyway. | |
903 // | |
904 // The two headers above may (but not always) come in different http requests | |
905 // so a post to the UI thread is still needed if |auto_accept| is not its | |
906 // default value, but |email| and |session_index| are. | |
907 if (session_index.empty() && email.empty() && | |
908 auto_accept == AUTO_ACCEPT_NONE && !continue_url.is_valid()) { | |
909 return; | |
910 } | |
911 | |
912 content::BrowserThread::PostTask( | |
913 content::BrowserThread::UI, FROM_HERE, | |
914 base::Bind(&OneClickSigninHelper::ShowInfoBarUIThread, session_index, | |
915 email, auto_accept, source, continue_url, child_id, route_id)); | |
916 } | |
917 | |
918 // static | |
919 void OneClickSigninHelper::LogConfirmHistogramValue(int action) { | |
920 UMA_HISTOGRAM_ENUMERATION("Signin.OneClickConfirmation", action, | |
921 signin_metrics::HISTOGRAM_CONFIRM_MAX); | |
922 } | |
923 // static | |
924 void OneClickSigninHelper::ShowInfoBarUIThread( | |
925 const std::string& session_index, | |
926 const std::string& email, | |
927 AutoAccept auto_accept, | |
928 signin_metrics::Source source, | |
929 const GURL& continue_url, | |
930 int child_id, | |
931 int route_id) { | |
932 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
933 | |
934 content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id, | |
935 route_id); | |
936 if (!web_contents) | |
937 return; | |
938 | |
939 // TODO(mathp): The appearance of this infobar should be tested using a | |
940 // browser_test. | |
941 OneClickSigninHelper* helper = | |
942 OneClickSigninHelper::FromWebContents(web_contents); | |
943 if (!helper) | |
944 return; | |
945 | |
946 if (auto_accept != AUTO_ACCEPT_NONE) | |
947 helper->auto_accept_ = auto_accept; | |
948 | |
949 if (source != signin_metrics::SOURCE_UNKNOWN && | |
950 helper->source_ == signin_metrics::SOURCE_UNKNOWN) { | |
951 helper->source_ = source; | |
952 } | |
953 | |
954 // Save the email in the one-click signin manager. The manager may | |
955 // not exist if the contents is incognito or if the profile is already | |
956 // connected to a Google account. | |
957 if (!session_index.empty()) | |
958 helper->session_index_ = session_index; | |
959 | |
960 if (!email.empty()) | |
961 helper->email_ = email; | |
962 | |
963 CanOfferFor can_offer_for = | |
964 (auto_accept != AUTO_ACCEPT_EXPLICIT && | |
965 helper->auto_accept_ != AUTO_ACCEPT_EXPLICIT) ? | |
966 CAN_OFFER_FOR_INTERSTITAL_ONLY : CAN_OFFER_FOR_ALL; | |
967 | |
968 std::string error_message; | |
969 | |
970 if (!web_contents || !CanOffer(web_contents, can_offer_for, email, | |
971 &error_message)) { | |
972 VLOG(1) << "OneClickSigninHelper::ShowInfoBarUIThread: not offering"; | |
973 // TODO(rogerta): Can we just display our error now instead of keeping it | |
974 // around and doing it later? | |
975 if (helper && helper->error_message_.empty() && !error_message.empty()) | |
976 helper->error_message_ = error_message; | |
977 | |
978 return; | |
979 } | |
980 | |
981 // Only allow the dedicated signin process to sign the user into | |
982 // Chrome without intervention, because it doesn't load any untrusted | |
983 // pages. If at any point an untrusted page is detected, chrome will | |
984 // show a modal dialog asking the user to confirm. | |
985 Profile* profile = | |
986 Profile::FromBrowserContext(web_contents->GetBrowserContext()); | |
987 SigninClient* signin_client = | |
988 profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL; | |
989 helper->untrusted_confirmation_required_ |= | |
990 (signin_client && !signin_client->IsSigninProcess(child_id)); | |
991 | |
992 if (continue_url.is_valid()) { | |
993 // Set |original_continue_url_| if it is currently empty. |continue_url| | |
994 // could be modified by gaia pages, thus we need to record the original | |
995 // continue url to navigate back to the right page when sync setup is | |
996 // complete. | |
997 if (helper->original_continue_url_.is_empty()) | |
998 helper->original_continue_url_ = continue_url; | |
999 helper->continue_url_ = continue_url; | |
1000 } | |
1001 } | |
1002 | |
1003 // static | |
1004 void OneClickSigninHelper::RemoveSigninRedirectURLHistoryItem( | |
1005 content::WebContents* web_contents) { | |
1006 } | |
1007 | |
1008 // static | |
1009 bool OneClickSigninHelper::HandleCrossAccountError( | |
1010 Profile* profile, | |
1011 const std::string& session_index, | |
1012 const std::string& email, | |
1013 const std::string& password, | |
1014 const std::string& refresh_token, | |
1015 OneClickSigninHelper::AutoAccept auto_accept, | |
1016 signin_metrics::Source source, | |
1017 OneClickSigninSyncStarter::StartSyncMode start_mode, | |
1018 OneClickSigninSyncStarter::Callback sync_callback) { | |
1019 std::string last_email = | |
1020 profile->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername); | |
1021 | |
1022 if (!last_email.empty() && !gaia::AreEmailsSame(last_email, email)) { | |
1023 // If the new email address is different from the email address that | |
1024 // just signed in, show a confirmation dialog on top of the current active | |
1025 // tab. | |
1026 | |
1027 // No need to display a second confirmation so pass false below. | |
1028 // TODO(atwilson): Move this into OneClickSigninSyncStarter. | |
1029 // The tab modal dialog always executes its callback before |contents| | |
1030 // is deleted. | |
1031 Browser* browser = chrome::FindLastActiveWithProfile( | |
1032 profile, chrome::GetActiveDesktop()); | |
1033 content::WebContents* contents = | |
1034 browser->tab_strip_model()->GetActiveWebContents(); | |
1035 | |
1036 ConfirmEmailDialogDelegate::AskForConfirmation( | |
1037 contents, | |
1038 last_email, | |
1039 email, | |
1040 base::Bind( | |
1041 &StartExplicitSync, | |
1042 StartSyncArgs(profile, browser, auto_accept, | |
1043 session_index, email, password, | |
1044 refresh_token, | |
1045 contents, false /* confirmation_required */, source, | |
1046 sync_callback), | |
1047 contents, | |
1048 start_mode)); | |
1049 return true; | |
1050 } | |
1051 | |
1052 return false; | |
1053 } | |
1054 | |
1055 // static | |
1056 void OneClickSigninHelper::RedirectToNtpOrAppsPage( | |
1057 content::WebContents* contents, signin_metrics::Source source) { | |
1058 // Do nothing if a navigation is pending, since this call can be triggered | |
1059 // from DidStartLoading. This avoids deleting the pending entry while we are | |
1060 // still navigating to it. See crbug/346632. | |
1061 if (contents->GetController().GetPendingEntry()) | |
1062 return; | |
1063 | |
1064 VLOG(1) << "RedirectToNtpOrAppsPage"; | |
1065 // Redirect to NTP/Apps page and display a confirmation bubble | |
1066 GURL url(source == signin_metrics::SOURCE_APPS_PAGE_LINK ? | |
1067 chrome::kChromeUIAppsURL : chrome::kChromeUINewTabURL); | |
1068 content::OpenURLParams params(url, | |
1069 content::Referrer(), | |
1070 CURRENT_TAB, | |
1071 ui::PAGE_TRANSITION_AUTO_TOPLEVEL, | |
1072 false); | |
1073 contents->OpenURL(params); | |
1074 } | |
1075 | |
1076 // static | |
1077 void OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary( | |
1078 content::WebContents* contents, signin_metrics::Source source) { | |
1079 if (source != signin_metrics::SOURCE_SETTINGS) { | |
1080 RedirectToNtpOrAppsPage(contents, source); | |
1081 } | |
1082 } | |
1083 | |
1084 void OneClickSigninHelper::RedirectToSignin() { | |
1085 VLOG(1) << "OneClickSigninHelper::RedirectToSignin"; | |
1086 | |
1087 // Extract the existing sounce=X value. Default to "2" if missing. | |
1088 signin_metrics::Source source = signin::GetSourceForPromoURL(continue_url_); | |
1089 if (source == signin_metrics::SOURCE_UNKNOWN) | |
1090 source = signin_metrics::SOURCE_MENU; | |
1091 GURL page = signin::GetPromoURL(source, false); | |
1092 | |
1093 content::WebContents* contents = web_contents(); | |
1094 contents->GetController().LoadURL(page, | |
1095 content::Referrer(), | |
1096 ui::PAGE_TRANSITION_AUTO_TOPLEVEL, | |
1097 std::string()); | |
1098 } | |
1099 | |
1100 void OneClickSigninHelper::CleanTransientState() { | |
1101 VLOG(1) << "OneClickSigninHelper::CleanTransientState"; | |
1102 showing_signin_ = false; | |
1103 email_.clear(); | |
1104 password_.clear(); | |
1105 auto_accept_ = AUTO_ACCEPT_NONE; | |
1106 source_ = signin_metrics::SOURCE_UNKNOWN; | |
1107 switched_to_advanced_ = false; | |
1108 continue_url_ = GURL(); | |
1109 untrusted_navigations_since_signin_visit_ = 0; | |
1110 untrusted_confirmation_required_ = false; | |
1111 error_message_.clear(); | |
1112 | |
1113 // Post to IO thread to clear pending email. | |
1114 if (!do_not_clear_pending_email_) { | |
1115 Profile* profile = | |
1116 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); | |
1117 content::BrowserThread::PostTask( | |
1118 content::BrowserThread::IO, FROM_HERE, | |
1119 base::Bind(&ClearPendingEmailOnIOThread, | |
1120 base::Unretained(profile->GetResourceContext()))); | |
1121 } | |
1122 } | |
1123 | |
1124 void OneClickSigninHelper::PasswordSubmitted( | |
1125 const autofill::PasswordForm& form) { | |
1126 // We only need to scrape the password for Gaia logins. | |
1127 if (gaia::IsGaiaSignonRealm(GURL(form.signon_realm))) { | |
1128 VLOG(1) << "OneClickSigninHelper::DidNavigateAnyFrame: got password"; | |
1129 password_ = base::UTF16ToUTF8(form.password_value); | |
1130 } | |
1131 } | |
1132 | |
1133 void OneClickSigninHelper::SetDoNotClearPendingEmailForTesting() { | |
1134 do_not_clear_pending_email_ = true; | |
1135 } | |
1136 | |
1137 void OneClickSigninHelper::set_do_not_start_sync_for_testing() { | |
1138 do_not_start_sync_for_testing_ = true; | |
1139 } | |
1140 | |
1141 void OneClickSigninHelper::DidStartNavigationToPendingEntry( | |
1142 const GURL& url, | |
1143 content::NavigationController::ReloadType reload_type) { | |
1144 VLOG(1) << "OneClickSigninHelper::DidStartNavigationToPendingEntry: url=" << | |
1145 url.spec(); | |
1146 // If the tab navigates to a new page, and this page is not a valid Gaia | |
1147 // sign in redirect or reponse, or the expected continue URL, make sure to | |
1148 // clear the internal state. This is needed to detect navigations in the | |
1149 // middle of the sign in process that may redirect back to the sign in | |
1150 // process (see crbug.com/181163 for details). | |
1151 GURL::Replacements replacements; | |
1152 replacements.ClearQuery(); | |
1153 | |
1154 if (!IsValidGaiaSigninRedirectOrResponseURL(url) && | |
1155 continue_url_.is_valid() && | |
1156 url.ReplaceComponents(replacements) != | |
1157 continue_url_.ReplaceComponents(replacements)) { | |
1158 if (++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince) | |
1159 CleanTransientState(); | |
1160 } | |
1161 } | |
1162 | |
1163 void OneClickSigninHelper::DidNavigateMainFrame( | |
1164 const content::LoadCommittedDetails& details, | |
1165 const content::FrameNavigateParams& params) { | |
1166 if (!SigninManager::IsWebBasedSigninFlowURL(params.url)) { | |
1167 // Make sure the renderer process is no longer considered the trusted | |
1168 // sign-in process when a navigation to a non-sign-in URL occurs. | |
1169 Profile* profile = | |
1170 Profile::FromBrowserContext(web_contents()->GetBrowserContext()); | |
1171 SigninClient* signin_client = | |
1172 profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL; | |
1173 int process_id = web_contents()->GetRenderProcessHost()->GetID(); | |
1174 if (signin_client && signin_client->IsSigninProcess(process_id)) | |
1175 signin_client->ClearSigninProcess(); | |
1176 | |
1177 // If the navigation to a non-sign-in URL hasn't been triggered by the web | |
1178 // contents, the sign in flow has been aborted and the state must be | |
1179 // cleaned (crbug.com/269421). | |
1180 if (!ui::PageTransitionIsWebTriggerable(params.transition) && | |
1181 auto_accept_ != AUTO_ACCEPT_NONE) { | |
1182 CleanTransientState(); | |
1183 } | |
1184 } | |
1185 } | |
1186 | |
1187 void OneClickSigninHelper::DidStopLoading( | |
1188 content::RenderViewHost* render_view_host) { | |
1189 // If the user left the sign in process, clear all members. | |
1190 // TODO(rogerta): might need to allow some youtube URLs. | |
1191 content::WebContents* contents = web_contents(); | |
1192 const GURL url = contents->GetLastCommittedURL(); | |
1193 Profile* profile = | |
1194 Profile::FromBrowserContext(contents->GetBrowserContext()); | |
1195 VLOG(1) << "OneClickSigninHelper::DidStopLoading: url=" << url.spec(); | |
1196 | |
1197 if (url.scheme() == content::kChromeUIScheme) { | |
1198 // Suppresses OneClickSigninHelper on webUI pages to avoid inteference with | |
1199 // inline signin flows. | |
1200 VLOG(1) << "OneClickSigninHelper::DidStopLoading: suppressed for url=" | |
1201 << url.spec(); | |
1202 CleanTransientState(); | |
1203 return; | |
1204 } | |
1205 | |
1206 // If an error has already occured during the sign in flow, make sure to | |
1207 // display it to the user and abort the process. Do this only for | |
1208 // explicit sign ins. | |
1209 // TODO(rogerta): Could we move this code back up to ShowInfoBarUIThread()? | |
1210 if (!error_message_.empty() && auto_accept_ == AUTO_ACCEPT_EXPLICIT) { | |
1211 VLOG(1) << "OneClickSigninHelper::DidStopLoading: error=" << error_message_; | |
1212 RemoveSigninRedirectURLHistoryItem(contents); | |
1213 // After we redirect to NTP, our browser pointer gets corrupted because the | |
1214 // WebContents have changed, so grab the browser pointer | |
1215 // before the navigation. | |
1216 Browser* browser = chrome::FindBrowserWithWebContents(contents); | |
1217 | |
1218 // Redirect to the landing page and display an error popup. | |
1219 RedirectToNtpOrAppsPage(web_contents(), source_); | |
1220 LoginUIServiceFactory::GetForProfile(profile)-> | |
1221 DisplayLoginResult(browser, base::UTF8ToUTF16(error_message_)); | |
1222 CleanTransientState(); | |
1223 return; | |
1224 } | |
1225 | |
1226 if (AreWeShowingSignin(url, source_, email_)) { | |
1227 if (!showing_signin_) | |
1228 LogHistogramValue(signin_metrics::HISTOGRAM_SHOWN); | |
1229 showing_signin_ = true; | |
1230 } | |
1231 | |
1232 // When Gaia finally redirects to the continue URL, Gaia will add some | |
1233 // extra query parameters. So ignore the parameters when checking to see | |
1234 // if the user has continued. Sometimes locales will redirect to a country- | |
1235 // specific TLD so just make sure it's a valid domain instead of comparing | |
1236 // for an exact match. | |
1237 GURL::Replacements replacements; | |
1238 replacements.ClearQuery(); | |
1239 bool google_domain_url = google_util::IsGoogleDomainUrl( | |
1240 url, | |
1241 google_util::ALLOW_SUBDOMAIN, | |
1242 google_util::DISALLOW_NON_STANDARD_PORTS); | |
1243 const bool continue_url_match = | |
1244 google_domain_url && | |
1245 url.ReplaceComponents(replacements).path() == | |
1246 continue_url_.ReplaceComponents(replacements).path(); | |
1247 const bool original_continue_url_match = | |
1248 google_domain_url && | |
1249 url.ReplaceComponents(replacements).path() == | |
1250 original_continue_url_.ReplaceComponents(replacements).path(); | |
1251 | |
1252 if (continue_url_match) | |
1253 RemoveSigninRedirectURLHistoryItem(contents); | |
1254 | |
1255 // If there is no valid email yet, there is nothing to do. As of M26, the | |
1256 // password is allowed to be empty, since its no longer required to setup | |
1257 // sync. | |
1258 if (email_.empty()) { | |
1259 VLOG(1) << "OneClickSigninHelper::DidStopLoading: nothing to do"; | |
1260 // Original-url check done because some user actions cans get us to a page | |
1261 // via a POST instead of a GET (and thus to immediate "cuntinue url") but | |
1262 // we still want redirects from the "blank.html" landing page to work for | |
1263 // non-security related redirects like NTP. | |
1264 // https://code.google.com/p/chromium/issues/detail?id=321938 | |
1265 if (original_continue_url_match) { | |
1266 if (auto_accept_ == AUTO_ACCEPT_EXPLICIT) | |
1267 RedirectToSignin(); | |
1268 std::string unused_value; | |
1269 if (net::GetValueForKeyInQuery(url, "ntp", &unused_value)) { | |
1270 signin::SetUserSkippedPromo(profile); | |
1271 RedirectToNtpOrAppsPage(web_contents(), source_); | |
1272 } | |
1273 } else { | |
1274 if (!IsValidGaiaSigninRedirectOrResponseURL(url) && | |
1275 ++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince) { | |
1276 CleanTransientState(); | |
1277 } | |
1278 } | |
1279 | |
1280 return; | |
1281 } | |
1282 | |
1283 if (!continue_url_match && IsValidGaiaSigninRedirectOrResponseURL(url)) | |
1284 return; | |
1285 | |
1286 // During an explicit sign in, if the user has not yet reached the final | |
1287 // continue URL, wait for it to arrive. Note that Gaia will add some extra | |
1288 // query parameters to the continue URL. Ignore them when checking to | |
1289 // see if the user has continued. | |
1290 // | |
1291 // If this is not an explicit sign in, we don't need to check if we landed | |
1292 // on the right continue URL. This is important because the continue URL | |
1293 // may itself lead to a redirect, which means this function will never see | |
1294 // the continue URL go by. | |
1295 if (auto_accept_ == AUTO_ACCEPT_EXPLICIT) { | |
1296 DCHECK(source_ != signin_metrics::SOURCE_UNKNOWN); | |
1297 if (!continue_url_match) { | |
1298 VLOG(1) << "OneClickSigninHelper::DidStopLoading: invalid url='" | |
1299 << url.spec() | |
1300 << "' expected continue url=" << continue_url_; | |
1301 CleanTransientState(); | |
1302 return; | |
1303 } | |
1304 | |
1305 // In explicit sign ins, the user may have changed the box | |
1306 // "Let me choose what to sync". This is reflected as a change in the | |
1307 // source of the continue URL. Make one last check of the current URL | |
1308 // to see if there is a valid source. If so, it overrides the | |
1309 // current source. | |
1310 // | |
1311 // If the source was changed to SOURCE_SETTINGS, we want | |
1312 // OneClickSigninSyncStarter to reuse the current tab to display the | |
1313 // advanced configuration. | |
1314 signin_metrics::Source source = signin::GetSourceForPromoURL(url); | |
1315 if (source != source_) { | |
1316 source_ = source; | |
1317 switched_to_advanced_ = source == signin_metrics::SOURCE_SETTINGS; | |
1318 } | |
1319 } | |
1320 | |
1321 Browser* browser = chrome::FindBrowserWithWebContents(contents); | |
1322 | |
1323 VLOG(1) << "OneClickSigninHelper::DidStopLoading: signin is go." | |
1324 << " auto_accept=" << auto_accept_ | |
1325 << " source=" << source_; | |
1326 | |
1327 switch (auto_accept_) { | |
1328 case AUTO_ACCEPT_NONE: | |
1329 if (showing_signin_) | |
1330 LogHistogramValue(signin_metrics::HISTOGRAM_DISMISSED); | |
1331 break; | |
1332 case AUTO_ACCEPT_ACCEPTED: | |
1333 LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED); | |
1334 LogHistogramValue(signin_metrics::HISTOGRAM_WITH_DEFAULTS); | |
1335 SigninManager::DisableOneClickSignIn(profile->GetPrefs()); | |
1336 // Start syncing with the default settings - prompt the user to sign in | |
1337 // first. | |
1338 if (!do_not_start_sync_for_testing_) { | |
1339 StartSync( | |
1340 StartSyncArgs(profile, browser, auto_accept_, | |
1341 session_index_, email_, password_, "", | |
1342 NULL /* don't force sync setup in same tab */, | |
1343 true /* confirmation_required */, source_, | |
1344 OneClickSigninSyncStarter::Callback()), | |
1345 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS); | |
1346 } | |
1347 break; | |
1348 case AUTO_ACCEPT_CONFIGURE: | |
1349 LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED); | |
1350 LogHistogramValue(signin_metrics::HISTOGRAM_WITH_ADVANCED); | |
1351 SigninManager::DisableOneClickSignIn(profile->GetPrefs()); | |
1352 // Display the extra confirmation (even in the SAML case) in case this | |
1353 // was an untrusted renderer. | |
1354 if (!do_not_start_sync_for_testing_) { | |
1355 StartSync( | |
1356 StartSyncArgs(profile, browser, auto_accept_, | |
1357 session_index_, email_, password_, "", | |
1358 NULL /* don't force sync setup in same tab */, | |
1359 true /* confirmation_required */, source_, | |
1360 OneClickSigninSyncStarter::Callback()), | |
1361 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST); | |
1362 } | |
1363 break; | |
1364 case AUTO_ACCEPT_EXPLICIT: { | |
1365 signin_metrics::Source original_source = | |
1366 signin::GetSourceForPromoURL(original_continue_url_); | |
1367 if (switched_to_advanced_) { | |
1368 LogHistogramValue(signin_metrics::HISTOGRAM_WITH_ADVANCED); | |
1369 LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED); | |
1370 } else { | |
1371 LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED); | |
1372 LogHistogramValue(signin_metrics::HISTOGRAM_WITH_DEFAULTS); | |
1373 } | |
1374 | |
1375 // - If sign in was initiated from the NTP or the hotdog menu, sync with | |
1376 // default settings. | |
1377 // - If sign in was initiated from the settings page for first time sync | |
1378 // set up, show the advanced sync settings dialog. | |
1379 // - If sign in was initiated from the settings page due to a re-auth when | |
1380 // sync was already setup, simply navigate back to the settings page. | |
1381 ProfileSyncService* sync_service = | |
1382 ProfileSyncServiceFactory::GetForProfile(profile); | |
1383 SigninErrorController* error_controller = | |
1384 SigninErrorControllerFactory::GetForProfile(profile); | |
1385 | |
1386 OneClickSigninSyncStarter::StartSyncMode start_mode = | |
1387 source_ == signin_metrics::SOURCE_SETTINGS ? | |
1388 (error_controller->HasError() && | |
1389 sync_service && sync_service->HasSyncSetupCompleted()) ? | |
1390 OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE : | |
1391 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST : | |
1392 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS; | |
1393 | |
1394 if (!HandleCrossAccountError(profile, session_index_, email_, password_, | |
1395 "", auto_accept_, source_, start_mode, | |
1396 OneClickSigninSyncStarter::Callback())) { | |
1397 if (!do_not_start_sync_for_testing_) { | |
1398 StartSync( | |
1399 StartSyncArgs(profile, browser, auto_accept_, | |
1400 session_index_, email_, password_, "", | |
1401 contents, | |
1402 untrusted_confirmation_required_, source_, | |
1403 OneClickSigninSyncStarter::Callback()), | |
1404 start_mode); | |
1405 } | |
1406 | |
1407 // If this explicit sign in is not from settings page/webstore, show | |
1408 // the NTP/Apps page after sign in completes. In the case of the | |
1409 // settings page, it will get auto-closed after sync setup. In the case | |
1410 // of webstore, it will redirect back to webstore. | |
1411 RedirectToNtpOrAppsPageIfNecessary(web_contents(), source_); | |
1412 } | |
1413 | |
1414 // Observe the sync service if the settings tab requested a gaia sign in, | |
1415 // so that when sign in and sync setup are successful, we can redirect to | |
1416 // the correct URL, or auto-close the gaia sign in tab. | |
1417 if (original_source == signin_metrics::SOURCE_SETTINGS) { | |
1418 // The observer deletes itself once it's done. | |
1419 new OneClickSigninSyncObserver(contents, original_continue_url_); | |
1420 } | |
1421 break; | |
1422 } | |
1423 case AUTO_ACCEPT_REJECTED_FOR_PROFILE: | |
1424 AddEmailToOneClickRejectedList(profile, email_); | |
1425 LogHistogramValue(signin_metrics::HISTOGRAM_REJECTED); | |
1426 break; | |
1427 default: | |
1428 NOTREACHED() << "Invalid auto_accept=" << auto_accept_; | |
1429 break; | |
1430 } | |
1431 | |
1432 CleanTransientState(); | |
1433 } | |
OLD | NEW |