// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/sync/one_click_signin_helper.h" #include #include #include #include #include "base/bind.h" #include "base/callback_forward.h" #include "base/callback_helpers.h" #include "base/compiler_specific.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop_proxy.h" #include "base/metrics/field_trial.h" #include "base/metrics/histogram.h" #include "base/prefs/pref_service.h" #include "base/prefs/scoped_user_pref_update.h" #include "base/strings/string_split.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/supports_user_data.h" #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/defaults.h" #include "chrome/browser/google/google_util.h" #include "chrome/browser/history/history_service.h" #include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/password_manager/password_manager.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_info_cache.h" #include "chrome/browser/profiles/profile_io_data.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/search/search.h" #include "chrome/browser/signin/chrome_signin_manager_delegate.h" #include "chrome/browser/signin/signin_global_error.h" #include "chrome/browser/signin/signin_manager.h" #include "chrome/browser/signin/signin_manager_delegate.h" #include "chrome/browser/signin/signin_manager_factory.h" #include "chrome/browser/signin/signin_names_io_thread.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_service_factory.h" #include "chrome/browser/sync/sync_prefs.h" #include "chrome/browser/tab_contents/tab_util.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/chrome_pages.h" #include "chrome/browser/ui/sync/one_click_signin_histogram.h" #include "chrome/browser/ui/sync/one_click_signin_sync_starter.h" #include "chrome/browser/ui/sync/signin_histogram.h" #include "chrome/browser/ui/tab_modal_confirm_dialog.h" #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/chrome_version_info.h" #include "chrome/common/net/url_util.h" #include "chrome/common/pref_names.h" #include "chrome/common/profile_management_switches.h" #include "chrome/common/url_constants.h" #include "components/autofill/core/common/password_form.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/page_navigator.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_view.h" #include "content/public/common/frame_navigate_params.h" #include "content/public/common/page_transition_types.h" #include "google_apis/gaia/gaia_auth_util.h" #include "google_apis/gaia/gaia_urls.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" #include "grit/theme_resources.h" #include "ipc/ipc_message_macros.h" #include "net/base/url_util.h" #include "net/cookies/cookie_monster.h" #include "net/url_request/url_request.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/resource/resource_bundle.h" #include "url/gurl.h" namespace { // StartSyncArgs -------------------------------------------------------------- // Arguments used with StartSync function. base::Bind() cannot support too // many args for performance reasons, so they are packaged up into a struct. struct StartSyncArgs { StartSyncArgs(Profile* profile, Browser* browser, OneClickSigninHelper::AutoAccept auto_accept, const std::string& session_index, const std::string& email, const std::string& password, const std::string& oauth_code, content::WebContents* web_contents, bool untrusted_confirmation_required, signin::Source source, OneClickSigninSyncStarter::Callback callback); Profile* profile; Browser* browser; OneClickSigninHelper::AutoAccept auto_accept; std::string session_index; std::string email; std::string password; std::string oauth_code; // Web contents in which the sync setup page should be displayed, // if necessary. Can be NULL. content::WebContents* web_contents; OneClickSigninSyncStarter::ConfirmationRequired confirmation_required; signin::Source source; OneClickSigninSyncStarter::Callback callback; }; StartSyncArgs::StartSyncArgs(Profile* profile, Browser* browser, OneClickSigninHelper::AutoAccept auto_accept, const std::string& session_index, const std::string& email, const std::string& password, const std::string& oauth_code, content::WebContents* web_contents, bool untrusted_confirmation_required, signin::Source source, OneClickSigninSyncStarter::Callback callback) : profile(profile), browser(browser), auto_accept(auto_accept), session_index(session_index), email(email), password(password), oauth_code(oauth_code), web_contents(web_contents), source(source), callback(callback) { if (untrusted_confirmation_required) { confirmation_required = OneClickSigninSyncStarter::CONFIRM_UNTRUSTED_SIGNIN; } else if (source == signin::SOURCE_SETTINGS || source == signin::SOURCE_WEBSTORE_INSTALL) { // Do not display a status confirmation for webstore installs or re-auth. confirmation_required = OneClickSigninSyncStarter::NO_CONFIRMATION; } else { confirmation_required = OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN; } } // ConfirmEmailDialogDelegate ------------------------------------------------- class ConfirmEmailDialogDelegate : public TabModalConfirmDialogDelegate { public: enum Action { CREATE_NEW_USER, START_SYNC, CLOSE }; // Callback indicating action performed by the user. typedef base::Callback Callback; // Ask the user for confirmation before starting to sync. static void AskForConfirmation(content::WebContents* contents, const std::string& last_email, const std::string& email, Callback callback); private: ConfirmEmailDialogDelegate(content::WebContents* contents, const std::string& last_email, const std::string& email, Callback callback); virtual ~ConfirmEmailDialogDelegate(); // TabModalConfirmDialogDelegate: virtual base::string16 GetTitle() OVERRIDE; virtual base::string16 GetMessage() OVERRIDE; virtual base::string16 GetAcceptButtonTitle() OVERRIDE; virtual base::string16 GetCancelButtonTitle() OVERRIDE; virtual base::string16 GetLinkText() const OVERRIDE; virtual void OnAccepted() OVERRIDE; virtual void OnCanceled() OVERRIDE; virtual void OnClosed() OVERRIDE; virtual void OnLinkClicked(WindowOpenDisposition disposition) OVERRIDE; std::string last_email_; std::string email_; Callback callback_; // Web contents from which the "Learn more" link should be opened. content::WebContents* web_contents_; DISALLOW_COPY_AND_ASSIGN(ConfirmEmailDialogDelegate); }; // static void ConfirmEmailDialogDelegate::AskForConfirmation( content::WebContents* contents, const std::string& last_email, const std::string& email, Callback callback) { TabModalConfirmDialog::Create( new ConfirmEmailDialogDelegate(contents, last_email, email, callback), contents); } ConfirmEmailDialogDelegate::ConfirmEmailDialogDelegate( content::WebContents* contents, const std::string& last_email, const std::string& email, Callback callback) : TabModalConfirmDialogDelegate(contents), last_email_(last_email), email_(email), callback_(callback), web_contents_(contents) { } ConfirmEmailDialogDelegate::~ConfirmEmailDialogDelegate() { } base::string16 ConfirmEmailDialogDelegate::GetTitle() { return l10n_util::GetStringUTF16( IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_TITLE); } base::string16 ConfirmEmailDialogDelegate::GetMessage() { return l10n_util::GetStringFUTF16( IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_MESSAGE, UTF8ToUTF16(last_email_), UTF8ToUTF16(email_)); } base::string16 ConfirmEmailDialogDelegate::GetAcceptButtonTitle() { return l10n_util::GetStringUTF16( IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_OK_BUTTON); } base::string16 ConfirmEmailDialogDelegate::GetCancelButtonTitle() { return l10n_util::GetStringUTF16( IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_CANCEL_BUTTON); } base::string16 ConfirmEmailDialogDelegate::GetLinkText() const { return l10n_util::GetStringUTF16(IDS_LEARN_MORE); } void ConfirmEmailDialogDelegate::OnAccepted() { base::ResetAndReturn(&callback_).Run(CREATE_NEW_USER); } void ConfirmEmailDialogDelegate::OnCanceled() { base::ResetAndReturn(&callback_).Run(START_SYNC); } void ConfirmEmailDialogDelegate::OnClosed() { base::ResetAndReturn(&callback_).Run(CLOSE); } void ConfirmEmailDialogDelegate::OnLinkClicked( WindowOpenDisposition disposition) { content::OpenURLParams params( GURL(chrome::kChromeSyncMergeTroubleshootingURL), content::Referrer(), NEW_POPUP, content::PAGE_TRANSITION_AUTO_TOPLEVEL, false); // It is guaranteed that |web_contents_| is valid here because when it's // deleted, the dialog is immediately closed and no further action can be // performed. web_contents_->OpenURL(params); } // Helpers -------------------------------------------------------------------- // Add a specific email to the list of emails rejected for one-click // sign-in, for this profile. void AddEmailToOneClickRejectedList(Profile* profile, const std::string& email) { ListPrefUpdate updater(profile->GetPrefs(), prefs::kReverseAutologinRejectedEmailList); updater->AppendIfNotPresent(new base::StringValue(email)); } void LogHistogramValue(signin::Source source, int action) { switch (source) { case signin::SOURCE_START_PAGE: UMA_HISTOGRAM_ENUMERATION("Signin.StartPageActions", action, one_click_signin::HISTOGRAM_MAX); break; case signin::SOURCE_NTP_LINK: UMA_HISTOGRAM_ENUMERATION("Signin.NTPLinkActions", action, one_click_signin::HISTOGRAM_MAX); break; case signin::SOURCE_MENU: UMA_HISTOGRAM_ENUMERATION("Signin.MenuActions", action, one_click_signin::HISTOGRAM_MAX); break; case signin::SOURCE_SETTINGS: UMA_HISTOGRAM_ENUMERATION("Signin.SettingsActions", action, one_click_signin::HISTOGRAM_MAX); break; case signin::SOURCE_EXTENSION_INSTALL_BUBBLE: UMA_HISTOGRAM_ENUMERATION("Signin.ExtensionInstallBubbleActions", action, one_click_signin::HISTOGRAM_MAX); break; case signin::SOURCE_WEBSTORE_INSTALL: UMA_HISTOGRAM_ENUMERATION("Signin.WebstoreInstallActions", action, one_click_signin::HISTOGRAM_MAX); break; case signin::SOURCE_APP_LAUNCHER: UMA_HISTOGRAM_ENUMERATION("Signin.AppLauncherActions", action, one_click_signin::HISTOGRAM_MAX); break; case signin::SOURCE_APPS_PAGE_LINK: UMA_HISTOGRAM_ENUMERATION("Signin.AppsPageLinkActions", action, one_click_signin::HISTOGRAM_MAX); break; case signin::SOURCE_BOOKMARK_BUBBLE: UMA_HISTOGRAM_ENUMERATION("Signin.BookmarkBubbleActions", action, one_click_signin::HISTOGRAM_MAX); break; default: // This switch statement needs to be updated when the enum Source changes. COMPILE_ASSERT(signin::SOURCE_UNKNOWN == 11, kSourceEnumHasChangedButNotThisSwitchStatement); NOTREACHED(); return; } UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action, one_click_signin::HISTOGRAM_MAX); } void LogOneClickHistogramValue(int action) { UMA_HISTOGRAM_ENUMERATION("Signin.OneClickActions", action, one_click_signin::HISTOGRAM_MAX); UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action, one_click_signin::HISTOGRAM_MAX); } void RedirectToNtpOrAppsPage(content::WebContents* contents, signin::Source source) { VLOG(1) << "RedirectToNtpOrAppsPage"; // Redirect to NTP/Apps page and display a confirmation bubble GURL url(source == signin::SOURCE_APPS_PAGE_LINK ? chrome::kChromeUIAppsURL : chrome::kChromeUINewTabURL); content::OpenURLParams params(url, content::Referrer(), CURRENT_TAB, content::PAGE_TRANSITION_AUTO_TOPLEVEL, false); contents->OpenURL(params); } void RedirectToNtpOrAppsPageWithIds(int child_id, int route_id, signin::Source source) { content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id, route_id); if (!web_contents) return; RedirectToNtpOrAppsPage(web_contents, source); } // Start syncing with the given user information. void StartSync(const StartSyncArgs& args, OneClickSigninSyncStarter::StartSyncMode start_mode) { if (start_mode == OneClickSigninSyncStarter::UNDO_SYNC) { LogOneClickHistogramValue(one_click_signin::HISTOGRAM_UNDO); return; } // The starter deletes itself once its done. new OneClickSigninSyncStarter(args.profile, args.browser, args.session_index, args.email, args.password, args.oauth_code, start_mode, args.web_contents, args.confirmation_required, args.callback); int action = one_click_signin::HISTOGRAM_MAX; switch (args.auto_accept) { case OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT: break; case OneClickSigninHelper::AUTO_ACCEPT_ACCEPTED: action = start_mode == OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS ? one_click_signin::HISTOGRAM_AUTO_WITH_DEFAULTS : one_click_signin::HISTOGRAM_AUTO_WITH_ADVANCED; break; case OneClickSigninHelper::AUTO_ACCEPT_CONFIGURE: DCHECK(start_mode == OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST); action = one_click_signin::HISTOGRAM_AUTO_WITH_ADVANCED; break; default: NOTREACHED() << "Invalid auto_accept: " << args.auto_accept; break; } if (action != one_click_signin::HISTOGRAM_MAX) LogOneClickHistogramValue(action); } void StartExplicitSync(const StartSyncArgs& args, content::WebContents* contents, OneClickSigninSyncStarter::StartSyncMode start_mode, ConfirmEmailDialogDelegate::Action action) { bool enable_inline = switches::IsEnableInlineSignin(); if (action == ConfirmEmailDialogDelegate::START_SYNC) { StartSync(args, start_mode); if (!enable_inline) { // Redirect/tab closing for inline flow is handled by the sync callback. OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary( contents, args.source); } } else { // Perform a redirection to the NTP/Apps page to hide the blank page when // the action is CLOSE or CREATE_NEW_USER. The redirection is useful when // the action is CREATE_NEW_USER because the "Create new user" page might // be opened in a different tab that is already showing settings. if (enable_inline) { // Redirect/tab closing for inline flow is handled by the sync callback. args.callback.Run(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE); } else { // Don't redirect when the visible URL is not a blank page: if the // source is SOURCE_WEBSTORE_INSTALL, |contents| might be showing an app // page that shouldn't be hidden. // // If redirecting, don't do so immediately, otherwise there may be 2 // nested navigations and a crash would occur (crbug.com/293261). Post // the task to the current thread instead. if (signin::IsContinueUrlForWebBasedSigninFlow( contents->GetVisibleURL())) { base::MessageLoopProxy::current()->PostNonNestableTask( FROM_HERE, base::Bind(RedirectToNtpOrAppsPageWithIds, contents->GetRenderProcessHost()->GetID(), contents->GetRoutingID(), args.source)); } } if (action == ConfirmEmailDialogDelegate::CREATE_NEW_USER) { chrome::ShowSettingsSubPage(args.browser, std::string(chrome::kSearchUsersSubPage)); } } } void ClearPendingEmailOnIOThread(content::ResourceContext* context) { ProfileIOData* io_data = ProfileIOData::FromResourceContext(context); DCHECK(io_data); io_data->set_reverse_autologin_pending_email(std::string()); } // Determines the source of the sign in and the continue URL. Its either one // of the known sign in access point (first run, NTP, Apps page, menu, settings) // or its an implicit sign in via another Google property. In the former case, // "service" is also checked to make sure its "chromiumsync". signin::Source GetSigninSource(const GURL& url, GURL* continue_url) { DCHECK(url.is_valid()); std::string value; net::GetValueForKeyInQuery(url, "service", &value); bool possibly_an_explicit_signin = value == "chromiumsync"; // Find the final continue URL for this sign in. In some cases, Gaia can // continue to itself, with the original continue URL buried under a couple // of layers of indirection. Peel those layers away. The final destination // can also be "IsGaiaSignonRealm" so stop if we get to the end (but be sure // we always extract at least one "continue" value). GURL local_continue_url = signin::GetNextPageURLForPromoURL(url); while (gaia::IsGaiaSignonRealm(local_continue_url.GetOrigin())) { GURL next_continue_url = signin::GetNextPageURLForPromoURL(local_continue_url); if (!next_continue_url.is_valid()) break; local_continue_url = next_continue_url; } if (continue_url && local_continue_url.is_valid()) { DCHECK(!continue_url->is_valid() || *continue_url == local_continue_url); *continue_url = local_continue_url; } return possibly_an_explicit_signin ? signin::GetSourceForPromoURL(local_continue_url) : signin::SOURCE_UNKNOWN; } // Returns true if |url| is a valid URL that can occur during the sign in // process. Valid URLs are of the form: // // https://accounts.google.{TLD}/... // https://accounts.youtube.com/... // https://accounts.blogger.com/... // // All special headers used by one click sign in occur on // https://accounts.google.com URLs. However, the sign in process may redirect // to intermediate Gaia URLs that do not end with .com. For example, an account // that uses SMS 2-factor outside the US may redirect to country specific URLs. // // The sign in process may also redirect to youtube and blogger account URLs // so that Gaia acts as a single signon service. bool IsValidGaiaSigninRedirectOrResponseURL(const GURL& url) { std::string hostname = url.host(); if (google_util::IsGoogleHostname(hostname, google_util::ALLOW_SUBDOMAIN)) { // Also using IsGaiaSignonRealm() to handle overriding with command line. return gaia::IsGaiaSignonRealm(url.GetOrigin()) || StartsWithASCII(hostname, "accounts.", false); } GURL origin = url.GetOrigin(); if (origin == GURL("https://accounts.youtube.com") || origin == GURL("https://accounts.blogger.com")) return true; return false; } // Tells when we are in the process of showing either the signin to chrome page // or the one click sign in to chrome page. // NOTE: This should only be used for logging purposes since it relies on hard // coded URLs that could change. bool AreWeShowingSignin(GURL url, signin::Source source, std::string email) { GURL::Replacements replacements; replacements.ClearQuery(); GURL clean_login_url = GaiaUrls::GetInstance()->service_login_url().ReplaceComponents( replacements); return (url.ReplaceComponents(replacements) == clean_login_url && source != signin::SOURCE_UNKNOWN) || (IsValidGaiaSigninRedirectOrResponseURL(url) && url.spec().find("ChromeLoginPrompt") != std::string::npos && !email.empty()); } // CurrentHistoryCleaner ------------------------------------------------------ // Watch a webcontents and remove URL from the history once loading is complete. // We have to delay the cleaning until the new URL has finished loading because // we're not allowed to remove the last-loaded URL from the history. Objects // of this type automatically self-destruct once they're finished their work. class CurrentHistoryCleaner : public content::WebContentsObserver { public: explicit CurrentHistoryCleaner(content::WebContents* contents); virtual ~CurrentHistoryCleaner(); // content::WebContentsObserver: virtual void WebContentsDestroyed(content::WebContents* contents) OVERRIDE; virtual void DidCommitProvisionalLoadForFrame( int64 frame_id, const base::string16& frame_unique_name, bool is_main_frame, const GURL& url, content::PageTransition transition_type, content::RenderViewHost* render_view_host) OVERRIDE; private: scoped_ptr contents_; int history_index_to_remove_; DISALLOW_COPY_AND_ASSIGN(CurrentHistoryCleaner); }; CurrentHistoryCleaner::CurrentHistoryCleaner(content::WebContents* contents) : WebContentsObserver(contents) { history_index_to_remove_ = web_contents()->GetController().GetLastCommittedEntryIndex(); } CurrentHistoryCleaner::~CurrentHistoryCleaner() { } void CurrentHistoryCleaner::DidCommitProvisionalLoadForFrame( int64 frame_id, const base::string16& frame_unique_name, bool is_main_frame, const GURL& url, content::PageTransition transition_type, content::RenderViewHost* render_view_host) { // Return early if this is not top-level navigation. if (!is_main_frame) return; content::NavigationController* nc = &web_contents()->GetController(); HistoryService* hs = HistoryServiceFactory::GetForProfile( Profile::FromBrowserContext(web_contents()->GetBrowserContext()), Profile::IMPLICIT_ACCESS); // Have to wait until something else gets added to history before removal. if (history_index_to_remove_ < nc->GetLastCommittedEntryIndex()) { content::NavigationEntry* entry = nc->GetEntryAtIndex(history_index_to_remove_); if (signin::IsContinueUrlForWebBasedSigninFlow(entry->GetURL())) { hs->DeleteURL(entry->GetURL()); nc->RemoveEntryAtIndex(history_index_to_remove_); delete this; // Success. } } } void CurrentHistoryCleaner::WebContentsDestroyed( content::WebContents* contents) { delete this; // Failure. } void CloseTab(content::WebContents* tab) { Browser* browser = chrome::FindBrowserWithWebContents(tab); if (browser) { TabStripModel* tab_strip_model = browser->tab_strip_model(); if (tab_strip_model) { int index = tab_strip_model->GetIndexOfWebContents(tab); if (index != TabStripModel::kNoTab) { tab_strip_model->ExecuteContextMenuCommand( index, TabStripModel::CommandCloseTab); } } } } } // namespace // OneClickSigninHelper ------------------------------------------------------- DEFINE_WEB_CONTENTS_USER_DATA_KEY(OneClickSigninHelper); // static const int OneClickSigninHelper::kMaxNavigationsSince = 10; OneClickSigninHelper::OneClickSigninHelper(content::WebContents* web_contents, PasswordManager* password_manager) : content::WebContentsObserver(web_contents), showing_signin_(false), auto_accept_(AUTO_ACCEPT_NONE), source_(signin::SOURCE_UNKNOWN), switched_to_advanced_(false), untrusted_navigations_since_signin_visit_(0), untrusted_confirmation_required_(false), do_not_clear_pending_email_(false), do_not_start_sync_for_testing_(false), weak_pointer_factory_(this) { // May be NULL during testing. if (password_manager) { password_manager->AddSubmissionCallback( base::Bind(&OneClickSigninHelper::PasswordSubmitted, weak_pointer_factory_.GetWeakPtr())); } } OneClickSigninHelper::~OneClickSigninHelper() { // WebContentsDestroyed() should always be called before the object is // deleted. DCHECK(!web_contents()); } // static void OneClickSigninHelper::CreateForWebContentsWithPasswordManager( content::WebContents* contents, PasswordManager* password_manager) { if (!FromWebContents(contents)) { contents->SetUserData(UserDataKey(), new OneClickSigninHelper(contents, password_manager)); } } // static bool OneClickSigninHelper::CanOffer(content::WebContents* web_contents, CanOfferFor can_offer_for, const std::string& email, std::string* error_message) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); VLOG(1) << "OneClickSigninHelper::CanOffer"; if (error_message) error_message->clear(); if (!web_contents) return false; if (web_contents->GetBrowserContext()->IsOffTheRecord()) return false; Profile* profile = Profile::FromBrowserContext(web_contents->GetBrowserContext()); if (!profile) return false; SigninManager* manager = SigninManagerFactory::GetForProfile(profile); if (manager && !manager->IsSigninAllowed()) return false; if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY && !profile->GetPrefs()->GetBoolean(prefs::kReverseAutologinEnabled)) return false; if (!ChromeSigninManagerDelegate::ProfileAllowsSigninCookies(profile)) return false; if (!email.empty()) { if (!manager) return false; // Make sure this username is not prohibited by policy. if (!manager->IsAllowedUsername(email)) { if (error_message) { error_message->assign( l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED)); } return false; } if (can_offer_for != CAN_OFFER_FOR_SECONDARY_ACCOUNT) { // If the signin manager already has an authenticated name, then this is a // re-auth scenario. Make sure the email just signed in corresponds to // the one sign in manager expects. std::string current_email = manager->GetAuthenticatedUsername(); const bool same_email = gaia::AreEmailsSame(current_email, email); if (!current_email.empty() && !same_email) { UMA_HISTOGRAM_ENUMERATION("Signin.Reauth", signin::HISTOGRAM_ACCOUNT_MISSMATCH, signin::HISTOGRAM_MAX); if (error_message) { error_message->assign( l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL, UTF8ToUTF16(current_email))); } return false; } // If some profile, not just the current one, is already connected to this // account, don't show the infobar. if (g_browser_process && !same_email) { ProfileManager* manager = g_browser_process->profile_manager(); if (manager) { base::string16 email16 = UTF8ToUTF16(email); ProfileInfoCache& cache = manager->GetProfileInfoCache(); for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) { if (email16 == cache.GetUserNameOfProfileAtIndex(i)) { if (error_message) { error_message->assign( l10n_util::GetStringUTF8(IDS_SYNC_USER_NAME_IN_USE_ERROR)); } return false; } } } } } // If email was already rejected by this profile for one-click sign-in. if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY) { const ListValue* rejected_emails = profile->GetPrefs()->GetList( prefs::kReverseAutologinRejectedEmailList); if (!rejected_emails->empty()) { base::ListValue::const_iterator iter = rejected_emails->Find( base::StringValue(email)); if (iter != rejected_emails->end()) return false; } } } VLOG(1) << "OneClickSigninHelper::CanOffer: yes we can"; return true; } // static OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThread( net::URLRequest* request, ProfileIOData* io_data) { return CanOfferOnIOThreadImpl(request->url(), request, io_data); } // static OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThreadImpl( const GURL& url, base::SupportsUserData* request, ProfileIOData* io_data) { if (!gaia::IsGaiaSignonRealm(url.GetOrigin())) return IGNORE_REQUEST; if (!io_data) return DONT_OFFER; // Check for incognito before other parts of the io_data, since those // members may not be initalized. if (io_data->is_incognito()) return DONT_OFFER; if (!SigninManager::IsSigninAllowedOnIOThread(io_data)) return DONT_OFFER; if (!io_data->reverse_autologin_enabled()->GetValue()) return DONT_OFFER; if (!io_data->google_services_username()->GetValue().empty()) return DONT_OFFER; if (!ChromeSigninManagerDelegate::SettingsAllowSigninCookies( io_data->GetCookieSettings())) return DONT_OFFER; // The checks below depend on chrome already knowing what account the user // signed in with. This happens only after receiving the response containing // the Google-Accounts-SignIn header. Until then, if there is even a chance // that we want to connect the profile, chrome needs to tell Gaia that // it should offer the interstitial. Therefore missing one click data on // the request means can offer is true. const std::string& pending_email = io_data->reverse_autologin_pending_email(); if (!pending_email.empty()) { if (!SigninManager::IsUsernameAllowedByPolicy(pending_email, io_data->google_services_username_pattern()->GetValue())) { return DONT_OFFER; } std::vector rejected_emails = io_data->one_click_signin_rejected_email_list()->GetValue(); if (std::count_if(rejected_emails.begin(), rejected_emails.end(), std::bind2nd(std::equal_to(), pending_email)) > 0) { return DONT_OFFER; } if (io_data->signin_names()->GetEmails().count( UTF8ToUTF16(pending_email)) > 0) { return DONT_OFFER; } } return CAN_OFFER; } // static void OneClickSigninHelper::ShowInfoBarIfPossible(net::URLRequest* request, ProfileIOData* io_data, int child_id, int route_id) { std::string google_chrome_signin_value; std::string google_accounts_signin_value; request->GetResponseHeaderByName("Google-Chrome-SignIn", &google_chrome_signin_value); request->GetResponseHeaderByName("Google-Accounts-SignIn", &google_accounts_signin_value); if (!google_accounts_signin_value.empty() || !google_chrome_signin_value.empty()) { VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:" << " g-a-s='" << google_accounts_signin_value << "'" << " g-c-s='" << google_chrome_signin_value << "'"; } if (!gaia::IsGaiaSignonRealm(request->url().GetOrigin())) return; // Parse Google-Accounts-SignIn. std::vector > pairs; base::SplitStringIntoKeyValuePairs(google_accounts_signin_value, '=', ',', &pairs); std::string session_index; std::string email; for (size_t i = 0; i < pairs.size(); ++i) { const std::pair& pair = pairs[i]; const std::string& key = pair.first; const std::string& value = pair.second; if (key == "email") { base::TrimString(value, "\"", &email); } else if (key == "sessionindex") { session_index = value; } } // Later in the chain of this request, we'll need to check the email address // in the IO thread (see CanOfferOnIOThread). So save the email address as // user data on the request (only for web-based flow). if (!email.empty()) io_data->set_reverse_autologin_pending_email(email); if (!email.empty() || !session_index.empty()) { VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:" << " email=" << email << " sessionindex=" << session_index; } // Parse Google-Chrome-SignIn. AutoAccept auto_accept = AUTO_ACCEPT_NONE; signin::Source source = signin::SOURCE_UNKNOWN; GURL continue_url; std::vector tokens; base::SplitString(google_chrome_signin_value, ',', &tokens); for (size_t i = 0; i < tokens.size(); ++i) { const std::string& token = tokens[i]; if (token == "accepted") { auto_accept = AUTO_ACCEPT_ACCEPTED; } else if (token == "configure") { auto_accept = AUTO_ACCEPT_CONFIGURE; } else if (token == "rejected-for-profile") { auto_accept = AUTO_ACCEPT_REJECTED_FOR_PROFILE; } } // If this is an explicit sign in (i.e., first run, NTP, Apps page, menu, // settings) then force the auto accept type to explicit. source = GetSigninSource(request->url(), &continue_url); if (source != signin::SOURCE_UNKNOWN) auto_accept = AUTO_ACCEPT_EXPLICIT; if (auto_accept != AUTO_ACCEPT_NONE) { VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:" << " auto_accept=" << auto_accept; } // If |session_index|, |email|, |auto_accept|, and |continue_url| all have // their default value, don't bother posting a task to the UI thread. // It will be a noop anyway. // // The two headers above may (but not always) come in different http requests // so a post to the UI thread is still needed if |auto_accept| is not its // default value, but |email| and |session_index| are. if (session_index.empty() && email.empty() && auto_accept == AUTO_ACCEPT_NONE && !continue_url.is_valid()) { return; } content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&OneClickSigninHelper::ShowInfoBarUIThread, session_index, email, auto_accept, source, continue_url, child_id, route_id)); } // static void OneClickSigninHelper::LogConfirmHistogramValue(int action) { UMA_HISTOGRAM_ENUMERATION("Signin.OneClickConfirmation", action, one_click_signin::HISTOGRAM_CONFIRM_MAX); } // static void OneClickSigninHelper::ShowInfoBarUIThread( const std::string& session_index, const std::string& email, AutoAccept auto_accept, signin::Source source, const GURL& continue_url, int child_id, int route_id) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id, route_id); if (!web_contents) return; // TODO(mathp): The appearance of this infobar should be tested using a // browser_test. OneClickSigninHelper* helper = OneClickSigninHelper::FromWebContents(web_contents); if (!helper) return; if (auto_accept != AUTO_ACCEPT_NONE) helper->auto_accept_ = auto_accept; if (source != signin::SOURCE_UNKNOWN && helper->source_ == signin::SOURCE_UNKNOWN) { helper->source_ = source; } // Save the email in the one-click signin manager. The manager may // not exist if the contents is incognito or if the profile is already // connected to a Google account. if (!session_index.empty()) helper->session_index_ = session_index; if (!email.empty()) helper->email_ = email; CanOfferFor can_offer_for = (auto_accept != AUTO_ACCEPT_EXPLICIT && helper->auto_accept_ != AUTO_ACCEPT_EXPLICIT) ? CAN_OFFER_FOR_INTERSTITAL_ONLY : CAN_OFFER_FOR_ALL; std::string error_message; if (!web_contents || !CanOffer(web_contents, can_offer_for, email, &error_message)) { VLOG(1) << "OneClickSigninHelper::ShowInfoBarUIThread: not offering"; // TODO(rogerta): Can we just display our error now instead of keeping it // around and doing it later? if (helper && helper->error_message_.empty() && !error_message.empty()) helper->error_message_ = error_message; return; } // Only allow the dedicated signin process to sign the user into // Chrome without intervention, because it doesn't load any untrusted // pages. If at any point an untrusted page is detected, chrome will // show a modal dialog asking the user to confirm. Profile* profile = Profile::FromBrowserContext(web_contents->GetBrowserContext()); SigninManager* manager = profile ? SigninManagerFactory::GetForProfile(profile) : NULL; helper->untrusted_confirmation_required_ |= (manager && !manager->IsSigninProcess(child_id)); if (continue_url.is_valid()) { // Set |original_continue_url_| if it is currently empty. |continue_url| // could be modified by gaia pages, thus we need to record the original // continue url to navigate back to the right page when sync setup is // complete. if (helper->original_continue_url_.is_empty()) helper->original_continue_url_ = continue_url; helper->continue_url_ = continue_url; } } // static void OneClickSigninHelper::RemoveSigninRedirectURLHistoryItem( content::WebContents* web_contents) { // Only actually remove the item if it's the blank.html continue url. if (signin::IsContinueUrlForWebBasedSigninFlow( web_contents->GetLastCommittedURL())) { new CurrentHistoryCleaner(web_contents); // will self-destruct when done } } // static void OneClickSigninHelper::ShowSigninErrorBubble(Browser* browser, const std::string& error) { DCHECK(!error.empty()); browser->window()->ShowOneClickSigninBubble( BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE, base::string16(), /* no SAML email */ UTF8ToUTF16(error), // This callback is never invoked. // TODO(rogerta): Separate out the bubble API so we don't have to pass // ignored |email| and |callback| params. BrowserWindow::StartSyncCallback()); } // static bool OneClickSigninHelper::HandleCrossAccountError( content::WebContents* contents, const std::string& session_index, const std::string& email, const std::string& password, const std::string& oauth_code, OneClickSigninHelper::AutoAccept auto_accept, signin::Source source, OneClickSigninSyncStarter::StartSyncMode start_mode, OneClickSigninSyncStarter::Callback sync_callback) { Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); std::string last_email = profile->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername); if (!last_email.empty() && !gaia::AreEmailsSame(last_email, email)) { // If the new email address is different from the email address that // just signed in, show a confirmation dialog. // No need to display a second confirmation so pass false below. // TODO(atwilson): Move this into OneClickSigninSyncStarter. // The tab modal dialog always executes its callback before |contents| // is deleted. Browser* browser = chrome::FindBrowserWithWebContents(contents); ConfirmEmailDialogDelegate::AskForConfirmation( contents, last_email, email, base::Bind( &StartExplicitSync, StartSyncArgs(profile, browser, auto_accept, session_index, email, password, oauth_code, contents, false /* confirmation_required */, source, sync_callback), contents, start_mode)); return true; } return false; } // static void OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary( content::WebContents* contents, signin::Source source) { if (source != signin::SOURCE_SETTINGS && source != signin::SOURCE_WEBSTORE_INSTALL) { RedirectToNtpOrAppsPage(contents, source); } } void OneClickSigninHelper::RedirectToSignin() { VLOG(1) << "OneClickSigninHelper::RedirectToSignin"; // Extract the existing sounce=X value. Default to "2" if missing. signin::Source source = signin::GetSourceForPromoURL(continue_url_); if (source == signin::SOURCE_UNKNOWN) source = signin::SOURCE_MENU; GURL page = signin::GetPromoURL(source, false); content::WebContents* contents = web_contents(); contents->GetController().LoadURL(page, content::Referrer(), content::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string()); } void OneClickSigninHelper::CleanTransientState() { VLOG(1) << "OneClickSigninHelper::CleanTransientState"; showing_signin_ = false; email_.clear(); password_.clear(); auto_accept_ = AUTO_ACCEPT_NONE; source_ = signin::SOURCE_UNKNOWN; switched_to_advanced_ = false; continue_url_ = GURL(); untrusted_navigations_since_signin_visit_ = 0; untrusted_confirmation_required_ = false; error_message_.clear(); // Post to IO thread to clear pending email. if (!do_not_clear_pending_email_) { Profile* profile = Profile::FromBrowserContext(web_contents()->GetBrowserContext()); content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&ClearPendingEmailOnIOThread, base::Unretained(profile->GetResourceContext()))); } } void OneClickSigninHelper::PasswordSubmitted( const autofill::PasswordForm& form) { // We only need to scrape the password for Gaia logins. if (gaia::IsGaiaSignonRealm(GURL(form.signon_realm))) { VLOG(1) << "OneClickSigninHelper::DidNavigateAnyFrame: got password"; password_ = UTF16ToUTF8(form.password_value); } } void OneClickSigninHelper::SetDoNotClearPendingEmailForTesting() { do_not_clear_pending_email_ = true; } void OneClickSigninHelper::set_do_not_start_sync_for_testing() { do_not_start_sync_for_testing_ = true; } void OneClickSigninHelper::DidStartNavigationToPendingEntry( const GURL& url, content::NavigationController::ReloadType reload_type) { VLOG(1) << "OneClickSigninHelper::DidStartNavigationToPendingEntry: url=" << url.spec(); // If the tab navigates to a new page, and this page is not a valid Gaia // sign in redirect or reponse, or the expected continue URL, make sure to // clear the internal state. This is needed to detect navigations in the // middle of the sign in process that may redirect back to the sign in // process (see crbug.com/181163 for details). GURL::Replacements replacements; replacements.ClearQuery(); if (!IsValidGaiaSigninRedirectOrResponseURL(url) && continue_url_.is_valid() && url.ReplaceComponents(replacements) != continue_url_.ReplaceComponents(replacements)) { if (++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince) CleanTransientState(); } } void OneClickSigninHelper::DidNavigateMainFrame( const content::LoadCommittedDetails& details, const content::FrameNavigateParams& params) { if (!SigninManager::IsWebBasedSigninFlowURL(params.url)) { // Make sure the renderer process is no longer considered the trusted // sign-in process when a navigation to a non-sign-in URL occurs. Profile* profile = Profile::FromBrowserContext(web_contents()->GetBrowserContext()); SigninManager* manager = profile ? SigninManagerFactory::GetForProfile(profile) : NULL; int process_id = web_contents()->GetRenderProcessHost()->GetID(); if (manager && manager->IsSigninProcess(process_id)) manager->ClearSigninProcess(); // If the navigation to a non-sign-in URL hasn't been triggered by the web // contents, the sign in flow has been aborted and the state must be // cleaned (crbug.com/269421). if (!content::PageTransitionIsWebTriggerable(params.transition) && auto_accept_ != AUTO_ACCEPT_NONE) { CleanTransientState(); } } } void OneClickSigninHelper::DidStopLoading( content::RenderViewHost* render_view_host) { // If the user left the sign in process, clear all members. // TODO(rogerta): might need to allow some youtube URLs. content::WebContents* contents = web_contents(); const GURL url = contents->GetLastCommittedURL(); Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); VLOG(1) << "OneClickSigninHelper::DidStopLoading: url=" << url.spec(); // If an error has already occured during the sign in flow, make sure to // display it to the user and abort the process. Do this only for // explicit sign ins. // TODO(rogerta): Could we move this code back up to ShowInfoBarUIThread()? if (!error_message_.empty() && auto_accept_ == AUTO_ACCEPT_EXPLICIT) { VLOG(1) << "OneClickSigninHelper::DidStopLoading: error=" << error_message_; RemoveSigninRedirectURLHistoryItem(contents); // After we redirect to NTP, our browser pointer gets corrupted because the // WebContents have changed, so grab the browser pointer // before the navigation. Browser* browser = chrome::FindBrowserWithWebContents(contents); // Redirect to the landing page and display an error popup. RedirectToNtpOrAppsPage(web_contents(), source_); ShowSigninErrorBubble(browser, error_message_); CleanTransientState(); return; } if (AreWeShowingSignin(url, source_, email_)) { if (!showing_signin_) { if (source_ == signin::SOURCE_UNKNOWN) LogOneClickHistogramValue(one_click_signin::HISTOGRAM_SHOWN); else LogHistogramValue(source_, one_click_signin::HISTOGRAM_SHOWN); } showing_signin_ = true; } // When Gaia finally redirects to the continue URL, Gaia will add some // extra query parameters. So ignore the parameters when checking to see // if the user has continued. GURL::Replacements replacements; replacements.ClearQuery(); const bool continue_url_match = ( continue_url_.is_valid() && url.ReplaceComponents(replacements) == continue_url_.ReplaceComponents(replacements)); const bool original_continue_url_match = ( original_continue_url_.is_valid() && url.ReplaceComponents(replacements) == original_continue_url_.ReplaceComponents(replacements)); if (continue_url_match) RemoveSigninRedirectURLHistoryItem(contents); // If there is no valid email yet, there is nothing to do. As of M26, the // password is allowed to be empty, since its no longer required to setup // sync. if (email_.empty()) { VLOG(1) << "OneClickSigninHelper::DidStopLoading: nothing to do"; // Original-url check done because some user actions cans get us to a page // via a POST instead of a GET (and thus to immediate "cuntinue url") but // we still want redirects from the "blank.html" landing page to work for // non-security related redirects like NTP. // https://code.google.com/p/chromium/issues/detail?id=321938 if (original_continue_url_match) { if (auto_accept_ == AUTO_ACCEPT_EXPLICIT) RedirectToSignin(); std::string unused_value; if (net::GetValueForKeyInQuery(url, "ntp", &unused_value)) { signin::SetUserSkippedPromo(profile); RedirectToNtpOrAppsPage(web_contents(), source_); } } else { if (!IsValidGaiaSigninRedirectOrResponseURL(url) && ++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince) { CleanTransientState(); } } return; } if (!continue_url_match && IsValidGaiaSigninRedirectOrResponseURL(url)) return; // During an explicit sign in, if the user has not yet reached the final // continue URL, wait for it to arrive. Note that Gaia will add some extra // query parameters to the continue URL. Ignore them when checking to // see if the user has continued. // // If this is not an explicit sign in, we don't need to check if we landed // on the right continue URL. This is important because the continue URL // may itself lead to a redirect, which means this function will never see // the continue URL go by. if (auto_accept_ == AUTO_ACCEPT_EXPLICIT) { DCHECK(source_ != signin::SOURCE_UNKNOWN); if (!continue_url_match) { VLOG(1) << "OneClickSigninHelper::DidStopLoading: invalid url='" << url.spec() << "' expected continue url=" << continue_url_; CleanTransientState(); return; } // In explicit sign ins, the user may have changed the box // "Let me choose what to sync". This is reflected as a change in the // source of the continue URL. Make one last check of the current URL // to see if there is a valid source. If so, it overrides the // current source. // // If the source was changed to SOURCE_SETTINGS, we want // OneClickSigninSyncStarter to reuse the current tab to display the // advanced configuration. signin::Source source = signin::GetSourceForPromoURL(url); if (source != source_) { source_ = source; switched_to_advanced_ = source == signin::SOURCE_SETTINGS; } } Browser* browser = chrome::FindBrowserWithWebContents(contents); VLOG(1) << "OneClickSigninHelper::DidStopLoading: signin is go." << " auto_accept=" << auto_accept_ << " source=" << source_; switch (auto_accept_) { case AUTO_ACCEPT_NONE: if (showing_signin_) LogOneClickHistogramValue(one_click_signin::HISTOGRAM_DISMISSED); break; case AUTO_ACCEPT_ACCEPTED: LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED); LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_DEFAULTS); SigninManager::DisableOneClickSignIn(profile); // Start syncing with the default settings - prompt the user to sign in // first. if (!do_not_start_sync_for_testing_) { StartSync( StartSyncArgs(profile, browser, auto_accept_, session_index_, email_, password_, "" /* oauth_code */, NULL /* don't force to show sync setup in same tab */, true /* confirmation_required */, source_, CreateSyncStarterCallback()), OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS); } break; case AUTO_ACCEPT_CONFIGURE: LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED); LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_ADVANCED); SigninManager::DisableOneClickSignIn(profile); // Display the extra confirmation (even in the SAML case) in case this // was an untrusted renderer. if (!do_not_start_sync_for_testing_) { StartSync( StartSyncArgs(profile, browser, auto_accept_, session_index_, email_, password_, "" /* oauth_code */, NULL /* don't force sync setup in same tab */, true /* confirmation_required */, source_, CreateSyncStarterCallback()), OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST); } break; case AUTO_ACCEPT_EXPLICIT: { signin::Source original_source = signin::GetSourceForPromoURL(original_continue_url_); if (switched_to_advanced_) { LogHistogramValue(original_source, one_click_signin::HISTOGRAM_WITH_ADVANCED); LogHistogramValue(original_source, one_click_signin::HISTOGRAM_ACCEPTED); } else { LogHistogramValue(source_, one_click_signin::HISTOGRAM_ACCEPTED); LogHistogramValue(source_, one_click_signin::HISTOGRAM_WITH_DEFAULTS); } // - If sign in was initiated from the NTP or the hotdog menu, sync with // default settings. // - If sign in was initiated from the settings page for first time sync // set up, show the advanced sync settings dialog. // - If sign in was initiated from the settings page due to a re-auth when // sync was already setup, simply navigate back to the settings page. ProfileSyncService* sync_service = ProfileSyncServiceFactory::GetForProfile(profile); OneClickSigninSyncStarter::StartSyncMode start_mode = source_ == signin::SOURCE_SETTINGS ? (SigninGlobalError::GetForProfile(profile)->HasMenuItem() && sync_service && sync_service->HasSyncSetupCompleted()) ? OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE : OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST : OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS; if (!HandleCrossAccountError(contents, session_index_, email_, password_, "" /* oauth_code */, auto_accept_, source_, start_mode, CreateSyncStarterCallback())) { if (!do_not_start_sync_for_testing_) { StartSync( StartSyncArgs(profile, browser, auto_accept_, session_index_, email_, password_, "" /* oauth_code */, contents, untrusted_confirmation_required_, source_, CreateSyncStarterCallback()), start_mode); } // If this explicit sign in is not from settings page/webstore, show // the NTP/Apps page after sign in completes. In the case of the // settings page, it will get auto-closed after sync setup. In the case // of webstore, it will redirect back to webstore. RedirectToNtpOrAppsPageIfNecessary(web_contents(), source_); } // Observe the sync service if the Webstore tab or the settings tab // requested a gaia sign in, so that when sign in and sync setup are // successful, we can redirect to the correct URL, or auto-close the gaia // sign in tab. if (original_source == signin::SOURCE_SETTINGS || (original_source == signin::SOURCE_WEBSTORE_INSTALL && source_ == signin::SOURCE_SETTINGS)) { ProfileSyncService* sync_service = ProfileSyncServiceFactory::GetForProfile(profile); if (sync_service) sync_service->AddObserver(this); } break; } case AUTO_ACCEPT_REJECTED_FOR_PROFILE: AddEmailToOneClickRejectedList(profile, email_); LogOneClickHistogramValue(one_click_signin::HISTOGRAM_REJECTED); break; default: NOTREACHED() << "Invalid auto_accept=" << auto_accept_; break; } CleanTransientState(); } // It is guaranteed that this method is called before the object is deleted. void OneClickSigninHelper::WebContentsDestroyed( content::WebContents* contents) { Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); ProfileSyncService* sync_service = ProfileSyncServiceFactory::GetForProfile(profile); if (sync_service) sync_service->RemoveObserver(this); } void OneClickSigninHelper::OnStateChanged() { // We only add observer for ProfileSyncService when original_continue_url_ is // not empty. DCHECK(!original_continue_url_.is_empty()); content::WebContents* contents = web_contents(); Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); ProfileSyncService* sync_service = ProfileSyncServiceFactory::GetForProfile(profile); // At this point, the sign in process is complete, and control has been handed // back to the sync engine. Close the gaia sign in tab if // |original_continue_url_| contains the |auto_close| parameter. Otherwise, // wait for sync setup to complete and then navigate to // |original_continue_url_|. if (signin::IsAutoCloseEnabledInURL(original_continue_url_)) { // Close the gaia sign in tab via a task to make sure we aren't in the // middle of any webui handler code. base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&CloseTab, base::Unretained(contents))); } else { // Sync setup not completed yet. if (sync_service->FirstSetupInProgress()) return; if (sync_service->sync_initialized() && signin::GetSourceForPromoURL(original_continue_url_) != signin::SOURCE_SETTINGS) { contents->GetController().LoadURL(original_continue_url_, content::Referrer(), content::PAGE_TRANSITION_AUTO_TOPLEVEL, std::string()); } } // Clears |original_continue_url_| here instead of in CleanTransientState, // because it is used in OnStateChanged which occurs later. original_continue_url_ = GURL(); sync_service->RemoveObserver(this); } OneClickSigninSyncStarter::Callback OneClickSigninHelper::CreateSyncStarterCallback() { // The callback will only be invoked if this object is still alive when sync // setup is completed. This is correct because this object is only deleted // when the web contents that potentially shows a blank page is deleted. return base::Bind(&OneClickSigninHelper::SyncSetupCompletedCallback, weak_pointer_factory_.GetWeakPtr()); } void OneClickSigninHelper::SyncSetupCompletedCallback( OneClickSigninSyncStarter::SyncSetupResult result) { if (result == OneClickSigninSyncStarter::SYNC_SETUP_FAILURE && web_contents()) { GURL current_url = web_contents()->GetVisibleURL(); // If the web contents is showing a blank page and not about to be closed, // redirect to the NTP or apps page. if (signin::IsContinueUrlForWebBasedSigninFlow(current_url) && !signin::IsAutoCloseEnabledInURL(original_continue_url_)) { RedirectToNtpOrAppsPage( web_contents(), signin::GetSourceForPromoURL(original_continue_url_)); } } }