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.
5 #include "chrome/browser/ui/sync/one_click_signin_helper.h"
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/google/google_util.h"
31 #include "chrome/browser/history/history_service.h"
32 #include "chrome/browser/history/history_service_factory.h"
33 #include "chrome/browser/password_manager/password_manager.h"
34 #include "chrome/browser/profiles/profile.h"
35 #include "chrome/browser/profiles/profile_info_cache.h"
36 #include "chrome/browser/profiles/profile_io_data.h"
37 #include "chrome/browser/profiles/profile_manager.h"
38 #include "chrome/browser/search/search.h"
39 #include "chrome/browser/signin/chrome_signin_manager_delegate.h"
40 #include "chrome/browser/signin/signin_global_error.h"
41 #include "chrome/browser/signin/signin_manager.h"
42 #include "chrome/browser/signin/signin_manager_delegate.h"
43 #include "chrome/browser/signin/signin_manager_factory.h"
44 #include "chrome/browser/signin/signin_names_io_thread.h"
45 #include "chrome/browser/sync/profile_sync_service.h"
46 #include "chrome/browser/sync/profile_sync_service_factory.h"
47 #include "chrome/browser/sync/sync_prefs.h"
48 #include "chrome/browser/tab_contents/tab_util.h"
49 #include "chrome/browser/ui/browser_finder.h"
50 #include "chrome/browser/ui/browser_window.h"
51 #include "chrome/browser/ui/chrome_pages.h"
52 #include "chrome/browser/ui/sync/one_click_signin_histogram.h"
53 #include "chrome/browser/ui/sync/one_click_signin_sync_starter.h"
54 #include "chrome/browser/ui/sync/signin_histogram.h"
55 #include "chrome/browser/ui/tab_modal_confirm_dialog.h"
56 #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
57 #include "chrome/browser/ui/tabs/tab_strip_model.h"
58 #include "chrome/common/chrome_switches.h"
59 #include "chrome/common/chrome_version_info.h"
60 #include "chrome/common/net/url_util.h"
61 #include "chrome/common/pref_names.h"
62 #include "chrome/common/url_constants.h"
63 #include "components/autofill/core/common/password_form.h"
64 #include "content/public/browser/browser_thread.h"
65 #include "content/public/browser/navigation_entry.h"
66 #include "content/public/browser/page_navigator.h"
67 #include "content/public/browser/render_process_host.h"
68 #include "content/public/browser/web_contents.h"
69 #include "content/public/browser/web_contents_view.h"
70 #include "content/public/common/frame_navigate_params.h"
71 #include "content/public/common/page_transition_types.h"
72 #include "google_apis/gaia/gaia_auth_util.h"
73 #include "google_apis/gaia/gaia_urls.h"
74 #include "grit/chromium_strings.h"
75 #include "grit/generated_resources.h"
76 #include "grit/theme_resources.h"
77 #include "ipc/ipc_message_macros.h"
78 #include "net/base/url_util.h"
79 #include "net/cookies/cookie_monster.h"
80 #include "net/url_request/url_request.h"
81 #include "ui/base/l10n/l10n_util.h"
82 #include "ui/base/resource/resource_bundle.h"
88 // StartSyncArgs --------------------------------------------------------------
90 // Arguments used with StartSync function. base::Bind() cannot support too
91 // many args for performance reasons, so they are packaged up into a struct.
92 struct StartSyncArgs {
93 StartSyncArgs(Profile* profile,
95 OneClickSigninHelper::AutoAccept auto_accept,
96 const std::string& session_index,
97 const std::string& email,
98 const std::string& password,
99 content::WebContents* web_contents,
100 bool untrusted_confirmation_required,
101 signin::Source source,
102 OneClickSigninSyncStarter::Callback callback);
106 OneClickSigninHelper::AutoAccept auto_accept;
107 std::string session_index;
109 std::string password;
111 // Web contents in which the sync setup page should be displayed,
112 // if necessary. Can be NULL.
113 content::WebContents* web_contents;
115 OneClickSigninSyncStarter::ConfirmationRequired confirmation_required;
116 signin::Source source;
117 OneClickSigninSyncStarter::Callback callback;
120 StartSyncArgs::StartSyncArgs(Profile* profile,
122 OneClickSigninHelper::AutoAccept auto_accept,
123 const std::string& session_index,
124 const std::string& email,
125 const std::string& password,
126 content::WebContents* web_contents,
127 bool untrusted_confirmation_required,
128 signin::Source source,
129 OneClickSigninSyncStarter::Callback callback)
132 auto_accept(auto_accept),
133 session_index(session_index),
136 web_contents(web_contents),
139 if (untrusted_confirmation_required) {
140 confirmation_required = OneClickSigninSyncStarter::CONFIRM_UNTRUSTED_SIGNIN;
141 } else if (source == signin::SOURCE_SETTINGS ||
142 source == signin::SOURCE_WEBSTORE_INSTALL) {
143 // Do not display a status confirmation for webstore installs or re-auth.
144 confirmation_required = OneClickSigninSyncStarter::NO_CONFIRMATION;
146 confirmation_required = OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN;
151 // ConfirmEmailDialogDelegate -------------------------------------------------
153 class ConfirmEmailDialogDelegate : public TabModalConfirmDialogDelegate {
161 // Callback indicating action performed by the user.
162 typedef base::Callback<void(Action)> Callback;
164 // Ask the user for confirmation before starting to sync.
165 static void AskForConfirmation(content::WebContents* contents,
166 const std::string& last_email,
167 const std::string& email,
171 ConfirmEmailDialogDelegate(content::WebContents* contents,
172 const std::string& last_email,
173 const std::string& email,
175 virtual ~ConfirmEmailDialogDelegate();
177 // TabModalConfirmDialogDelegate:
178 virtual string16 GetTitle() OVERRIDE;
179 virtual string16 GetMessage() OVERRIDE;
180 virtual string16 GetAcceptButtonTitle() OVERRIDE;
181 virtual string16 GetCancelButtonTitle() OVERRIDE;
182 virtual string16 GetLinkText() const OVERRIDE;
183 virtual void OnAccepted() OVERRIDE;
184 virtual void OnCanceled() OVERRIDE;
185 virtual void OnClosed() OVERRIDE;
186 virtual void OnLinkClicked(WindowOpenDisposition disposition) OVERRIDE;
188 std::string last_email_;
192 // Web contents from which the "Learn more" link should be opened.
193 content::WebContents* web_contents_;
195 DISALLOW_COPY_AND_ASSIGN(ConfirmEmailDialogDelegate);
199 void ConfirmEmailDialogDelegate::AskForConfirmation(
200 content::WebContents* contents,
201 const std::string& last_email,
202 const std::string& email,
204 TabModalConfirmDialog::Create(
205 new ConfirmEmailDialogDelegate(contents, last_email, email,
206 callback), contents);
209 ConfirmEmailDialogDelegate::ConfirmEmailDialogDelegate(
210 content::WebContents* contents,
211 const std::string& last_email,
212 const std::string& email,
214 : TabModalConfirmDialogDelegate(contents),
215 last_email_(last_email),
218 web_contents_(contents) {
221 ConfirmEmailDialogDelegate::~ConfirmEmailDialogDelegate() {
224 string16 ConfirmEmailDialogDelegate::GetTitle() {
225 return l10n_util::GetStringUTF16(
226 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_TITLE);
229 string16 ConfirmEmailDialogDelegate::GetMessage() {
230 return l10n_util::GetStringFUTF16(
231 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_MESSAGE,
232 UTF8ToUTF16(last_email_), UTF8ToUTF16(email_));
235 string16 ConfirmEmailDialogDelegate::GetAcceptButtonTitle() {
236 return l10n_util::GetStringUTF16(
237 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_OK_BUTTON);
240 string16 ConfirmEmailDialogDelegate::GetCancelButtonTitle() {
241 return l10n_util::GetStringUTF16(
242 IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_CANCEL_BUTTON);
245 string16 ConfirmEmailDialogDelegate::GetLinkText() const {
246 return l10n_util::GetStringUTF16(IDS_LEARN_MORE);
249 void ConfirmEmailDialogDelegate::OnAccepted() {
250 base::ResetAndReturn(&callback_).Run(CREATE_NEW_USER);
253 void ConfirmEmailDialogDelegate::OnCanceled() {
254 base::ResetAndReturn(&callback_).Run(START_SYNC);
257 void ConfirmEmailDialogDelegate::OnClosed() {
258 base::ResetAndReturn(&callback_).Run(CLOSE);
261 void ConfirmEmailDialogDelegate::OnLinkClicked(
262 WindowOpenDisposition disposition) {
263 content::OpenURLParams params(
264 GURL(chrome::kChromeSyncMergeTroubleshootingURL),
267 content::PAGE_TRANSITION_AUTO_TOPLEVEL,
269 // It is guaranteed that |web_contents_| is valid here because when it's
270 // deleted, the dialog is immediately closed and no further action can be
272 web_contents_->OpenURL(params);
276 // Helpers --------------------------------------------------------------------
278 // Add a specific email to the list of emails rejected for one-click
279 // sign-in, for this profile.
280 void AddEmailToOneClickRejectedList(Profile* profile,
281 const std::string& email) {
282 ListPrefUpdate updater(profile->GetPrefs(),
283 prefs::kReverseAutologinRejectedEmailList);
284 updater->AppendIfNotPresent(new base::StringValue(email));
287 void LogHistogramValue(signin::Source source, int action) {
289 case signin::SOURCE_START_PAGE:
290 UMA_HISTOGRAM_ENUMERATION("Signin.StartPageActions", action,
291 one_click_signin::HISTOGRAM_MAX);
293 case signin::SOURCE_NTP_LINK:
294 UMA_HISTOGRAM_ENUMERATION("Signin.NTPLinkActions", action,
295 one_click_signin::HISTOGRAM_MAX);
297 case signin::SOURCE_MENU:
298 UMA_HISTOGRAM_ENUMERATION("Signin.MenuActions", action,
299 one_click_signin::HISTOGRAM_MAX);
301 case signin::SOURCE_SETTINGS:
302 UMA_HISTOGRAM_ENUMERATION("Signin.SettingsActions", action,
303 one_click_signin::HISTOGRAM_MAX);
305 case signin::SOURCE_EXTENSION_INSTALL_BUBBLE:
306 UMA_HISTOGRAM_ENUMERATION("Signin.ExtensionInstallBubbleActions", action,
307 one_click_signin::HISTOGRAM_MAX);
309 case signin::SOURCE_WEBSTORE_INSTALL:
310 UMA_HISTOGRAM_ENUMERATION("Signin.WebstoreInstallActions", action,
311 one_click_signin::HISTOGRAM_MAX);
313 case signin::SOURCE_APP_LAUNCHER:
314 UMA_HISTOGRAM_ENUMERATION("Signin.AppLauncherActions", action,
315 one_click_signin::HISTOGRAM_MAX);
317 case signin::SOURCE_APPS_PAGE_LINK:
318 UMA_HISTOGRAM_ENUMERATION("Signin.AppsPageLinkActions", action,
319 one_click_signin::HISTOGRAM_MAX);
321 case signin::SOURCE_BOOKMARK_BUBBLE:
322 UMA_HISTOGRAM_ENUMERATION("Signin.BookmarkBubbleActions", action,
323 one_click_signin::HISTOGRAM_MAX);
326 // This switch statement needs to be updated when the enum Source changes.
327 COMPILE_ASSERT(signin::SOURCE_UNKNOWN == 11,
328 kSourceEnumHasChangedButNotThisSwitchStatement);
332 UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action,
333 one_click_signin::HISTOGRAM_MAX);
336 void LogOneClickHistogramValue(int action) {
337 UMA_HISTOGRAM_ENUMERATION("Signin.OneClickActions", action,
338 one_click_signin::HISTOGRAM_MAX);
339 UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action,
340 one_click_signin::HISTOGRAM_MAX);
343 void RedirectToNtpOrAppsPage(content::WebContents* contents,
344 signin::Source source) {
345 VLOG(1) << "RedirectToNtpOrAppsPage";
346 // Redirect to NTP/Apps page and display a confirmation bubble
347 GURL url(source == signin::SOURCE_APPS_PAGE_LINK ?
348 chrome::kChromeUIAppsURL : chrome::kChromeUINewTabURL);
349 content::OpenURLParams params(url,
352 content::PAGE_TRANSITION_AUTO_TOPLEVEL,
354 contents->OpenURL(params);
357 void RedirectToNtpOrAppsPageWithIds(int child_id,
359 signin::Source source) {
360 content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id,
365 RedirectToNtpOrAppsPage(web_contents, source);
368 // If the |source| is not settings page/webstore, redirects to
369 // the NTP/Apps page.
370 void RedirectToNtpOrAppsPageIfNecessary(content::WebContents* contents,
371 signin::Source source) {
372 if (source != signin::SOURCE_SETTINGS &&
373 source != signin::SOURCE_WEBSTORE_INSTALL) {
374 RedirectToNtpOrAppsPage(contents, source);
378 // Start syncing with the given user information.
379 void StartSync(const StartSyncArgs& args,
380 OneClickSigninSyncStarter::StartSyncMode start_mode) {
381 if (start_mode == OneClickSigninSyncStarter::UNDO_SYNC) {
382 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_UNDO);
386 // The starter deletes itself once its done.
387 new OneClickSigninSyncStarter(args.profile, args.browser, args.session_index,
388 args.email, args.password,
389 "" /* oauth_code */, start_mode,
391 args.confirmation_required,
394 int action = one_click_signin::HISTOGRAM_MAX;
395 switch (args.auto_accept) {
396 case OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT:
398 case OneClickSigninHelper::AUTO_ACCEPT_ACCEPTED:
400 start_mode == OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS ?
401 one_click_signin::HISTOGRAM_AUTO_WITH_DEFAULTS :
402 one_click_signin::HISTOGRAM_AUTO_WITH_ADVANCED;
404 case OneClickSigninHelper::AUTO_ACCEPT_CONFIGURE:
405 DCHECK(start_mode == OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
406 action = one_click_signin::HISTOGRAM_AUTO_WITH_ADVANCED;
409 NOTREACHED() << "Invalid auto_accept: " << args.auto_accept;
412 if (action != one_click_signin::HISTOGRAM_MAX)
413 LogOneClickHistogramValue(action);
416 void StartExplicitSync(const StartSyncArgs& args,
417 content::WebContents* contents,
418 OneClickSigninSyncStarter::StartSyncMode start_mode,
419 ConfirmEmailDialogDelegate::Action action) {
420 if (action == ConfirmEmailDialogDelegate::START_SYNC) {
421 StartSync(args, start_mode);
422 RedirectToNtpOrAppsPageIfNecessary(contents, args.source);
424 // Perform a redirection to the NTP/Apps page to hide the blank page when
425 // the action is CLOSE or CREATE_NEW_USER. The redirection is useful when
426 // the action is CREATE_NEW_USER because the "Create new user" page might
427 // be opened in a different tab that is already showing settings.
429 // Don't redirect when the visible URL is not a blank page: if the
430 // source is SOURCE_WEBSTORE_INSTALL, |contents| might be showing an app
431 // page that shouldn't be hidden.
433 // If redirecting, don't do so immediately, otherwise there may be 2 nested
434 // navigations and a crash would occur (crbug.com/293261). Post the task
435 // to the current thread instead.
436 if (signin::IsContinueUrlForWebBasedSigninFlow(
437 contents->GetVisibleURL())) {
438 base::MessageLoopProxy::current()->PostNonNestableTask(
440 base::Bind(RedirectToNtpOrAppsPageWithIds,
441 contents->GetRenderProcessHost()->GetID(),
442 contents->GetRoutingID(),
445 if (action == ConfirmEmailDialogDelegate::CREATE_NEW_USER) {
446 chrome::ShowSettingsSubPage(args.browser,
447 std::string(chrome::kSearchUsersSubPage));
452 void ClearPendingEmailOnIOThread(content::ResourceContext* context) {
453 ProfileIOData* io_data = ProfileIOData::FromResourceContext(context);
455 io_data->set_reverse_autologin_pending_email(std::string());
458 // Determines the source of the sign in and the continue URL. Its either one
459 // of the known sign in access point (first run, NTP, Apps page, menu, settings)
460 // or its an implicit sign in via another Google property. In the former case,
461 // "service" is also checked to make sure its "chromiumsync".
462 signin::Source GetSigninSource(const GURL& url, GURL* continue_url) {
463 DCHECK(url.is_valid());
465 net::GetValueForKeyInQuery(url, "service", &value);
466 bool possibly_an_explicit_signin = value == "chromiumsync";
468 // Find the final continue URL for this sign in. In some cases, Gaia can
469 // continue to itself, with the original continue URL buried under a couple
470 // of layers of indirection. Peel those layers away. The final destination
471 // can also be "IsGaiaSignonRealm" so stop if we get to the end (but be sure
472 // we always extract at least one "continue" value).
473 GURL local_continue_url = signin::GetNextPageURLForPromoURL(url);
474 while (gaia::IsGaiaSignonRealm(local_continue_url.GetOrigin())) {
475 GURL next_continue_url =
476 signin::GetNextPageURLForPromoURL(local_continue_url);
477 if (!next_continue_url.is_valid())
479 local_continue_url = next_continue_url;
482 if (continue_url && local_continue_url.is_valid()) {
483 DCHECK(!continue_url->is_valid() || *continue_url == local_continue_url);
484 *continue_url = local_continue_url;
487 return possibly_an_explicit_signin ?
488 signin::GetSourceForPromoURL(local_continue_url) :
489 signin::SOURCE_UNKNOWN;
492 // Returns true if |url| is a valid URL that can occur during the sign in
493 // process. Valid URLs are of the form:
495 // https://accounts.google.{TLD}/...
496 // https://accounts.youtube.com/...
497 // https://accounts.blogger.com/...
499 // All special headers used by one click sign in occur on
500 // https://accounts.google.com URLs. However, the sign in process may redirect
501 // to intermediate Gaia URLs that do not end with .com. For example, an account
502 // that uses SMS 2-factor outside the US may redirect to country specific URLs.
504 // The sign in process may also redirect to youtube and blogger account URLs
505 // so that Gaia acts as a single signon service.
506 bool IsValidGaiaSigninRedirectOrResponseURL(const GURL& url) {
507 std::string hostname = url.host();
508 if (google_util::IsGoogleHostname(hostname, google_util::ALLOW_SUBDOMAIN)) {
509 // Also using IsGaiaSignonRealm() to handle overriding with command line.
510 return gaia::IsGaiaSignonRealm(url.GetOrigin()) ||
511 StartsWithASCII(hostname, "accounts.", false);
514 GURL origin = url.GetOrigin();
515 if (origin == GURL("https://accounts.youtube.com") ||
516 origin == GURL("https://accounts.blogger.com"))
522 // Tells when we are in the process of showing either the signin to chrome page
523 // or the one click sign in to chrome page.
524 // NOTE: This should only be used for logging purposes since it relies on hard
525 // coded URLs that could change.
526 bool AreWeShowingSignin(GURL url, signin::Source source, std::string email) {
527 GURL::Replacements replacements;
528 replacements.ClearQuery();
529 GURL clean_login_url =
530 GaiaUrls::GetInstance()->service_login_url().ReplaceComponents(
533 return (url.ReplaceComponents(replacements) == clean_login_url &&
534 source != signin::SOURCE_UNKNOWN) ||
535 (IsValidGaiaSigninRedirectOrResponseURL(url) &&
536 url.spec().find("ChromeLoginPrompt") != std::string::npos &&
540 // CurrentHistoryCleaner ------------------------------------------------------
542 // Watch a webcontents and remove URL from the history once loading is complete.
543 // We have to delay the cleaning until the new URL has finished loading because
544 // we're not allowed to remove the last-loaded URL from the history. Objects
545 // of this type automatically self-destruct once they're finished their work.
546 class CurrentHistoryCleaner : public content::WebContentsObserver {
548 explicit CurrentHistoryCleaner(content::WebContents* contents);
549 virtual ~CurrentHistoryCleaner();
551 // content::WebContentsObserver:
552 virtual void WebContentsDestroyed(content::WebContents* contents) OVERRIDE;
553 virtual void DidCommitProvisionalLoadForFrame(
555 const string16& frame_unique_name,
558 content::PageTransition transition_type,
559 content::RenderViewHost* render_view_host) OVERRIDE;
562 scoped_ptr<content::WebContents> contents_;
563 int history_index_to_remove_;
565 DISALLOW_COPY_AND_ASSIGN(CurrentHistoryCleaner);
568 CurrentHistoryCleaner::CurrentHistoryCleaner(content::WebContents* contents)
569 : WebContentsObserver(contents) {
570 history_index_to_remove_ =
571 web_contents()->GetController().GetLastCommittedEntryIndex();
574 CurrentHistoryCleaner::~CurrentHistoryCleaner() {
577 void CurrentHistoryCleaner::DidCommitProvisionalLoadForFrame(
579 const string16& frame_unique_name,
582 content::PageTransition transition_type,
583 content::RenderViewHost* render_view_host) {
584 // Return early if this is not top-level navigation.
588 content::NavigationController* nc = &web_contents()->GetController();
589 HistoryService* hs = HistoryServiceFactory::GetForProfile(
590 Profile::FromBrowserContext(web_contents()->GetBrowserContext()),
591 Profile::IMPLICIT_ACCESS);
593 // Have to wait until something else gets added to history before removal.
594 if (history_index_to_remove_ < nc->GetLastCommittedEntryIndex()) {
595 content::NavigationEntry* entry =
596 nc->GetEntryAtIndex(history_index_to_remove_);
597 if (signin::IsContinueUrlForWebBasedSigninFlow(entry->GetURL())) {
598 hs->DeleteURL(entry->GetURL());
599 nc->RemoveEntryAtIndex(history_index_to_remove_);
600 delete this; // Success.
605 void CurrentHistoryCleaner::WebContentsDestroyed(
606 content::WebContents* contents) {
607 delete this; // Failure.
610 void CloseTab(content::WebContents* tab) {
611 Browser* browser = chrome::FindBrowserWithWebContents(tab);
613 TabStripModel* tab_strip_model = browser->tab_strip_model();
614 if (tab_strip_model) {
615 int index = tab_strip_model->GetIndexOfWebContents(tab);
616 if (index != TabStripModel::kNoTab) {
617 tab_strip_model->ExecuteContextMenuCommand(
618 index, TabStripModel::CommandCloseTab);
627 // OneClickSigninHelper -------------------------------------------------------
629 DEFINE_WEB_CONTENTS_USER_DATA_KEY(OneClickSigninHelper);
632 const int OneClickSigninHelper::kMaxNavigationsSince = 10;
634 OneClickSigninHelper::OneClickSigninHelper(content::WebContents* web_contents,
635 PasswordManager* password_manager)
636 : content::WebContentsObserver(web_contents),
637 showing_signin_(false),
638 auto_accept_(AUTO_ACCEPT_NONE),
639 source_(signin::SOURCE_UNKNOWN),
640 switched_to_advanced_(false),
641 untrusted_navigations_since_signin_visit_(0),
642 untrusted_confirmation_required_(false),
643 do_not_clear_pending_email_(false),
644 do_not_start_sync_for_testing_(false),
645 weak_pointer_factory_(this) {
646 // May be NULL during testing.
647 if (password_manager) {
648 password_manager->AddSubmissionCallback(
649 base::Bind(&OneClickSigninHelper::PasswordSubmitted,
650 weak_pointer_factory_.GetWeakPtr()));
654 OneClickSigninHelper::~OneClickSigninHelper() {
655 // WebContentsDestroyed() should always be called before the object is
657 DCHECK(!web_contents());
661 void OneClickSigninHelper::CreateForWebContentsWithPasswordManager(
662 content::WebContents* contents,
663 PasswordManager* password_manager) {
664 if (!FromWebContents(contents)) {
665 contents->SetUserData(UserDataKey(),
666 new OneClickSigninHelper(contents, password_manager));
671 bool OneClickSigninHelper::CanOffer(content::WebContents* web_contents,
672 CanOfferFor can_offer_for,
673 const std::string& email,
674 std::string* error_message) {
675 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
676 VLOG(1) << "OneClickSigninHelper::CanOffer";
679 error_message->clear();
684 if (web_contents->GetBrowserContext()->IsOffTheRecord())
688 Profile::FromBrowserContext(web_contents->GetBrowserContext());
692 SigninManager* manager =
693 SigninManagerFactory::GetForProfile(profile);
694 if (manager && !manager->IsSigninAllowed())
697 if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY &&
698 !profile->GetPrefs()->GetBoolean(prefs::kReverseAutologinEnabled))
701 if (!ChromeSigninManagerDelegate::ProfileAllowsSigninCookies(profile))
704 if (!email.empty()) {
708 // If the signin manager already has an authenticated name, then this is a
709 // re-auth scenario. Make sure the email just signed in corresponds to the
710 // the one sign in manager expects.
711 std::string current_email = manager->GetAuthenticatedUsername();
712 const bool same_email = gaia::AreEmailsSame(current_email, email);
713 if (!current_email.empty() && !same_email) {
714 UMA_HISTOGRAM_ENUMERATION("Signin.Reauth",
715 signin::HISTOGRAM_ACCOUNT_MISSMATCH,
716 signin::HISTOGRAM_MAX);
718 error_message->assign(
719 l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL,
720 UTF8ToUTF16(current_email)));
725 // Make sure this username is not prohibited by policy.
726 if (!manager->IsAllowedUsername(email)) {
728 error_message->assign(
729 l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED));
734 // If some profile, not just the current one, is already connected to this
735 // account, don't show the infobar.
736 if (g_browser_process && !same_email) {
737 ProfileManager* manager = g_browser_process->profile_manager();
739 string16 email16 = UTF8ToUTF16(email);
740 ProfileInfoCache& cache = manager->GetProfileInfoCache();
742 for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) {
743 if (email16 == cache.GetUserNameOfProfileAtIndex(i)) {
745 error_message->assign(
746 l10n_util::GetStringUTF8(IDS_SYNC_USER_NAME_IN_USE_ERROR));
754 // If email was already rejected by this profile for one-click sign-in.
755 if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY) {
756 const ListValue* rejected_emails = profile->GetPrefs()->GetList(
757 prefs::kReverseAutologinRejectedEmailList);
758 if (!rejected_emails->empty()) {
759 base::ListValue::const_iterator iter = rejected_emails->Find(
760 base::StringValue(email));
761 if (iter != rejected_emails->end())
767 VLOG(1) << "OneClickSigninHelper::CanOffer: yes we can";
772 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThread(
773 net::URLRequest* request,
774 ProfileIOData* io_data) {
775 return CanOfferOnIOThreadImpl(request->url(), request->referrer(),
780 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThreadImpl(
782 const std::string& referrer,
783 base::SupportsUserData* request,
784 ProfileIOData* io_data) {
785 if (!gaia::IsGaiaSignonRealm(url.GetOrigin()))
786 return IGNORE_REQUEST;
791 // Check for incognito before other parts of the io_data, since those
792 // members may not be initalized.
793 if (io_data->is_incognito())
796 if (!SigninManager::IsSigninAllowedOnIOThread(io_data))
799 if (!io_data->reverse_autologin_enabled()->GetValue())
802 if (!io_data->google_services_username()->GetValue().empty())
805 if (!ChromeSigninManagerDelegate::SettingsAllowSigninCookies(
806 io_data->GetCookieSettings()))
809 // The checks below depend on chrome already knowing what account the user
810 // signed in with. This happens only after receiving the response containing
811 // the Google-Accounts-SignIn header. Until then, if there is even a chance
812 // that we want to connect the profile, chrome needs to tell Gaia that
813 // it should offer the interstitial. Therefore missing one click data on
814 // the request means can offer is true.
815 const std::string& pending_email = io_data->reverse_autologin_pending_email();
816 if (!pending_email.empty()) {
817 if (!SigninManager::IsUsernameAllowedByPolicy(pending_email,
818 io_data->google_services_username_pattern()->GetValue())) {
822 std::vector<std::string> rejected_emails =
823 io_data->one_click_signin_rejected_email_list()->GetValue();
824 if (std::count_if(rejected_emails.begin(), rejected_emails.end(),
825 std::bind2nd(std::equal_to<std::string>(),
826 pending_email)) > 0) {
830 if (io_data->signin_names()->GetEmails().count(
831 UTF8ToUTF16(pending_email)) > 0) {
840 void OneClickSigninHelper::ShowInfoBarIfPossible(net::URLRequest* request,
841 ProfileIOData* io_data,
844 std::string google_chrome_signin_value;
845 std::string google_accounts_signin_value;
846 request->GetResponseHeaderByName("Google-Chrome-SignIn",
847 &google_chrome_signin_value);
848 request->GetResponseHeaderByName("Google-Accounts-SignIn",
849 &google_accounts_signin_value);
851 if (!google_accounts_signin_value.empty() ||
852 !google_chrome_signin_value.empty()) {
853 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
854 << " g-a-s='" << google_accounts_signin_value << "'"
855 << " g-c-s='" << google_chrome_signin_value << "'";
858 if (!gaia::IsGaiaSignonRealm(request->original_url().GetOrigin()))
861 // Parse Google-Accounts-SignIn.
862 std::vector<std::pair<std::string, std::string> > pairs;
863 base::SplitStringIntoKeyValuePairs(google_accounts_signin_value, '=', ',',
865 std::string session_index;
867 for (size_t i = 0; i < pairs.size(); ++i) {
868 const std::pair<std::string, std::string>& pair = pairs[i];
869 const std::string& key = pair.first;
870 const std::string& value = pair.second;
871 if (key == "email") {
872 TrimString(value, "\"", &email);
873 } else if (key == "sessionindex") {
874 session_index = value;
878 // Later in the chain of this request, we'll need to check the email address
879 // in the IO thread (see CanOfferOnIOThread). So save the email address as
880 // user data on the request (only for web-based flow).
882 io_data->set_reverse_autologin_pending_email(email);
884 if (!email.empty() || !session_index.empty()) {
885 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
886 << " email=" << email
887 << " sessionindex=" << session_index;
890 // Parse Google-Chrome-SignIn.
891 AutoAccept auto_accept = AUTO_ACCEPT_NONE;
892 signin::Source source = signin::SOURCE_UNKNOWN;
894 std::vector<std::string> tokens;
895 base::SplitString(google_chrome_signin_value, ',', &tokens);
896 for (size_t i = 0; i < tokens.size(); ++i) {
897 const std::string& token = tokens[i];
898 if (token == "accepted") {
899 auto_accept = AUTO_ACCEPT_ACCEPTED;
900 } else if (token == "configure") {
901 auto_accept = AUTO_ACCEPT_CONFIGURE;
902 } else if (token == "rejected-for-profile") {
903 auto_accept = AUTO_ACCEPT_REJECTED_FOR_PROFILE;
907 // If this is an explicit sign in (i.e., first run, NTP, Apps page, menu,
908 // settings) then force the auto accept type to explicit.
909 source = GetSigninSource(request->url(), &continue_url);
910 if (source != signin::SOURCE_UNKNOWN)
911 auto_accept = AUTO_ACCEPT_EXPLICIT;
913 if (auto_accept != AUTO_ACCEPT_NONE) {
914 VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
915 << " auto_accept=" << auto_accept;
918 // If |session_index|, |email|, |auto_accept|, and |continue_url| all have
919 // their default value, don't bother posting a task to the UI thread.
920 // It will be a noop anyway.
922 // The two headers above may (but not always) come in different http requests
923 // so a post to the UI thread is still needed if |auto_accept| is not its
924 // default value, but |email| and |session_index| are.
925 if (session_index.empty() && email.empty() &&
926 auto_accept == AUTO_ACCEPT_NONE && !continue_url.is_valid()) {
930 content::BrowserThread::PostTask(
931 content::BrowserThread::UI, FROM_HERE,
932 base::Bind(&OneClickSigninHelper::ShowInfoBarUIThread, session_index,
933 email, auto_accept, source, continue_url, child_id, route_id));
937 void OneClickSigninHelper::LogConfirmHistogramValue(int action) {
938 UMA_HISTOGRAM_ENUMERATION("Signin.OneClickConfirmation", action,
939 one_click_signin::HISTOGRAM_CONFIRM_MAX);
942 void OneClickSigninHelper::ShowInfoBarUIThread(
943 const std::string& session_index,
944 const std::string& email,
945 AutoAccept auto_accept,
946 signin::Source source,
947 const GURL& continue_url,
950 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
952 content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id,
957 // TODO(mathp): The appearance of this infobar should be tested using a
959 OneClickSigninHelper* helper =
960 OneClickSigninHelper::FromWebContents(web_contents);
964 if (auto_accept != AUTO_ACCEPT_NONE)
965 helper->auto_accept_ = auto_accept;
967 if (source != signin::SOURCE_UNKNOWN &&
968 helper->source_ == signin::SOURCE_UNKNOWN) {
969 helper->source_ = source;
972 // Save the email in the one-click signin manager. The manager may
973 // not exist if the contents is incognito or if the profile is already
974 // connected to a Google account.
975 if (!session_index.empty())
976 helper->session_index_ = session_index;
979 helper->email_ = email;
981 CanOfferFor can_offer_for =
982 (auto_accept != AUTO_ACCEPT_EXPLICIT &&
983 helper->auto_accept_ != AUTO_ACCEPT_EXPLICIT) ?
984 CAN_OFFER_FOR_INTERSTITAL_ONLY : CAN_OFFER_FOR_ALL;
986 std::string error_message;
988 if (!web_contents || !CanOffer(web_contents, can_offer_for, email,
990 VLOG(1) << "OneClickSigninHelper::ShowInfoBarUIThread: not offering";
991 // TODO(rogerta): Can we just display our error now instead of keeping it
992 // around and doing it later?
993 if (helper && helper->error_message_.empty() && !error_message.empty())
994 helper->error_message_ = error_message;
999 // Only allow the dedicated signin process to sign the user into
1000 // Chrome without intervention, because it doesn't load any untrusted
1001 // pages. If at any point an untrusted page is detected, chrome will
1002 // show a modal dialog asking the user to confirm.
1004 Profile::FromBrowserContext(web_contents->GetBrowserContext());
1005 SigninManager* manager = profile ?
1006 SigninManagerFactory::GetForProfile(profile) : NULL;
1007 helper->untrusted_confirmation_required_ |=
1008 (manager && !manager->IsSigninProcess(child_id));
1010 if (continue_url.is_valid()) {
1011 // Set |original_continue_url_| if it is currently empty. |continue_url|
1012 // could be modified by gaia pages, thus we need to record the original
1013 // continue url to navigate back to the right page when sync setup is
1015 if (helper->original_continue_url_.is_empty())
1016 helper->original_continue_url_ = continue_url;
1017 helper->continue_url_ = continue_url;
1022 void OneClickSigninHelper::RemoveSigninRedirectURLHistoryItem(
1023 content::WebContents* web_contents) {
1024 // Only actually remove the item if it's the blank.html continue url.
1025 if (signin::IsContinueUrlForWebBasedSigninFlow(
1026 web_contents->GetLastCommittedURL())) {
1027 new CurrentHistoryCleaner(web_contents); // will self-destruct when done
1031 void OneClickSigninHelper::ShowSigninErrorBubble(Browser* browser,
1032 const std::string& error) {
1033 DCHECK(!error.empty());
1035 browser->window()->ShowOneClickSigninBubble(
1036 BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE,
1037 string16(), /* no SAML email */
1039 // This callback is never invoked.
1040 // TODO(rogerta): Separate out the bubble API so we don't have to pass
1041 // ignored |email| and |callback| params.
1042 BrowserWindow::StartSyncCallback());
1045 void OneClickSigninHelper::RedirectToSignin() {
1046 VLOG(1) << "OneClickSigninHelper::RedirectToSignin";
1048 // Extract the existing sounce=X value. Default to "2" if missing.
1049 signin::Source source = signin::GetSourceForPromoURL(continue_url_);
1050 if (source == signin::SOURCE_UNKNOWN)
1051 source = signin::SOURCE_MENU;
1052 GURL page = signin::GetPromoURL(source, false);
1054 content::WebContents* contents = web_contents();
1055 contents->GetController().LoadURL(page,
1056 content::Referrer(),
1057 content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1061 void OneClickSigninHelper::CleanTransientState() {
1062 VLOG(1) << "OneClickSigninHelper::CleanTransientState";
1063 showing_signin_ = false;
1066 auto_accept_ = AUTO_ACCEPT_NONE;
1067 source_ = signin::SOURCE_UNKNOWN;
1068 switched_to_advanced_ = false;
1069 continue_url_ = GURL();
1070 untrusted_navigations_since_signin_visit_ = 0;
1071 untrusted_confirmation_required_ = false;
1072 error_message_.clear();
1074 // Post to IO thread to clear pending email.
1075 if (!do_not_clear_pending_email_) {
1077 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
1078 content::BrowserThread::PostTask(
1079 content::BrowserThread::IO, FROM_HERE,
1080 base::Bind(&ClearPendingEmailOnIOThread,
1081 base::Unretained(profile->GetResourceContext())));
1085 void OneClickSigninHelper::PasswordSubmitted(
1086 const autofill::PasswordForm& form) {
1087 // We only need to scrape the password for Gaia logins.
1088 if (gaia::IsGaiaSignonRealm(GURL(form.signon_realm))) {
1089 VLOG(1) << "OneClickSigninHelper::DidNavigateAnyFrame: got password";
1090 password_ = UTF16ToUTF8(form.password_value);
1094 void OneClickSigninHelper::SetDoNotClearPendingEmailForTesting() {
1095 do_not_clear_pending_email_ = true;
1098 void OneClickSigninHelper::set_do_not_start_sync_for_testing() {
1099 do_not_start_sync_for_testing_ = true;
1102 void OneClickSigninHelper::NavigateToPendingEntry(
1104 content::NavigationController::ReloadType reload_type) {
1105 VLOG(1) << "OneClickSigninHelper::NavigateToPendingEntry: url=" << url.spec();
1106 // If the tab navigates to a new page, and this page is not a valid Gaia
1107 // sign in redirect or reponse, or the expected continue URL, make sure to
1108 // clear the internal state. This is needed to detect navigations in the
1109 // middle of the sign in process that may redirect back to the sign in
1110 // process (see crbug.com/181163 for details).
1111 const GURL continue_url = signin::GetNextPageURLForPromoURL(
1112 signin::GetPromoURL(signin::SOURCE_START_PAGE, false));
1113 GURL::Replacements replacements;
1114 replacements.ClearQuery();
1116 if (!IsValidGaiaSigninRedirectOrResponseURL(url) &&
1117 continue_url_.is_valid() &&
1118 url.ReplaceComponents(replacements) !=
1119 continue_url_.ReplaceComponents(replacements)) {
1120 if (++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince)
1121 CleanTransientState();
1125 void OneClickSigninHelper::DidNavigateMainFrame(
1126 const content::LoadCommittedDetails& details,
1127 const content::FrameNavigateParams& params) {
1128 if (!SigninManager::IsWebBasedSigninFlowURL(params.url)) {
1129 // Make sure the renderer process is no longer considered the trusted
1130 // sign-in process when a navigation to a non-sign-in URL occurs.
1132 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
1133 SigninManager* manager = profile ?
1134 SigninManagerFactory::GetForProfile(profile) : NULL;
1135 int process_id = web_contents()->GetRenderProcessHost()->GetID();
1136 if (manager && manager->IsSigninProcess(process_id))
1137 manager->ClearSigninProcess();
1139 // If the navigation to a non-sign-in URL hasn't been triggered by the web
1140 // contents, the sign in flow has been aborted and the state must be
1141 // cleaned (crbug.com/269421).
1142 if (!content::PageTransitionIsWebTriggerable(params.transition) &&
1143 auto_accept_ != AUTO_ACCEPT_NONE) {
1144 CleanTransientState();
1149 void OneClickSigninHelper::DidStopLoading(
1150 content::RenderViewHost* render_view_host) {
1151 // If the user left the sign in process, clear all members.
1152 // TODO(rogerta): might need to allow some youtube URLs.
1153 content::WebContents* contents = web_contents();
1154 const GURL url = contents->GetLastCommittedURL();
1156 Profile::FromBrowserContext(contents->GetBrowserContext());
1157 VLOG(1) << "OneClickSigninHelper::DidStopLoading: url=" << url.spec();
1159 // If an error has already occured during the sign in flow, make sure to
1160 // display it to the user and abort the process. Do this only for
1161 // explicit sign ins.
1162 // TODO(rogerta): Could we move this code back up to ShowInfoBarUIThread()?
1163 if (!error_message_.empty() && auto_accept_ == AUTO_ACCEPT_EXPLICIT) {
1164 VLOG(1) << "OneClickSigninHelper::DidStopLoading: error=" << error_message_;
1165 RemoveSigninRedirectURLHistoryItem(contents);
1166 // After we redirect to NTP, our browser pointer gets corrupted because the
1167 // WebContents have changed, so grab the browser pointer
1168 // before the navigation.
1169 Browser* browser = chrome::FindBrowserWithWebContents(contents);
1171 // Redirect to the landing page and display an error popup.
1172 RedirectToNtpOrAppsPage(web_contents(), source_);
1173 ShowSigninErrorBubble(browser, error_message_);
1174 CleanTransientState();
1178 if (AreWeShowingSignin(url, source_, email_)) {
1179 if (!showing_signin_) {
1180 if (source_ == signin::SOURCE_UNKNOWN)
1181 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_SHOWN);
1183 LogHistogramValue(source_, one_click_signin::HISTOGRAM_SHOWN);
1185 showing_signin_ = true;
1188 // When Gaia finally redirects to the continue URL, Gaia will add some
1189 // extra query parameters. So ignore the parameters when checking to see
1190 // if the user has continued.
1191 GURL::Replacements replacements;
1192 replacements.ClearQuery();
1193 const bool continue_url_match = (
1194 continue_url_.is_valid() &&
1195 url.ReplaceComponents(replacements) ==
1196 continue_url_.ReplaceComponents(replacements));
1198 if (continue_url_match)
1199 RemoveSigninRedirectURLHistoryItem(contents);
1201 // If there is no valid email yet, there is nothing to do. As of M26, the
1202 // password is allowed to be empty, since its no longer required to setup
1204 if (email_.empty()) {
1205 VLOG(1) << "OneClickSigninHelper::DidStopLoading: nothing to do";
1206 if (continue_url_match) {
1207 if (auto_accept_ == AUTO_ACCEPT_EXPLICIT)
1209 std::string unused_value;
1210 if (net::GetValueForKeyInQuery(url, "ntp", &unused_value)) {
1211 signin::SetUserSkippedPromo(profile);
1212 RedirectToNtpOrAppsPage(web_contents(), source_);
1215 if (!IsValidGaiaSigninRedirectOrResponseURL(url) &&
1216 ++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince) {
1217 CleanTransientState();
1224 if (!continue_url_match && IsValidGaiaSigninRedirectOrResponseURL(url))
1227 // During an explicit sign in, if the user has not yet reached the final
1228 // continue URL, wait for it to arrive. Note that Gaia will add some extra
1229 // query parameters to the continue URL. Ignore them when checking to
1230 // see if the user has continued.
1232 // If this is not an explicit sign in, we don't need to check if we landed
1233 // on the right continue URL. This is important because the continue URL
1234 // may itself lead to a redirect, which means this function will never see
1235 // the continue URL go by.
1236 if (auto_accept_ == AUTO_ACCEPT_EXPLICIT) {
1237 DCHECK(source_ != signin::SOURCE_UNKNOWN);
1238 if (!continue_url_match) {
1239 VLOG(1) << "OneClickSigninHelper::DidStopLoading: invalid url='"
1241 << "' expected continue url=" << continue_url_;
1242 CleanTransientState();
1246 // In explicit sign ins, the user may have changed the box
1247 // "Let me choose what to sync". This is reflected as a change in the
1248 // source of the continue URL. Make one last check of the current URL
1249 // to see if there is a valid source. If so, it overrides the
1252 // If the source was changed to SOURCE_SETTINGS, we want
1253 // OneClickSigninSyncStarter to reuse the current tab to display the
1254 // advanced configuration.
1255 signin::Source source = signin::GetSourceForPromoURL(url);
1256 if (source != source_) {
1258 switched_to_advanced_ = source == signin::SOURCE_SETTINGS;
1262 Browser* browser = chrome::FindBrowserWithWebContents(contents);
1264 VLOG(1) << "OneClickSigninHelper::DidStopLoading: signin is go."
1265 << " auto_accept=" << auto_accept_
1266 << " source=" << source_;
1268 switch (auto_accept_) {
1269 case AUTO_ACCEPT_NONE:
1270 if (showing_signin_)
1271 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_DISMISSED);
1273 case AUTO_ACCEPT_ACCEPTED:
1274 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED);
1275 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_DEFAULTS);
1276 SigninManager::DisableOneClickSignIn(profile);
1277 // Start syncing with the default settings - prompt the user to sign in
1279 if (!do_not_start_sync_for_testing_) {
1281 StartSyncArgs(profile, browser, auto_accept_,
1282 session_index_, email_, password_,
1283 NULL /* don't force to show sync setup in same tab */,
1284 true /* confirmation_required */, source_,
1285 CreateSyncStarterCallback()),
1286 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS);
1289 case AUTO_ACCEPT_CONFIGURE:
1290 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED);
1291 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_ADVANCED);
1292 SigninManager::DisableOneClickSignIn(profile);
1293 // Display the extra confirmation (even in the SAML case) in case this
1294 // was an untrusted renderer.
1295 if (!do_not_start_sync_for_testing_) {
1297 StartSyncArgs(profile, browser, auto_accept_,
1298 session_index_, email_, password_,
1299 NULL /* don't force sync setup in same tab */,
1300 true /* confirmation_required */, source_,
1301 CreateSyncStarterCallback()),
1302 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
1305 case AUTO_ACCEPT_EXPLICIT: {
1306 signin::Source original_source =
1307 signin::GetSourceForPromoURL(original_continue_url_);
1308 if (switched_to_advanced_) {
1309 LogHistogramValue(original_source,
1310 one_click_signin::HISTOGRAM_WITH_ADVANCED);
1311 LogHistogramValue(original_source,
1312 one_click_signin::HISTOGRAM_ACCEPTED);
1314 LogHistogramValue(source_, one_click_signin::HISTOGRAM_ACCEPTED);
1315 LogHistogramValue(source_, one_click_signin::HISTOGRAM_WITH_DEFAULTS);
1318 // - If sign in was initiated from the NTP or the hotdog menu, sync with
1319 // default settings.
1320 // - If sign in was initiated from the settings page for first time sync
1321 // set up, show the advanced sync settings dialog.
1322 // - If sign in was initiated from the settings page due to a re-auth when
1323 // sync was already setup, simply navigate back to the settings page.
1324 ProfileSyncService* sync_service =
1325 ProfileSyncServiceFactory::GetForProfile(profile);
1326 OneClickSigninSyncStarter::StartSyncMode start_mode =
1327 source_ == signin::SOURCE_SETTINGS ?
1328 (SigninGlobalError::GetForProfile(profile)->HasMenuItem() &&
1329 sync_service && sync_service->HasSyncSetupCompleted()) ?
1330 OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE :
1331 OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST :
1332 OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS;
1334 std::string last_email =
1335 profile->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername);
1337 if (!last_email.empty() && !gaia::AreEmailsSame(last_email, email_)) {
1338 // If the new email address is different from the email address that
1339 // just signed in, show a confirmation dialog.
1341 // No need to display a second confirmation so pass false below.
1342 // TODO(atwilson): Move this into OneClickSigninSyncStarter.
1343 // The tab modal dialog always executes its callback before |contents|
1345 ConfirmEmailDialogDelegate::AskForConfirmation(
1351 StartSyncArgs(profile, browser, auto_accept_,
1352 session_index_, email_, password_, contents,
1353 false /* confirmation_required */, source_,
1354 CreateSyncStarterCallback()),
1358 if (!do_not_start_sync_for_testing_) {
1360 StartSyncArgs(profile, browser, auto_accept_,
1361 session_index_, email_, password_, contents,
1362 untrusted_confirmation_required_, source_,
1363 CreateSyncStarterCallback()),
1367 // If this explicit sign in is not from settings page/webstore, show
1368 // the NTP/Apps page after sign in completes. In the case of the
1369 // settings page, it will get auto-closed after sync setup. In the case
1370 // of webstore, it will redirect back to webstore.
1371 RedirectToNtpOrAppsPageIfNecessary(web_contents(), source_);
1374 // Observe the sync service if the Webstore tab or the settings tab
1375 // requested a gaia sign in, so that when sign in and sync setup are
1376 // successful, we can redirect to the correct URL, or auto-close the gaia
1378 if (original_source == signin::SOURCE_SETTINGS ||
1379 (original_source == signin::SOURCE_WEBSTORE_INSTALL &&
1380 source_ == signin::SOURCE_SETTINGS)) {
1381 ProfileSyncService* sync_service =
1382 ProfileSyncServiceFactory::GetForProfile(profile);
1384 sync_service->AddObserver(this);
1388 case AUTO_ACCEPT_REJECTED_FOR_PROFILE:
1389 AddEmailToOneClickRejectedList(profile, email_);
1390 LogOneClickHistogramValue(one_click_signin::HISTOGRAM_REJECTED);
1393 NOTREACHED() << "Invalid auto_accept=" << auto_accept_;
1397 CleanTransientState();
1400 // It is guaranteed that this method is called before the object is deleted.
1401 void OneClickSigninHelper::WebContentsDestroyed(
1402 content::WebContents* contents) {
1404 Profile::FromBrowserContext(contents->GetBrowserContext());
1405 ProfileSyncService* sync_service =
1406 ProfileSyncServiceFactory::GetForProfile(profile);
1408 sync_service->RemoveObserver(this);
1411 void OneClickSigninHelper::OnStateChanged() {
1412 // We only add observer for ProfileSyncService when original_continue_url_ is
1414 DCHECK(!original_continue_url_.is_empty());
1416 content::WebContents* contents = web_contents();
1418 Profile::FromBrowserContext(contents->GetBrowserContext());
1419 ProfileSyncService* sync_service =
1420 ProfileSyncServiceFactory::GetForProfile(profile);
1422 // At this point, the sign in process is complete, and control has been handed
1423 // back to the sync engine. Close the gaia sign in tab if
1424 // |original_continue_url_| contains the |auto_close| parameter. Otherwise,
1425 // wait for sync setup to complete and then navigate to
1426 // |original_continue_url_|.
1427 if (signin::IsAutoCloseEnabledInURL(original_continue_url_)) {
1428 // Close the gaia sign in tab via a task to make sure we aren't in the
1429 // middle of any webui handler code.
1430 base::MessageLoop::current()->PostTask(
1432 base::Bind(&CloseTab, base::Unretained(contents)));
1434 // Sync setup not completed yet.
1435 if (sync_service->FirstSetupInProgress())
1438 if (sync_service->sync_initialized() &&
1439 signin::GetSourceForPromoURL(original_continue_url_)
1440 != signin::SOURCE_SETTINGS) {
1441 contents->GetController().LoadURL(original_continue_url_,
1442 content::Referrer(),
1443 content::PAGE_TRANSITION_AUTO_TOPLEVEL,
1448 // Clears |original_continue_url_| here instead of in CleanTransientState,
1449 // because it is used in OnStateChanged which occurs later.
1450 original_continue_url_ = GURL();
1451 sync_service->RemoveObserver(this);
1454 OneClickSigninSyncStarter::Callback
1455 OneClickSigninHelper::CreateSyncStarterCallback() {
1456 // The callback will only be invoked if this object is still alive when sync
1457 // setup is completed. This is correct because this object is only deleted
1458 // when the web contents that potentially shows a blank page is deleted.
1459 return base::Bind(&OneClickSigninHelper::SyncSetupCompletedCallback,
1460 weak_pointer_factory_.GetWeakPtr());
1463 void OneClickSigninHelper::SyncSetupCompletedCallback(
1464 OneClickSigninSyncStarter::SyncSetupResult result) {
1465 if (result == OneClickSigninSyncStarter::SYNC_SETUP_FAILURE &&
1467 GURL current_url = web_contents()->GetVisibleURL();
1469 // If the web contents is showing a blank page and not about to be closed,
1470 // redirect to the NTP or apps page.
1471 if (signin::IsContinueUrlForWebBasedSigninFlow(current_url) &&
1472 !signin::IsAutoCloseEnabledInURL(original_continue_url_)) {
1473 RedirectToNtpOrAppsPage(
1475 signin::GetSourceForPromoURL(original_continue_url_));