From 861ae783973af63043c05ebe3ba250cc36c45c3d Mon Sep 17 00:00:00 2001 From: rmcilroy Date: Tue, 10 Mar 2015 05:00:40 -0700 Subject: [PATCH] Revert of Remove OneClickSigninHelper since it is no longer used. (patchset #5 id:340001 of https://codereview.chromium.org/914363003/) Reason for revert: Broke the tab_switching.tough_energy_cases benchmark on the perf bots. BUG=465692 Original issue's description: > Remove OneClickSigninHelper since it is no longer used. > > BUG=260022,350202 > > Committed: https://crrev.com/be592667bdf919fb8292c88c1b59767add856ba4 > Cr-Commit-Position: refs/heads/master@{#319662} TBR=noms@chromium.org,asvitkine@chromium.org,sky@chromium.org,rogerta@chromium.org NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true BUG=260022,350202 Review URL: https://codereview.chromium.org/998513002 Cr-Commit-Position: refs/heads/master@{#319862} --- .../browser/chrome_browser_field_trials_desktop.cc | 1 + .../chrome_resource_dispatcher_host_delegate.cc | 49 + .../chrome_resource_dispatcher_host_delegate.h | 8 + .../ui/cocoa/one_click_signin_view_controller.mm | 13 +- chrome/browser/ui/sync/one_click_signin_helper.cc | 1433 ++++++++++++++++++++ chrome/browser/ui/sync/one_click_signin_helper.h | 380 ++++++ .../ui/sync/one_click_signin_helper_unittest.cc | 986 ++++++++++++++ .../ui/views/sync/one_click_signin_bubble_view.cc | 17 +- .../ui/webui/signin/inline_login_handler_impl.cc | 356 +---- .../ui/webui/signin/inline_login_handler_impl.h | 20 - .../ui/webui/signin/inline_login_ui_browsertest.cc | 147 -- chrome/chrome_browser_ui.gypi | 2 + chrome/chrome_tests_unit.gypi | 2 + components/signin/core/browser/signin_metrics.cc | 5 - components/signin/core/browser/signin_metrics.h | 2 - 15 files changed, 2897 insertions(+), 524 deletions(-) create mode 100644 chrome/browser/ui/sync/one_click_signin_helper.cc create mode 100644 chrome/browser/ui/sync/one_click_signin_helper.h create mode 100644 chrome/browser/ui/sync/one_click_signin_helper_unittest.cc diff --git a/chrome/browser/chrome_browser_field_trials_desktop.cc b/chrome/browser/chrome_browser_field_trials_desktop.cc index ddc023589dea..9f3c516fb87e 100644 --- a/chrome/browser/chrome_browser_field_trials_desktop.cc +++ b/chrome/browser/chrome_browser_field_trials_desktop.cc @@ -16,6 +16,7 @@ #include "chrome/browser/profiles/profiles_state.h" #include "chrome/browser/safe_browsing/safe_browsing_blocking_page.h" #include "chrome/browser/ui/app_list/app_list_util.h" +#include "chrome/browser/ui/sync/one_click_signin_helper.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_version_info.h" #include "chrome/common/variations/variations_util.h" diff --git a/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc b/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc index 3d859531b77f..b46b3f78fe9c 100644 --- a/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc +++ b/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.cc @@ -79,6 +79,10 @@ #include "chrome/browser/supervised_user/supervised_user_resource_throttle.h" #endif +#if defined(ENABLE_ONE_CLICK_SIGNIN) +#include "chrome/browser/ui/sync/one_click_signin_helper.h" +#endif + #if defined(USE_SYSTEM_PROTOBUF) #include #else @@ -435,6 +439,10 @@ void ChromeResourceDispatcherHostDelegate::RequestBeginning( request->SetExtraRequestHeaders(headers); } +#if defined(ENABLE_ONE_CLICK_SIGNIN) + AppendChromeSyncGaiaHeader(request, resource_context); +#endif + #if defined(ENABLE_CONFIGURATION_POLICY) if (io_data->policy_header_helper()) io_data->policy_header_helper()->AddPolicyHeaders(request->url(), request); @@ -585,6 +593,28 @@ void ChromeResourceDispatcherHostDelegate::AppendStandardResourceThrottles( } } +#if defined(ENABLE_ONE_CLICK_SIGNIN) +void ChromeResourceDispatcherHostDelegate::AppendChromeSyncGaiaHeader( + net::URLRequest* request, + content::ResourceContext* resource_context) { + static const char kAllowChromeSignIn[] = "Allow-Chrome-SignIn"; + + ProfileIOData* io_data = ProfileIOData::FromResourceContext(resource_context); + OneClickSigninHelper::Offer offer = + OneClickSigninHelper::CanOfferOnIOThread(request, io_data); + switch (offer) { + case OneClickSigninHelper::CAN_OFFER: + request->SetExtraRequestHeaderByName(kAllowChromeSignIn, "1", false); + break; + case OneClickSigninHelper::DONT_OFFER: + request->RemoveRequestHeaderByName(kAllowChromeSignIn); + break; + case OneClickSigninHelper::IGNORE_REQUEST: + break; + } +} +#endif + bool ChromeResourceDispatcherHostDelegate::ShouldForceDownloadResource( const GURL& url, const std::string& mime_type) { #if defined(ENABLE_EXTENSIONS) @@ -673,6 +703,15 @@ void ChromeResourceDispatcherHostDelegate::OnResponseStarted( const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request); ProfileIOData* io_data = ProfileIOData::FromResourceContext(resource_context); +#if defined(ENABLE_ONE_CLICK_SIGNIN) + // See if the response contains the Google-Accounts-SignIn header. If so, + // then the user has just finished signing in, and the server is allowing the + // browser to suggest connecting the user's profile to the account. + OneClickSigninHelper::ShowInfoBarIfPossible(request, io_data, + info->GetChildID(), + info->GetRouteID()); +#endif + // See if the response contains the X-Chrome-Manage-Accounts header. If so // show the profile avatar bubble so that user can complete signin/out action // the native UI. @@ -722,6 +761,16 @@ void ChromeResourceDispatcherHostDelegate::OnRequestRedirected( const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request); +#if defined(ENABLE_ONE_CLICK_SIGNIN) + // See if the response contains the Google-Accounts-SignIn header. If so, + // then the user has just finished signing in, and the server is allowing the + // browser to suggest connecting the user's profile to the account. + OneClickSigninHelper::ShowInfoBarIfPossible(request, io_data, + info->GetChildID(), + info->GetRouteID()); + AppendChromeSyncGaiaHeader(request, resource_context); +#endif + // In the Mirror world, Chrome should append a X-Chrome-Connected header to // all Gaia requests from a connected profile so Gaia could return a 204 // response and let Chrome handle the action with native UI. The only diff --git a/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.h b/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.h index ffbab62156f2..d3da5b194e9c 100644 --- a/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.h +++ b/chrome/browser/renderer_host/chrome_resource_dispatcher_host_delegate.h @@ -92,6 +92,14 @@ class ChromeResourceDispatcherHostDelegate content::ResourceType resource_type, ScopedVector* throttles); +#if defined(ENABLE_ONE_CLICK_SIGNIN) + // Append headers required to tell Gaia whether the sync interstitial + // should be shown or not. This header is only added for valid Gaia URLs. + void AppendChromeSyncGaiaHeader( + net::URLRequest* request, + content::ResourceContext* resource_context); +#endif + scoped_refptr download_request_limiter_; scoped_refptr safe_browsing_; #if defined(ENABLE_EXTENSIONS) diff --git a/chrome/browser/ui/cocoa/one_click_signin_view_controller.mm b/chrome/browser/ui/cocoa/one_click_signin_view_controller.mm index 6ecd11ee8de8..50e2931b8d66 100644 --- a/chrome/browser/ui/cocoa/one_click_signin_view_controller.mm +++ b/chrome/browser/ui/cocoa/one_click_signin_view_controller.mm @@ -8,6 +8,7 @@ #include "base/logging.h" #include "base/mac/bundle_locations.h" #import "chrome/browser/ui/chrome_style.h" +#include "chrome/browser/ui/sync/one_click_signin_helper.h" #include "chrome/common/url_constants.h" #include "chrome/grit/chromium_strings.h" #include "chrome/grit/generated_resources.h" @@ -77,7 +78,7 @@ void ShiftOriginY(NSView* view, CGFloat amount) { - (IBAction)ok:(id)sender { if (isSyncDialog_) { - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( clickedLearnMore_ ? signin_metrics::HISTOGRAM_CONFIRM_LEARN_MORE_OK : signin_metrics::HISTOGRAM_CONFIRM_OK); @@ -90,7 +91,7 @@ void ShiftOriginY(NSView* view, CGFloat amount) { - (IBAction)onClickUndo:(id)sender { if (isSyncDialog_) { - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( clickedLearnMore_ ? signin_metrics::HISTOGRAM_CONFIRM_LEARN_MORE_UNDO : signin_metrics::HISTOGRAM_CONFIRM_UNDO); @@ -103,7 +104,7 @@ void ShiftOriginY(NSView* view, CGFloat amount) { - (IBAction)onClickAdvancedLink:(id)sender { if (isSyncDialog_) { - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( clickedLearnMore_ ? signin_metrics::HISTOGRAM_CONFIRM_LEARN_MORE_ADVANCED : signin_metrics::HISTOGRAM_CONFIRM_ADVANCED); @@ -122,7 +123,7 @@ void ShiftOriginY(NSView* view, CGFloat amount) { - (IBAction)onClickClose:(id)sender { if (isSyncDialog_) { - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( clickedLearnMore_ ? signin_metrics::HISTOGRAM_CONFIRM_LEARN_MORE_CLOSE : signin_metrics::HISTOGRAM_CONFIRM_CLOSE); @@ -210,7 +211,7 @@ void ShiftOriginY(NSView* view, CGFloat amount) { delta:delta]; if (isSyncDialog_) { - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( signin_metrics::HISTOGRAM_CONFIRM_SHOWN); } } @@ -286,7 +287,7 @@ void ShiftOriginY(NSView* view, CGFloat amount) { if (isSyncDialog_ && !clickedLearnMore_) { clickedLearnMore_ = YES; - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( signin_metrics::HISTOGRAM_CONFIRM_LEARN_MORE); } WindowOpenDisposition location = isSyncDialog_ ? diff --git a/chrome/browser/ui/sync/one_click_signin_helper.cc b/chrome/browser/ui/sync/one_click_signin_helper.cc new file mode 100644 index 000000000000..e72ae55f591a --- /dev/null +++ b/chrome/browser/ui/sync/one_click_signin_helper.cc @@ -0,0 +1,1433 @@ +// 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/history/history_service.h" +#include "chrome/browser/history/history_service_factory.h" +#include "chrome/browser/password_manager/chrome_password_manager_client.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_client.h" +#include "chrome/browser/signin/chrome_signin_client_factory.h" +#include "chrome/browser/signin/signin_error_controller_factory.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/tab_contents/tab_util.h" +#include "chrome/browser/ui/browser_finder.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/chrome_pages.h" +#include "chrome/browser/ui/sync/one_click_signin_sync_observer.h" +#include "chrome/browser/ui/sync/one_click_signin_sync_starter.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/browser/ui/webui/signin/login_ui_service_factory.h" +#include "chrome/common/chrome_version_info.h" +#include "chrome/common/net/url_util.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" +#include "chrome/grit/chromium_strings.h" +#include "chrome/grit/generated_resources.h" +#include "components/autofill/core/common/password_form.h" +#include "components/google/core/browser/google_util.h" +#include "components/password_manager/core/browser/password_manager.h" +#include "components/signin/core/browser/signin_client.h" +#include "components/signin/core/browser/signin_error_controller.h" +#include "components/signin/core/browser/signin_manager.h" +#include "components/signin/core/browser/signin_manager_cookie_helper.h" +#include "components/signin/core/browser/signin_metrics.h" +#include "components/signin/core/common/profile_management_switches.h" +#include "components/sync_driver/sync_prefs.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_frame_host.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_delegate.h" +#include "content/public/common/frame_navigate_params.h" +#include "google_apis/gaia/gaia_auth_util.h" +#include "google_apis/gaia/gaia_urls.h" +#include "grit/components_strings.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/page_transition_types.h" +#include "url/gurl.h" + + +namespace { + +// 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); + ~ConfirmEmailDialogDelegate() override; + + // TabModalConfirmDialogDelegate: + base::string16 GetTitle() override; + base::string16 GetDialogMessage() override; + base::string16 GetAcceptButtonTitle() override; + base::string16 GetCancelButtonTitle() override; + base::string16 GetLinkText() const override; + void OnAccepted() override; + void OnCanceled() override; + void OnClosed() override; + 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::GetDialogMessage() { + return l10n_util::GetStringFUTF16( + IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_MESSAGE, + base::UTF8ToUTF16(last_email_), base::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, + ui::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)); +} + +// Start syncing with the given user information. +void StartSync(const OneClickSigninHelper::StartSyncArgs& args, + OneClickSigninSyncStarter::StartSyncMode start_mode) { + if (start_mode == OneClickSigninSyncStarter::UNDO_SYNC) { + OneClickSigninHelper::LogHistogramValue(signin_metrics::HISTOGRAM_UNDO); + return; + } + + // The wrapper deletes itself once it's done. + OneClickSigninHelper::SyncStarterWrapper* wrapper = + new OneClickSigninHelper::SyncStarterWrapper(args, start_mode); + wrapper->Start(); + + int action = signin_metrics::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 ? + signin_metrics::HISTOGRAM_AUTO_WITH_DEFAULTS : + signin_metrics::HISTOGRAM_AUTO_WITH_ADVANCED; + break; + case OneClickSigninHelper::AUTO_ACCEPT_CONFIGURE: + DCHECK(start_mode == OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST); + action = signin_metrics::HISTOGRAM_AUTO_WITH_ADVANCED; + break; + default: + NOTREACHED() << "Invalid auto_accept: " << args.auto_accept; + break; + } + if (action != signin_metrics::HISTOGRAM_MAX) + OneClickSigninHelper::LogHistogramValue(action); +} + +void StartExplicitSync(const OneClickSigninHelper::StartSyncArgs& args, + content::WebContents* contents, + OneClickSigninSyncStarter::StartSyncMode start_mode, + ConfirmEmailDialogDelegate::Action action) { + if (action == ConfirmEmailDialogDelegate::START_SYNC) { + StartSync(args, start_mode); + } 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. + args.callback.Run(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE); + if (action == ConfirmEmailDialogDelegate::CREATE_NEW_USER) { + chrome::ShowSettingsSubPage(args.browser, + std::string(chrome::kCreateProfileSubPage)); + } + } +} + +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. It's either one +// of the known sign-in access points (first run, NTP, Apps page, menu, or +// settings) or it's an implicit sign in via another Google property. In the +// former case, "service" is also checked to make sure its "chromiumsync". +signin_metrics::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_metrics::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_metrics::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_metrics::SOURCE_UNKNOWN) || + (IsValidGaiaSigninRedirectOrResponseURL(url) && + url.spec().find("ChromeLoginPrompt") != std::string::npos && + !email.empty()); +} + +// If profile is valid then get signin scoped device id from signin client. +// Otherwise returns empty string. +std::string GetSigninScopedDeviceId(Profile* profile) { + std::string signin_scoped_device_id; + SigninClient* signin_client = + profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL; + if (signin_client) { + signin_scoped_device_id = signin_client->GetSigninScopedDeviceId(); + } + return signin_scoped_device_id; +} + +// 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); + ~CurrentHistoryCleaner() override; + + // content::WebContentsObserver: + void WebContentsDestroyed() override; + void DidCommitProvisionalLoadForFrame( + content::RenderFrameHost* render_frame_host, + const GURL& url, + ui::PageTransition transition_type) 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( + content::RenderFrameHost* render_frame_host, + const GURL& url, + ui::PageTransition transition_type) { + // Return early if this is not top-level navigation. + if (render_frame_host->GetParent()) + return; +} + +void CurrentHistoryCleaner::WebContentsDestroyed() { + delete this; // Failure. +} + +} // namespace + + +// StartSyncArgs -------------------------------------------------------------- + +OneClickSigninHelper::StartSyncArgs::StartSyncArgs() + : profile(NULL), + browser(NULL), + auto_accept(AUTO_ACCEPT_NONE), + web_contents(NULL), + confirmation_required(OneClickSigninSyncStarter::NO_CONFIRMATION), + source(signin_metrics::SOURCE_UNKNOWN) {} + +OneClickSigninHelper::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& refresh_token, + content::WebContents* web_contents, + bool untrusted_confirmation_required, + signin_metrics::Source source, + OneClickSigninSyncStarter::Callback callback) + : profile(profile), + browser(browser), + auto_accept(auto_accept), + session_index(session_index), + email(email), + password(password), + refresh_token(refresh_token), + web_contents(web_contents), + source(source), + callback(callback) { + DCHECK(session_index.empty() != refresh_token.empty()); + if (untrusted_confirmation_required) { + confirmation_required = OneClickSigninSyncStarter::CONFIRM_UNTRUSTED_SIGNIN; + } else if (source == signin_metrics::SOURCE_SETTINGS) { + // Do not display a status confirmation for re-auth. + confirmation_required = OneClickSigninSyncStarter::NO_CONFIRMATION; + } else { + confirmation_required = OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN; + } +} + +OneClickSigninHelper::StartSyncArgs::~StartSyncArgs() {} + +// SyncStarterWrapper --------------------------------------------------------- + +OneClickSigninHelper::SyncStarterWrapper::SyncStarterWrapper( + const OneClickSigninHelper::StartSyncArgs& args, + OneClickSigninSyncStarter::StartSyncMode start_mode) + : args_(args), start_mode_(start_mode), weak_pointer_factory_(this) { + BrowserList::AddObserver(this); + + // Cache the parent desktop for the browser, so we can reuse that same + // desktop for any UI we want to display. + desktop_type_ = args_.browser ? args_.browser->host_desktop_type() + : chrome::GetActiveDesktop(); +} + +OneClickSigninHelper::SyncStarterWrapper::~SyncStarterWrapper() { + BrowserList::RemoveObserver(this); +} + +void OneClickSigninHelper::SyncStarterWrapper::Start() { + if (args_.refresh_token.empty()) { + if (args_.password.empty()) { + VerifyGaiaCookiesBeforeSignIn(); + } else { + StartSigninOAuthHelper(); + } + } else { + OnSigninOAuthInformationAvailable(args_.email, args_.email, + args_.refresh_token); + } +} + +void +OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationAvailable( + const std::string& email, + const std::string& display_email, + const std::string& refresh_token) { + if (!gaia::AreEmailsSame(display_email, args_.email)) { + DisplayErrorBubble( + GoogleServiceAuthError( + GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString()); + } else { + StartOneClickSigninSyncStarter(email, refresh_token); + } + + base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +void OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationFailure( + const GoogleServiceAuthError& error) { + DisplayErrorBubble(error.ToString()); + base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +void OneClickSigninHelper::SyncStarterWrapper::OnBrowserRemoved( + Browser* browser) { + if (args_.browser == browser) + args_.browser = NULL; +} + +void OneClickSigninHelper::SyncStarterWrapper::VerifyGaiaCookiesBeforeSignIn() { + scoped_refptr cookie_helper( + new SigninManagerCookieHelper( + args_.profile->GetRequestContext(), + content::BrowserThread::GetMessageLoopProxyForThread( + content::BrowserThread::UI), + content::BrowserThread::GetMessageLoopProxyForThread( + content::BrowserThread::IO))); + cookie_helper->StartFetchingGaiaCookiesOnUIThread( + base::Bind(&SyncStarterWrapper::OnGaiaCookiesFetched, + weak_pointer_factory_.GetWeakPtr(), + args_.session_index)); +} + +void OneClickSigninHelper::SyncStarterWrapper::OnGaiaCookiesFetched( + const std::string session_index, const net::CookieList& cookie_list) { + net::CookieList::const_iterator it; + bool success = false; + for (it = cookie_list.begin(); it != cookie_list.end(); ++it) { + // Make sure the LSID cookie is set on the GAIA host, instead of a super- + // domain. + if (it->Name() == "LSID") { + if (it->IsHostCookie() && it->IsHttpOnly() && it->IsSecure()) { + // Found a valid LSID cookie. Continue loop to make sure we don't have + // invalid LSID cookies on any super-domain. + success = true; + } else { + success = false; + break; + } + } + } + + if (success) { + StartSigninOAuthHelper(); + } else { + DisplayErrorBubble( + GoogleServiceAuthError( + GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString()); + base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); + } +} + +void OneClickSigninHelper::SyncStarterWrapper::DisplayErrorBubble( + const std::string& error_message) { + args_.browser = OneClickSigninSyncStarter::EnsureBrowser( + args_.browser, args_.profile, desktop_type_); + LoginUIServiceFactory::GetForProfile(args_.profile)->DisplayLoginResult( + args_.browser, base::UTF8ToUTF16(error_message)); +} + +void OneClickSigninHelper::SyncStarterWrapper::StartSigninOAuthHelper() { + std::string signin_scoped_device_id = GetSigninScopedDeviceId(args_.profile); + signin_oauth_helper_.reset( + new SigninOAuthHelper(args_.profile->GetRequestContext(), + args_.session_index, + signin_scoped_device_id, + this)); +} + +void +OneClickSigninHelper::SyncStarterWrapper::StartOneClickSigninSyncStarter( + const std::string& email, + const std::string& refresh_token) { + // The starter deletes itself once it's done. + new OneClickSigninSyncStarter(args_.profile, args_.browser, + email, args_.password, + refresh_token, start_mode_, + args_.web_contents, + args_.confirmation_required, + GURL(), + args_.callback); +} + + +// OneClickSigninHelper ------------------------------------------------------- + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(OneClickSigninHelper); + +// static +const int OneClickSigninHelper::kMaxNavigationsSince = 10; + +OneClickSigninHelper::OneClickSigninHelper(content::WebContents* web_contents) + : content::WebContentsObserver(web_contents), + showing_signin_(false), + auto_accept_(AUTO_ACCEPT_NONE), + source_(signin_metrics::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) { + ChromePasswordManagerClient* client = + ChromePasswordManagerClient::FromWebContents(web_contents); + // May be NULL during testing. + if (client) { + client->GetPasswordManager()->AddSubmissionCallback( + base::Bind(&OneClickSigninHelper::PasswordSubmitted, + weak_pointer_factory_.GetWeakPtr())); + } +} + +OneClickSigninHelper::~OneClickSigninHelper() {} + +// static +void OneClickSigninHelper::LogHistogramValue(int action) { + UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action, + signin_metrics::HISTOGRAM_MAX); +} + +// static +bool OneClickSigninHelper::CanOffer(content::WebContents* web_contents, + CanOfferFor can_offer_for, + const std::string& email, + std::string* error_message) { + DCHECK_CURRENTLY_ON(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 (!ChromeSigninClient::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_metrics::HISTOGRAM_ACCOUNT_MISSMATCH, + signin_metrics::HISTOGRAM_MAX); + if (error_message) { + error_message->assign( + l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL, + base::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) { + ProfileInfoCache& cache = manager->GetProfileInfoCache(); + for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) { + std::string current_email = + base::UTF16ToUTF8(cache.GetUserNameOfProfileAtIndex(i)); + if (gaia::AreEmailsSame(email, current_email)) { + 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 base::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->IsOffTheRecord()) + return DONT_OFFER; + + if (!io_data->signin_allowed()->GetValue()) + 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 (!ChromeSigninClient::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( + base::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. + base::StringPairs 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_metrics::Source source = signin_metrics::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_metrics::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, + signin_metrics::HISTOGRAM_CONFIRM_MAX); +} +// static +void OneClickSigninHelper::ShowInfoBarUIThread( + const std::string& session_index, + const std::string& email, + AutoAccept auto_accept, + signin_metrics::Source source, + const GURL& continue_url, + int child_id, + int route_id) { + DCHECK_CURRENTLY_ON(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_metrics::SOURCE_UNKNOWN && + helper->source_ == signin_metrics::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()); + SigninClient* signin_client = + profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL; + helper->untrusted_confirmation_required_ |= + (signin_client && !signin_client->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) { +} + +// static +bool OneClickSigninHelper::HandleCrossAccountError( + Profile* profile, + const std::string& session_index, + const std::string& email, + const std::string& password, + const std::string& refresh_token, + OneClickSigninHelper::AutoAccept auto_accept, + signin_metrics::Source source, + OneClickSigninSyncStarter::StartSyncMode start_mode, + OneClickSigninSyncStarter::Callback sync_callback) { + 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 on top of the current active + // tab. + + // 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::FindLastActiveWithProfile( + profile, chrome::GetActiveDesktop()); + content::WebContents* contents = + browser->tab_strip_model()->GetActiveWebContents(); + + ConfirmEmailDialogDelegate::AskForConfirmation( + contents, + last_email, + email, + base::Bind( + &StartExplicitSync, + StartSyncArgs(profile, browser, auto_accept, + session_index, email, password, + refresh_token, + contents, false /* confirmation_required */, source, + sync_callback), + contents, + start_mode)); + return true; + } + + return false; +} + +// static +void OneClickSigninHelper::RedirectToNtpOrAppsPage( + content::WebContents* contents, signin_metrics::Source source) { + // Do nothing if a navigation is pending, since this call can be triggered + // from DidStartLoading. This avoids deleting the pending entry while we are + // still navigating to it. See crbug/346632. + if (contents->GetController().GetPendingEntry()) + return; + + VLOG(1) << "RedirectToNtpOrAppsPage"; + // Redirect to NTP/Apps page and display a confirmation bubble + GURL url(source == signin_metrics::SOURCE_APPS_PAGE_LINK ? + chrome::kChromeUIAppsURL : chrome::kChromeUINewTabURL); + content::OpenURLParams params(url, + content::Referrer(), + CURRENT_TAB, + ui::PAGE_TRANSITION_AUTO_TOPLEVEL, + false); + contents->OpenURL(params); +} + +// static +void OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary( + content::WebContents* contents, signin_metrics::Source source) { + if (source != signin_metrics::SOURCE_SETTINGS) { + RedirectToNtpOrAppsPage(contents, source); + } +} + +void OneClickSigninHelper::RedirectToSignin() { + VLOG(1) << "OneClickSigninHelper::RedirectToSignin"; + + // Extract the existing sounce=X value. Default to "2" if missing. + signin_metrics::Source source = signin::GetSourceForPromoURL(continue_url_); + if (source == signin_metrics::SOURCE_UNKNOWN) + source = signin_metrics::SOURCE_MENU; + GURL page = signin::GetPromoURL(source, false); + + content::WebContents* contents = web_contents(); + contents->GetController().LoadURL(page, + content::Referrer(), + ui::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_metrics::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_ = base::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()); + SigninClient* signin_client = + profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL; + int process_id = web_contents()->GetRenderProcessHost()->GetID(); + if (signin_client && signin_client->IsSigninProcess(process_id)) + signin_client->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 (!ui::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 (url.scheme() == content::kChromeUIScheme) { + // Suppresses OneClickSigninHelper on webUI pages to avoid inteference with + // inline signin flows. + VLOG(1) << "OneClickSigninHelper::DidStopLoading: suppressed for url=" + << url.spec(); + CleanTransientState(); + return; + } + + // 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_); + LoginUIServiceFactory::GetForProfile(profile)-> + DisplayLoginResult(browser, base::UTF8ToUTF16(error_message_)); + CleanTransientState(); + return; + } + + if (AreWeShowingSignin(url, source_, email_)) { + if (!showing_signin_) + LogHistogramValue(signin_metrics::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. Sometimes locales will redirect to a country- + // specific TLD so just make sure it's a valid domain instead of comparing + // for an exact match. + GURL::Replacements replacements; + replacements.ClearQuery(); + bool google_domain_url = google_util::IsGoogleDomainUrl( + url, + google_util::ALLOW_SUBDOMAIN, + google_util::DISALLOW_NON_STANDARD_PORTS); + const bool continue_url_match = + google_domain_url && + url.ReplaceComponents(replacements).path() == + continue_url_.ReplaceComponents(replacements).path(); + const bool original_continue_url_match = + google_domain_url && + url.ReplaceComponents(replacements).path() == + original_continue_url_.ReplaceComponents(replacements).path(); + + 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_metrics::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_metrics::Source source = signin::GetSourceForPromoURL(url); + if (source != source_) { + source_ = source; + switched_to_advanced_ = source == signin_metrics::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_) + LogHistogramValue(signin_metrics::HISTOGRAM_DISMISSED); + break; + case AUTO_ACCEPT_ACCEPTED: + LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED); + LogHistogramValue(signin_metrics::HISTOGRAM_WITH_DEFAULTS); + SigninManager::DisableOneClickSignIn(profile->GetPrefs()); + // 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_, "", + NULL /* don't force sync setup in same tab */, + true /* confirmation_required */, source_, + OneClickSigninSyncStarter::Callback()), + OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS); + } + break; + case AUTO_ACCEPT_CONFIGURE: + LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED); + LogHistogramValue(signin_metrics::HISTOGRAM_WITH_ADVANCED); + SigninManager::DisableOneClickSignIn(profile->GetPrefs()); + // 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_, "", + NULL /* don't force sync setup in same tab */, + true /* confirmation_required */, source_, + OneClickSigninSyncStarter::Callback()), + OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST); + } + break; + case AUTO_ACCEPT_EXPLICIT: { + signin_metrics::Source original_source = + signin::GetSourceForPromoURL(original_continue_url_); + if (switched_to_advanced_) { + LogHistogramValue(signin_metrics::HISTOGRAM_WITH_ADVANCED); + LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED); + } else { + LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED); + LogHistogramValue(signin_metrics::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); + SigninErrorController* error_controller = + SigninErrorControllerFactory::GetForProfile(profile); + + OneClickSigninSyncStarter::StartSyncMode start_mode = + source_ == signin_metrics::SOURCE_SETTINGS ? + (error_controller->HasError() && + sync_service && sync_service->HasSyncSetupCompleted()) ? + OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE : + OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST : + OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS; + + if (!HandleCrossAccountError(profile, session_index_, email_, password_, + "", auto_accept_, source_, start_mode, + OneClickSigninSyncStarter::Callback())) { + if (!do_not_start_sync_for_testing_) { + StartSync( + StartSyncArgs(profile, browser, auto_accept_, + session_index_, email_, password_, "", + contents, + untrusted_confirmation_required_, source_, + OneClickSigninSyncStarter::Callback()), + 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 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_metrics::SOURCE_SETTINGS) { + // The observer deletes itself once it's done. + new OneClickSigninSyncObserver(contents, original_continue_url_); + } + break; + } + case AUTO_ACCEPT_REJECTED_FOR_PROFILE: + AddEmailToOneClickRejectedList(profile, email_); + LogHistogramValue(signin_metrics::HISTOGRAM_REJECTED); + break; + default: + NOTREACHED() << "Invalid auto_accept=" << auto_accept_; + break; + } + + CleanTransientState(); +} diff --git a/chrome/browser/ui/sync/one_click_signin_helper.h b/chrome/browser/ui/sync/one_click_signin_helper.h new file mode 100644 index 000000000000..335ad333f65b --- /dev/null +++ b/chrome/browser/ui/sync/one_click_signin_helper.h @@ -0,0 +1,380 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_SYNC_ONE_CLICK_SIGNIN_HELPER_H_ +#define CHROME_BROWSER_UI_SYNC_ONE_CLICK_SIGNIN_HELPER_H_ + +#include + +#include "base/gtest_prod_util.h" +#include "base/memory/weak_ptr.h" +#include "chrome/browser/signin/signin_promo.h" +#include "chrome/browser/sync/profile_sync_service_observer.h" +#include "chrome/browser/ui/sync/one_click_signin_sync_starter.h" +#include "components/signin/core/browser/signin_oauth_helper.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" +#include "google_apis/gaia/google_service_auth_error.h" + +class Browser; +class GURL; +class ProfileIOData; + +namespace autofill { +struct PasswordForm; +} + +namespace content { +class WebContents; +struct FrameNavigateParams; +struct LoadCommittedDetails; +} + +namespace net { +class URLRequest; +} + +namespace password_manager { +class PasswordManager; +} + +// Per-tab one-click signin helper. When a user signs in to a Google service +// and the profile is not yet connected to a Google account, will start the +// process of helping the user connect his profile with one click. The process +// begins with an infobar and is followed with a confirmation dialog explaining +// more about what this means. +class OneClickSigninHelper + : public content::WebContentsObserver, + public content::WebContentsUserData { + public: + // Represents user's decision about sign in process. + enum AutoAccept { + // User decision not yet known. Assume cancel. + AUTO_ACCEPT_NONE, + + // User has explicitly accepted to sign in. A bubble is shown with the + // option to start sync, configure it first, or abort. + AUTO_ACCEPT_ACCEPTED, + + // User has explicitly accepted to sign in, but wants to configure sync + // settings before turning it on. + AUTO_ACCEPT_CONFIGURE, + + // User has explicitly rejected to sign in. Furthermore, the user does + // not want to be prompted to see the interstitial again in this profile. + AUTO_ACCEPT_REJECTED_FOR_PROFILE, + + // This is an explicit sign in from either first run, NTP, wrench menu, + // or settings page. The user will be signed in automatically with sync + // enabled using default settings. + AUTO_ACCEPT_EXPLICIT + }; + + // Return value of CanOfferOnIOThread(). + enum Offer { + CAN_OFFER, + DONT_OFFER, + IGNORE_REQUEST + }; + + // Argument to CanOffer(). + enum CanOfferFor { + CAN_OFFER_FOR_ALL, + CAN_OFFER_FOR_INTERSTITAL_ONLY, + CAN_OFFER_FOR_SECONDARY_ACCOUNT + // TODO(guohui): needs to handle adding secondary account through + // interstitial. + }; + + // 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 { + // Default contructor for testing only. + 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& refresh_token, + content::WebContents* web_contents, + bool untrusted_confirmation_required, + signin_metrics::Source source, + OneClickSigninSyncStarter::Callback callback); + ~StartSyncArgs(); + + Profile* profile; + Browser* browser; + OneClickSigninHelper::AutoAccept auto_accept; + std::string session_index; + std::string email; + std::string password; + std::string refresh_token; + + // 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_metrics::Source source; + OneClickSigninSyncStarter::Callback callback; + }; + + // Wrapper to call OneClickSigninSyncStarter after fetching the refresh token + // if needed. Also verifies that the cookies are correct if no password is + // specified, and checks that the email from the cookies match the expected + // email address. + class SyncStarterWrapper : public SigninOAuthHelper::Consumer, + public chrome::BrowserListObserver { + public: + SyncStarterWrapper( + const OneClickSigninHelper::StartSyncArgs& args, + OneClickSigninSyncStarter::StartSyncMode start_mode); + ~SyncStarterWrapper() override; + + void Start(); + + private: + void VerifyGaiaCookiesBeforeSignIn(); + void OnGaiaCookiesFetched(const std::string session_index, + const net::CookieList& cookie_list); + + // Virtual to be overridden in tests. + virtual void DisplayErrorBubble(const std::string& error_message); + virtual void StartSigninOAuthHelper(); + virtual void StartOneClickSigninSyncStarter( + const std::string& email, + const std::string& refresh_token); + + // Overriden from SigninOAuthHelper::Consumer. + void OnSigninOAuthInformationAvailable( + const std::string& email, + const std::string& display_email, + const std::string& refresh_token) override; + void OnSigninOAuthInformationFailure( + const GoogleServiceAuthError& error) override; + + // Overriden from chrome::BrowserListObserver. + void OnBrowserRemoved(Browser* browser) override; + + OneClickSigninHelper::StartSyncArgs args_; + chrome::HostDesktopType desktop_type_; + OneClickSigninSyncStarter::StartSyncMode start_mode_; + scoped_ptr signin_oauth_helper_; + base::WeakPtrFactory weak_pointer_factory_; + + DISALLOW_COPY_AND_ASSIGN(SyncStarterWrapper); + }; + + static void LogHistogramValue(int action); + + // Returns true if the one-click signin feature can be offered at this time. + // If |email| is not empty, then the profile is checked to see if it's + // already connected to a google account or if the user has already rejected + // one-click sign-in with this email, in which cases a one click signin + // should not be offered. + // + // If |can_offer_for| is |CAN_OFFER_FOR_INTERSTITAL_ONLY|, then only do the + // checks that would affect the interstitial page. Otherwise, do the checks + // that would affect the interstitial and the explicit sign ins. + // + // Returns in |error_message_id| an explanation as a string resource ID for + // why one-clicked cannot be offered. |error_message_id| is valid only if + // the return value is false. If no explanation is needed, |error_message_id| + // may be null. + static bool CanOffer(content::WebContents* web_contents, + CanOfferFor can_offer_for, + const std::string& email, + std::string* error_message); + + // Returns true if the one-click signin feature can be offered at this time. + // It can be offered if the io_data is not in an incognito window and if the + // origin of |url| is a valid Gaia sign in origin. This function is meant + // to called only from the IO thread. + static Offer CanOfferOnIOThread(net::URLRequest* request, + ProfileIOData* io_data); + + // Looks for the Google-Accounts-SignIn response header, and if found, + // tries to display an infobar in the tab contents identified by the + // child/route id. + static void ShowInfoBarIfPossible(net::URLRequest* request, + ProfileIOData* io_data, + int child_id, + int route_id); + + // Handles cross account sign in error. If the supplied |email| does not match + // the last signed in email of the current profile, then Chrome will show a + // confirmation dialog before starting sync. It returns true if there is a + // cross account error, and false otherwise. + static bool HandleCrossAccountError( + Profile* profile, + const std::string& session_index, + const std::string& email, + const std::string& password, + const std::string& refresh_token, + OneClickSigninHelper::AutoAccept auto_accept, + signin_metrics::Source source, + OneClickSigninSyncStarter::StartSyncMode start_mode, + OneClickSigninSyncStarter::Callback sync_callback); + + static void RedirectToNtpOrAppsPage( + content::WebContents* contents, signin_metrics::Source source); + + // If the |source| is not settings page/webstore, redirects to + // the NTP/Apps page. + static void RedirectToNtpOrAppsPageIfNecessary( + content::WebContents* contents, signin_metrics::Source source); + + // Remove the item currently at the top of the history list if it's + // the Gaia redirect URL. Due to limitations of the NavigationController + // this cannot be done until a new page becomes "current". + static void RemoveSigninRedirectURLHistoryItem( + content::WebContents* web_contents); + + static void LogConfirmHistogramValue(int action); + + private: + friend class content::WebContentsUserData; + friend class OneClickSigninHelperTest; + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIncognitoTest, + ShowInfoBarUIThreadIncognito); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest, + SigninFromWebstoreWithConfigSyncfirst); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest, + ShowSigninBubbleAfterSigninComplete); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest, SigninCancelled); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest, SigninFailed); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperTest, + CleanTransientStateOnNavigate); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, CanOfferOnIOThread); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, + CanOfferOnIOThreadIncognito); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, + CanOfferOnIOThreadNoIOData); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, + CanOfferOnIOThreadBadURL); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, + CanOfferOnIOThreadDisabled); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, + CanOfferOnIOThreadSignedIn); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, + CanOfferOnIOThreadEmailNotAllowed); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, + CanOfferOnIOThreadEmailAlreadyUsed); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, + CreateTestProfileIOData); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, + CanOfferOnIOThreadWithRejectedEmail); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, + CanOfferOnIOThreadNoSigninCookies); + FRIEND_TEST_ALL_PREFIXES(OneClickSigninHelperIOTest, + CanOfferOnIOThreadDisabledByPolicy); + + // Maximum number of navigations away from the set of valid Gaia URLs before + // clearing the internal state of the helper. This is necessary to support + // SAML-based accounts, but causes bug crbug.com/181163. + static const int kMaxNavigationsSince; + + explicit OneClickSigninHelper(content::WebContents* web_contents); + + ~OneClickSigninHelper() override; + + // Returns true if the one-click signin feature can be offered at this time. + // It can be offered if the io_data is not in an incognito window and if the + // origin of |url| is a valid Gaia sign in origin. This function is meant + // to called only from the IO thread. + static Offer CanOfferOnIOThreadImpl(const GURL& url, + base::SupportsUserData* request, + ProfileIOData* io_data); + + // The portion of ShowInfoBarIfPossible() that needs to run on the UI thread. + // |session_index| and |email| are extracted from the Google-Accounts-SignIn + // header. |auto_accept| is extracted from the Google-Chrome-SignIn header. + // |source| is used to determine which of the explicit sign in mechanism is + // being used. + // + // |continue_url| is where Gaia will continue to when the sign in process is + // done. For explicit sign ins, this is a URL chrome controls. For one-click + // sign in, this could be any google property. This URL is used to know + // when the sign process is over and to collect infomation from the user + // entered on the Gaia sign in page (for explicit sign ins). + static void ShowInfoBarUIThread(const std::string& session_index, + const std::string& email, + AutoAccept auto_accept, + signin_metrics::Source source, + const GURL& continue_url, + int child_id, + int route_id); + + void RedirectToSignin(); + + // Clear all data member of the helper, except for the error. + void CleanTransientState(); + + // Unitests that use a TestingProfile should call this. + // Otherwise, clearing the pending e-mail crashes because the code expects + // a real ResourceContext rather than the MockResourceContext a + // TestingProfile provides. + void SetDoNotClearPendingEmailForTesting(); + + // In unit tests, disable starting the actual sync. + void set_do_not_start_sync_for_testing(); + + // Called when password has been submitted. + void PasswordSubmitted(const autofill::PasswordForm& form); + + // content::WebContentsObserver overrides. + void DidStartNavigationToPendingEntry( + const GURL& url, + content::NavigationController::ReloadType reload_type) override; + void DidNavigateMainFrame( + const content::LoadCommittedDetails& details, + const content::FrameNavigateParams& params) override; + void DidStopLoading(content::RenderViewHost* render_view_host) override; + + // Tracks if we are in the process of showing the signin or one click + // interstitial page. It's set to true the first time we load one of those + // pages and set to false when transient state is cleaned. + // Note: This should only be used for logging purposes. + bool showing_signin_; + + // Information about the account that has just logged in. + std::string session_index_; + std::string email_; + std::string password_; + AutoAccept auto_accept_; + signin_metrics::Source source_; + bool switched_to_advanced_; + GURL continue_url_; + // The orignal continue URL after sync setup is complete. + GURL original_continue_url_; + std::string error_message_; + + // Number of navigations since starting a sign in that is outside the + // the set of trusted Gaia URLs. Sign in attempts that include visits to + // one more untrusted will cause a modal dialog to appear asking the user + // to confirm, similar to the interstitial flow. + int untrusted_navigations_since_signin_visit_; + + // Whether a Gaia URL during the sign in process was not handled by the + // dedicated sign in process (e.g. SAML login, which redirects to a + // non-google-controlled domain). + // This is set to true if at least one such URL is detected. + bool untrusted_confirmation_required_; + + // Allows unittests to avoid accessing the ResourceContext for clearing a + // pending e-mail. + bool do_not_clear_pending_email_; + + // Allows unittest to avoid starting sync for real. + bool do_not_start_sync_for_testing_; + + base::WeakPtrFactory weak_pointer_factory_; + + DISALLOW_COPY_AND_ASSIGN(OneClickSigninHelper); +}; + +#endif // CHROME_BROWSER_UI_SYNC_ONE_CLICK_SIGNIN_HELPER_H_ diff --git a/chrome/browser/ui/sync/one_click_signin_helper_unittest.cc b/chrome/browser/ui/sync/one_click_signin_helper_unittest.cc new file mode 100644 index 000000000000..ebae62fa0a68 --- /dev/null +++ b/chrome/browser/ui/sync/one_click_signin_helper_unittest.cc @@ -0,0 +1,986 @@ +// 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 "base/prefs/pref_service.h" +#include "base/prefs/scoped_user_pref_update.h" +#include "base/run_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/content_settings/cookie_settings.h" +#include "chrome/browser/custom_handlers/protocol_handler_registry.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/signin/chrome_signin_client.h" +#include "chrome/browser/signin/chrome_signin_client_factory.h" +#include "chrome/browser/signin/fake_profile_oauth2_token_service.h" +#include "chrome/browser/signin/fake_profile_oauth2_token_service_builder.h" +#include "chrome/browser/signin/fake_signin_manager.h" +#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" +#include "chrome/browser/signin/signin_manager_factory.h" +#include "chrome/browser/signin/signin_names_io_thread.h" +#include "chrome/browser/signin/signin_promo.h" +#include "chrome/browser/sync/profile_sync_components_factory_mock.h" +#include "chrome/browser/sync/profile_sync_service_factory.h" +#include "chrome/browser/sync/test_profile_sync_service.h" +#include "chrome/browser/ui/sync/one_click_signin_helper.h" +#include "chrome/browser/ui/webui/signin/login_ui_service.h" +#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" +#include "chrome/common/pref_names.h" +#include "chrome/grit/chromium_strings.h" +#include "chrome/grit/generated_resources.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_pref_service_syncable.h" +#include "chrome/test/base/testing_profile.h" +#include "chrome/test/base/testing_profile_manager.h" +#include "components/autofill/core/common/password_form.h" +#include "components/signin/core/browser/profile_oauth2_token_service.h" +#include "components/signin/core/browser/signin_manager.h" +#include "components/sync_driver/pref_names.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/navigation_details.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_delegate.h" +#include "content/public/common/frame_navigate_params.h" +#include "content/public/common/url_constants.h" +#include "content/public/test/mock_render_process_host.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/l10n/l10n_util.h" + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::Return; + +namespace { + +// Used to confirm OneClickSigninHelper does not trigger redirect when there is +// a pending navigation. +class MockWebContentsDelegate : public content::WebContentsDelegate { + public: + MOCK_METHOD2(OpenURLFromTab, + content::WebContents*(content::WebContents* source, + const content::OpenURLParams& params)); +}; + +class SigninManagerMock : public FakeSigninManager { + public: + explicit SigninManagerMock(Profile* profile) : FakeSigninManager(profile) { + Initialize(NULL); + } + MOCK_CONST_METHOD1(IsAllowedUsername, bool(const std::string& username)); +}; + +static KeyedService* BuildSigninManagerMock(content::BrowserContext* profile) { + return new SigninManagerMock(static_cast(profile)); +} + +class TestProfileIOData : public ProfileIOData { + public: + TestProfileIOData(Profile::ProfileType profile_type, + PrefService* pref_service, PrefService* local_state, + CookieSettings* cookie_settings) + : ProfileIOData(profile_type) { + // Initialize the IO members required for these tests, but keep them on + // this thread since we don't use a background thread here. + google_services_username()->Init(prefs::kGoogleServicesUsername, + pref_service); + reverse_autologin_enabled()->Init(prefs::kReverseAutologinEnabled, + pref_service); + one_click_signin_rejected_email_list()->Init( + prefs::kReverseAutologinRejectedEmailList, pref_service); + + google_services_username_pattern()->Init( + prefs::kGoogleServicesUsernamePattern, local_state); + + sync_disabled()->Init(sync_driver::prefs::kSyncManaged, pref_service); + + signin_allowed()->Init(prefs::kSigninAllowed, pref_service); + + set_signin_names_for_testing(new SigninNamesOnIOThread()); + SetCookieSettingsForTesting(cookie_settings); + } + + ~TestProfileIOData() override { + signin_names()->ReleaseResourcesOnUIThread(); + } + + // ProfileIOData overrides: + void InitializeInternal( + scoped_ptr chrome_network_delegate, + ProfileParams* profile_params, + content::ProtocolHandlerMap* protocol_handlers, + content::URLRequestInterceptorScopedVector + request_interceptors) const override { + NOTREACHED(); + } + void InitializeExtensionsRequestContext( + ProfileParams* profile_params) const override { + NOTREACHED(); + } + net::URLRequestContext* InitializeAppRequestContext( + net::URLRequestContext* main_context, + const StoragePartitionDescriptor& details, + scoped_ptr + protocol_handler_interceptor, + content::ProtocolHandlerMap* protocol_handlers, + content::URLRequestInterceptorScopedVector request_interceptors) + const override { + NOTREACHED(); + return NULL; + } + net::URLRequestContext* InitializeMediaRequestContext( + net::URLRequestContext* original_context, + const StoragePartitionDescriptor& details) const override { + NOTREACHED(); + return NULL; + } + net::URLRequestContext* AcquireMediaRequestContext() const override { + NOTREACHED(); + return NULL; + } + net::URLRequestContext* AcquireIsolatedAppRequestContext( + net::URLRequestContext* main_context, + const StoragePartitionDescriptor& partition_descriptor, + scoped_ptr + protocol_handler_interceptor, + content::ProtocolHandlerMap* protocol_handlers, + content::URLRequestInterceptorScopedVector request_interceptors) + const override { + NOTREACHED(); + return NULL; + } + net::URLRequestContext* AcquireIsolatedMediaRequestContext( + net::URLRequestContext* app_context, + const StoragePartitionDescriptor& partition_descriptor) const override { + NOTREACHED(); + return NULL; + } +}; + +class TestURLRequest : public base::SupportsUserData { + public: + TestURLRequest() {} + ~TestURLRequest() override {} +}; + +class OneClickTestProfileSyncService : public TestProfileSyncService { + public: + ~OneClickTestProfileSyncService() override {} + + // Helper routine to be used in conjunction with + // BrowserContextKeyedServiceFactory::SetTestingFactory(). + static KeyedService* Build(content::BrowserContext* profile) { + return new OneClickTestProfileSyncService(static_cast(profile)); + } + + // Need to control this for certain tests. + bool FirstSetupInProgress() const override { + return first_setup_in_progress_; + } + + bool SyncActive() const override { return sync_active_; } + + // Controls return value of FirstSetupInProgress. Because some bits + // of UI depend on that value, it's useful to control it separately + // from the internal work and components that are triggered (such as + // ReconfigureDataTypeManager) to facilitate unit tests. + void set_first_setup_in_progress(bool in_progress) { + first_setup_in_progress_ = in_progress; + } + + void set_sync_active(bool active) { + sync_active_ = active; + } + + private: + explicit OneClickTestProfileSyncService(Profile* profile) + : TestProfileSyncService( + scoped_ptr( + new ProfileSyncComponentsFactoryMock()), + profile, + SigninManagerFactory::GetForProfile(profile), + ProfileOAuth2TokenServiceFactory::GetForProfile(profile), + browser_sync::MANUAL_START), + first_setup_in_progress_(false), + sync_active_(false) {} + + bool first_setup_in_progress_; + bool sync_active_; +}; + +} // namespace + +class OneClickSigninHelperTest : public ChromeRenderViewHostTestHarness { + public: + OneClickSigninHelperTest(); + + void SetUp() override; + void TearDown() override; + + // Sets up the sign-in manager for tests. If |username| is + // is not empty, the profile of the mock WebContents will be connected to + // the given account. + void SetUpSigninManager(const std::string& username); + + // Set the ID of the signin process that the test will assume to be the + // only process allowed to sign the user in to Chrome. + void SetTrustedSigninProcessID(int id); + + void AddEmailToOneClickRejectedList(const std::string& email); + void EnableOneClick(bool enable); + void AllowSigninCookies(bool enable); + void SetAllowedUsernamePattern(const std::string& pattern); + void SubmitGAIAPassword(OneClickSigninHelper* helper); + + SigninManagerMock* signin_manager_; + FakeProfileOAuth2TokenService* fake_oauth2_token_service_; + + protected: + GoogleServiceAuthError no_error_; + + private: + // ChromeRenderViewHostTestHarness overrides: + content::BrowserContext* CreateBrowserContext() override; + + // The ID of the signin process the test will assume to be trusted. + // By default, set to the test RenderProcessHost's process ID, but + // overridden by SetTrustedSigninProcessID. + int trusted_signin_process_id_; + + DISALLOW_COPY_AND_ASSIGN(OneClickSigninHelperTest); +}; + +OneClickSigninHelperTest::OneClickSigninHelperTest() + : signin_manager_(NULL), + fake_oauth2_token_service_(NULL), + no_error_(GoogleServiceAuthError::NONE), + trusted_signin_process_id_(-1) { +} + +void OneClickSigninHelperTest::SetUp() { + signin::ForceWebBasedSigninFlowForTesting(true); + content::RenderViewHostTestHarness::SetUp(); + SetTrustedSigninProcessID(process()->GetID()); +} + +void OneClickSigninHelperTest::TearDown() { + signin::ForceWebBasedSigninFlowForTesting(false); + content::RenderViewHostTestHarness::TearDown(); +} + +void OneClickSigninHelperTest::SetTrustedSigninProcessID(int id) { + trusted_signin_process_id_ = id; +} + +void OneClickSigninHelperTest::SetUpSigninManager(const std::string& username) { + SigninClient* signin_client = + ChromeSigninClientFactory::GetForProfile(profile()); + if (signin_client) + signin_client->SetSigninProcess(trusted_signin_process_id_); + + signin_manager_ = static_cast( + SigninManagerFactory::GetForProfile(profile())); + if (!username.empty()) { + ASSERT_TRUE(signin_manager_); + signin_manager_->SetAuthenticatedUsername(username); + } +} + +void OneClickSigninHelperTest::EnableOneClick(bool enable) { + PrefService* pref_service = profile()->GetPrefs(); + pref_service->SetBoolean(prefs::kReverseAutologinEnabled, enable); +} + +void OneClickSigninHelperTest::AddEmailToOneClickRejectedList( + const std::string& email) { + PrefService* pref_service = profile()->GetPrefs(); + ListPrefUpdate updater(pref_service, + prefs::kReverseAutologinRejectedEmailList); + updater->AppendIfNotPresent(new base::StringValue(email)); +} + +void OneClickSigninHelperTest::AllowSigninCookies(bool enable) { + CookieSettings* cookie_settings = + CookieSettings::Factory::GetForProfile(profile()).get(); + cookie_settings->SetDefaultCookieSetting(enable ? CONTENT_SETTING_ALLOW + : CONTENT_SETTING_BLOCK); +} + +void OneClickSigninHelperTest::SetAllowedUsernamePattern( + const std::string& pattern) { + PrefService* local_state = g_browser_process->local_state(); + local_state->SetString(prefs::kGoogleServicesUsernamePattern, pattern); +} + +void OneClickSigninHelperTest::SubmitGAIAPassword( + OneClickSigninHelper* helper) { + autofill::PasswordForm password_form; + password_form.origin = GURL("https://accounts.google.com"); + password_form.signon_realm = "https://accounts.google.com"; + password_form.password_value = base::UTF8ToUTF16("password"); + helper->PasswordSubmitted(password_form); +} + +content::BrowserContext* OneClickSigninHelperTest::CreateBrowserContext() { + TestingProfile::Builder builder; + builder.AddTestingFactory(ProfileOAuth2TokenServiceFactory::GetInstance(), + BuildFakeProfileOAuth2TokenService); + builder.AddTestingFactory(SigninManagerFactory::GetInstance(), + BuildSigninManagerMock); + scoped_ptr profile = builder.Build(); + + fake_oauth2_token_service_ = + static_cast( + ProfileOAuth2TokenServiceFactory::GetForProfile(profile.get())); + + return profile.release(); +} + +class OneClickSigninHelperIOTest : public OneClickSigninHelperTest { + public: + OneClickSigninHelperIOTest(); + + void SetUp() override; + + TestProfileIOData* CreateTestProfileIOData(Profile::ProfileType profile_type); + + protected: + TestingProfileManager testing_profile_manager_; + TestURLRequest request_; + const GURL valid_gaia_url_; + + private: + DISALLOW_COPY_AND_ASSIGN(OneClickSigninHelperIOTest); +}; + +OneClickSigninHelperIOTest::OneClickSigninHelperIOTest() + : testing_profile_manager_( + TestingBrowserProcess::GetGlobal()), + valid_gaia_url_("https://accounts.google.com/") { +} + +void OneClickSigninHelperIOTest::SetUp() { + OneClickSigninHelperTest::SetUp(); + ASSERT_TRUE(testing_profile_manager_.SetUp()); +} + +TestProfileIOData* OneClickSigninHelperIOTest::CreateTestProfileIOData( + Profile::ProfileType profile_type) { + PrefService* pref_service = profile()->GetPrefs(); + PrefService* local_state = g_browser_process->local_state(); + CookieSettings* cookie_settings = + CookieSettings::Factory::GetForProfile(profile()).get(); + TestProfileIOData* io_data = new TestProfileIOData( + profile_type, pref_service, local_state, cookie_settings); + io_data->set_reverse_autologin_pending_email("user@gmail.com"); + return io_data; +} + +class OneClickSigninHelperIncognitoTest : public OneClickSigninHelperTest { + protected: + // content::RenderViewHostTestHarness. + content::BrowserContext* CreateBrowserContext() override; +}; + +content::BrowserContext* +OneClickSigninHelperIncognitoTest::CreateBrowserContext() { + // Simulate an incognito profile to run this test. RenderViewHostTestHarness + // takes ownership of the return value, so it can't be a "proper" incognito + // profile, since they are owned by their parent, non-incognito profile. + scoped_ptr profile = TestingProfile::Builder().Build(); + profile->ForceIncognito(true); + return profile.release(); +} + +TEST_F(OneClickSigninHelperTest, CanOfferNoContents) { + std::string error_message; + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + NULL, OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "user@gmail.com", &error_message)); + EXPECT_EQ("", error_message); + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + NULL, OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "user@gmail.com", &error_message)); + EXPECT_EQ("", error_message); + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + NULL, + OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + std::string(), + &error_message)); + EXPECT_EQ("", error_message); +} + +TEST_F(OneClickSigninHelperTest, CanOffer) { + SetUpSigninManager(std::string()); + + EXPECT_CALL(*signin_manager_, IsAllowedUsername(_)). + WillRepeatedly(Return(true)); + + EnableOneClick(true); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "user@gmail.com", NULL)); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "user@gmail.com", NULL)); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), + OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + std::string(), + NULL)); + + EnableOneClick(false); + + std::string error_message; + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "user@gmail.com", &error_message)); + EXPECT_EQ("", error_message); + + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "user@gmail.com", &error_message)); + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), + OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + std::string(), + &error_message)); + EXPECT_EQ("", error_message); +} + +TEST_F(OneClickSigninHelperTest, CanOfferFirstSetup) { + SetUpSigninManager(std::string()); + + EXPECT_CALL(*signin_manager_, IsAllowedUsername(_)). + WillRepeatedly(Return(true)); + + // Invoke OneClickTestProfileSyncService factory function and grab result. + OneClickTestProfileSyncService* sync = + static_cast( + ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse( + profile(), OneClickTestProfileSyncService::Build)); + sync->set_sync_active(false); + sync->Initialize(); + sync->set_sync_active(true); + sync->set_first_setup_in_progress(true); + + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), + OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "foo@gmail.com", NULL)); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), + OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "foo@gmail.com", NULL)); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), + OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + std::string(), + NULL)); +} + +TEST_F(OneClickSigninHelperTest, CanOfferProfileConnected) { + SetUpSigninManager("foo@gmail.com"); + + EXPECT_CALL(*signin_manager_, IsAllowedUsername(_)). + WillRepeatedly(Return(true)); + + std::string error_message; + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "foo@gmail.com", &error_message)); + EXPECT_EQ("", error_message); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "foo", &error_message)); + EXPECT_EQ("", error_message); + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "user@gmail.com", &error_message)); + EXPECT_EQ(l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL, + base::UTF8ToUTF16("foo@gmail.com")), + error_message); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "foo@gmail.com", &error_message)); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "foo", &error_message)); + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "user@gmail.com", &error_message)); + EXPECT_EQ(l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL, + base::UTF8ToUTF16("foo@gmail.com")), + error_message); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), + OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + std::string(), + &error_message)); +} + +TEST_F(OneClickSigninHelperTest, CanOfferUsernameNotAllowed) { + SetUpSigninManager(std::string()); + + EXPECT_CALL(*signin_manager_, IsAllowedUsername(_)). + WillRepeatedly(Return(false)); + + std::string error_message; + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "foo@gmail.com", &error_message)); + EXPECT_EQ(l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED), + error_message); + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "foo@gmail.com", &error_message)); + EXPECT_EQ(l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED), + error_message); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), + OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + std::string(), + &error_message)); +} + +TEST_F(OneClickSigninHelperTest, CanOfferWithRejectedEmail) { + SetUpSigninManager(std::string()); + + EXPECT_CALL(*signin_manager_, IsAllowedUsername(_)). + WillRepeatedly(Return(true)); + + AddEmailToOneClickRejectedList("foo@gmail.com"); + AddEmailToOneClickRejectedList("user@gmail.com"); + + std::string error_message; + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "foo@gmail.com", &error_message)); + EXPECT_EQ("", error_message); + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "user@gmail.com", &error_message)); + EXPECT_EQ("", error_message); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "foo@gmail.com", &error_message)); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "user@gmail.com", &error_message)); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "john@gmail.com", &error_message)); +} + +TEST_F(OneClickSigninHelperIncognitoTest, CanOfferIncognito) { + SetUpSigninManager(std::string()); + + std::string error_message; + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "user@gmail.com", &error_message)); + EXPECT_EQ("", error_message); + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "user@gmail.com", &error_message)); + EXPECT_EQ("", error_message); + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), + OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + std::string(), + &error_message)); + EXPECT_EQ("", error_message); +} + +TEST_F(OneClickSigninHelperTest, CanOfferNoSigninCookies) { + SetUpSigninManager(std::string()); + AllowSigninCookies(false); + + EXPECT_CALL(*signin_manager_, IsAllowedUsername(_)). + WillRepeatedly(Return(true)); + + std::string error_message; + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + "user@gmail.com", &error_message)); + EXPECT_EQ("", error_message); + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "user@gmail.com", &error_message)); + EXPECT_EQ("", error_message); + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), + OneClickSigninHelper::CAN_OFFER_FOR_INTERSTITAL_ONLY, + std::string(), + &error_message)); + EXPECT_EQ("", error_message); +} + +TEST_F(OneClickSigninHelperTest, CanOfferDisabledByPolicy) { + SetUpSigninManager(std::string()); + + EXPECT_CALL(*signin_manager_, IsAllowedUsername(_)). + WillRepeatedly(Return(true)); + + EnableOneClick(true); + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "user@gmail.com", NULL)); + + // Simulate a policy disabling signin by writing kSigninAllowed directly. + profile()->GetTestingPrefService()->SetManagedPref( + prefs::kSigninAllowed, new base::FundamentalValue(false)); + + EXPECT_FALSE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "user@gmail.com", NULL)); + + // Reset the preference value to true. + profile()->GetTestingPrefService()->SetManagedPref( + prefs::kSigninAllowed, new base::FundamentalValue(true)); + + // Simulate a policy disabling sync by writing kSyncManaged directly. + profile()->GetTestingPrefService()->SetManagedPref( + sync_driver::prefs::kSyncManaged, new base::FundamentalValue(true)); + + // Should still offer even if sync is disabled by policy. + EXPECT_TRUE(OneClickSigninHelper::CanOffer( + web_contents(), OneClickSigninHelper::CAN_OFFER_FOR_ALL, + "user@gmail.com", NULL)); +} + +// Should not crash if a helper instance is not associated with an incognito +// web contents. +TEST_F(OneClickSigninHelperIncognitoTest, ShowInfoBarUIThreadIncognito) { + SetUpSigninManager(std::string()); + OneClickSigninHelper* helper = + OneClickSigninHelper::FromWebContents(web_contents()); + EXPECT_EQ(NULL, helper); + + OneClickSigninHelper::ShowInfoBarUIThread( + "session_index", "email", OneClickSigninHelper::AUTO_ACCEPT_ACCEPTED, + signin_metrics::SOURCE_UNKNOWN, GURL(), process()->GetID(), + rvh()->GetRoutingID()); +} + +// Checks that the state of OneClickSigninHelper is cleaned when there is a +// navigation away from the sign in flow that is not triggered by the +// web contents. +TEST_F(OneClickSigninHelperTest, CleanTransientStateOnNavigate) { + content::WebContents* contents = web_contents(); + + OneClickSigninHelper::CreateForWebContents(contents); + OneClickSigninHelper* helper = + OneClickSigninHelper::FromWebContents(contents); + helper->SetDoNotClearPendingEmailForTesting(); + helper->auto_accept_ = OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT; + + content::LoadCommittedDetails details; + content::FrameNavigateParams params; + params.url = GURL("http://crbug.com"); + params.transition = ui::PAGE_TRANSITION_TYPED; + helper->DidNavigateMainFrame(details, params); + + EXPECT_EQ(OneClickSigninHelper::AUTO_ACCEPT_NONE, helper->auto_accept_); +} + +TEST_F(OneClickSigninHelperTest, NoRedirectToNTPWithPendingEntry) { + content::NavigationController& controller = web_contents()->GetController(); + EXPECT_FALSE(controller.GetPendingEntry()); + + const GURL fooWebUIURL("chrome://foo"); + controller.LoadURL(fooWebUIURL, content::Referrer(), + ui::PAGE_TRANSITION_TYPED, std::string()); + EXPECT_EQ(fooWebUIURL, controller.GetPendingEntry()->GetURL()); + + MockWebContentsDelegate delegate; + EXPECT_CALL(delegate, OpenURLFromTab(_, _)).Times(0); + web_contents()->SetDelegate(&delegate); + OneClickSigninHelper::RedirectToNtpOrAppsPage( + web_contents(), signin_metrics::SOURCE_UNKNOWN); + + EXPECT_EQ(fooWebUIURL, controller.GetPendingEntry()->GetURL()); +} + +// I/O thread tests + +TEST_F(OneClickSigninHelperIOTest, CanOfferOnIOThread) { + scoped_ptr io_data( + CreateTestProfileIOData(Profile::REGULAR_PROFILE)); + EXPECT_EQ(OneClickSigninHelper::CAN_OFFER, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + valid_gaia_url_, &request_, io_data.get())); +} + +TEST_F(OneClickSigninHelperIOTest, CanOfferOnIOThreadIncognito) { + scoped_ptr io_data( + CreateTestProfileIOData(Profile::INCOGNITO_PROFILE)); + EXPECT_EQ(OneClickSigninHelper::DONT_OFFER, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + valid_gaia_url_, &request_, io_data.get())); +} + +TEST_F(OneClickSigninHelperIOTest, CanOfferOnIOThreadNoIOData) { + EXPECT_EQ(OneClickSigninHelper::DONT_OFFER, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + valid_gaia_url_, &request_, NULL)); +} + +TEST_F(OneClickSigninHelperIOTest, CanOfferOnIOThreadBadURL) { + scoped_ptr io_data( + CreateTestProfileIOData(Profile::REGULAR_PROFILE)); + EXPECT_EQ( + OneClickSigninHelper::IGNORE_REQUEST, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + GURL("https://foo.com/"), &request_, io_data.get())); + EXPECT_EQ(OneClickSigninHelper::IGNORE_REQUEST, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + GURL("http://accounts.google.com/"), + &request_, + io_data.get())); +} + +TEST_F(OneClickSigninHelperIOTest, CanOfferOnIOThreadDisabled) { + EnableOneClick(false); + scoped_ptr io_data( + CreateTestProfileIOData(Profile::REGULAR_PROFILE)); + EXPECT_EQ(OneClickSigninHelper::DONT_OFFER, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + valid_gaia_url_, &request_, io_data.get())); +} + +TEST_F(OneClickSigninHelperIOTest, CanOfferOnIOThreadSignedIn) { + PrefService* pref_service = profile()->GetPrefs(); + pref_service->SetString(prefs::kGoogleServicesUsername, "user@gmail.com"); + + scoped_ptr io_data( + CreateTestProfileIOData(Profile::REGULAR_PROFILE)); + EXPECT_EQ(OneClickSigninHelper::DONT_OFFER, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + valid_gaia_url_, &request_, io_data.get())); +} + +TEST_F(OneClickSigninHelperIOTest, CanOfferOnIOThreadEmailNotAllowed) { + SetAllowedUsernamePattern("*@example.com"); + scoped_ptr io_data( + CreateTestProfileIOData(Profile::REGULAR_PROFILE)); + EXPECT_EQ(OneClickSigninHelper::DONT_OFFER, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + valid_gaia_url_, &request_, io_data.get())); +} + +TEST_F(OneClickSigninHelperIOTest, CanOfferOnIOThreadEmailAlreadyUsed) { + ProfileInfoCache* cache = testing_profile_manager_.profile_info_cache(); + const base::FilePath& user_data_dir = cache->GetUserDataDir(); + cache->AddProfileToCache(user_data_dir.Append(FILE_PATH_LITERAL("user")), + base::UTF8ToUTF16("user"), + base::UTF8ToUTF16("user@gmail.com"), 0, + std::string()); + + scoped_ptr io_data( + CreateTestProfileIOData(Profile::REGULAR_PROFILE)); + EXPECT_EQ(OneClickSigninHelper::DONT_OFFER, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + valid_gaia_url_, &request_, io_data.get())); +} + +TEST_F(OneClickSigninHelperIOTest, CanOfferOnIOThreadWithRejectedEmail) { + AddEmailToOneClickRejectedList("user@gmail.com"); + scoped_ptr io_data( + CreateTestProfileIOData(Profile::REGULAR_PROFILE)); + EXPECT_EQ(OneClickSigninHelper::DONT_OFFER, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + valid_gaia_url_, &request_, io_data.get())); +} + +TEST_F(OneClickSigninHelperIOTest, CanOfferOnIOThreadNoSigninCookies) { + AllowSigninCookies(false); + scoped_ptr io_data( + CreateTestProfileIOData(Profile::REGULAR_PROFILE)); + EXPECT_EQ(OneClickSigninHelper::DONT_OFFER, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + valid_gaia_url_, &request_, io_data.get())); +} + +TEST_F(OneClickSigninHelperIOTest, CanOfferOnIOThreadDisabledByPolicy) { + scoped_ptr io_data( + CreateTestProfileIOData(Profile::REGULAR_PROFILE)); + EXPECT_EQ(OneClickSigninHelper::CAN_OFFER, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + valid_gaia_url_, &request_, io_data.get())); + + // Simulate a policy disabling signin by writing kSigninAllowed directly. + // We should not offer to sign in the browser. + profile()->GetTestingPrefService()->SetManagedPref( + prefs::kSigninAllowed, new base::FundamentalValue(false)); + EXPECT_EQ(OneClickSigninHelper::DONT_OFFER, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + valid_gaia_url_, &request_, io_data.get())); + + // Reset the preference. + profile()->GetTestingPrefService()->SetManagedPref( + prefs::kSigninAllowed, new base::FundamentalValue(true)); + + // Simulate a policy disabling sync by writing kSyncManaged directly. + // We should still offer to sign in the browser. + profile()->GetTestingPrefService()->SetManagedPref( + sync_driver::prefs::kSyncManaged, new base::FundamentalValue(true)); + EXPECT_EQ(OneClickSigninHelper::CAN_OFFER, + OneClickSigninHelper::CanOfferOnIOThreadImpl( + valid_gaia_url_, &request_, io_data.get())); +} + + +class MockStarterWrapper + : public testing::StrictMock { + public: + MockStarterWrapper( + const OneClickSigninHelper::StartSyncArgs& args, + OneClickSigninSyncStarter::StartSyncMode start_mode); + + MOCK_METHOD1(DisplayErrorBubble, void(const std::string& error_message)); + MOCK_METHOD0(StartSigninOAuthHelper, void()); + MOCK_METHOD2(StartOneClickSigninSyncStarter, + void(const std::string& email, + const std::string& refresh_token)); +}; + +MockStarterWrapper::MockStarterWrapper( + const OneClickSigninHelper::StartSyncArgs& args, + OneClickSigninSyncStarter::StartSyncMode start_mode) + : testing::StrictMock( + args, start_mode) { +} + +class OneClickSyncStarterWrapperTest : public testing::Test { + public: + void SetUp() override { + TestingProfile::Builder builder; + profile_ = builder.Build(); + } + + void TearDown() override { + // Let the SyncStarterWrapper delete itself. + base::RunLoop().RunUntilIdle(); + } + + void SetCookie(const std::string& value) { + // Set a valid LSID cookie in the test cookie store. + scoped_refptr cookie_monster = + profile()->GetCookieMonster(); + net::CookieOptions options; + options.set_include_httponly(); + cookie_monster->SetCookieWithOptionsAsync( + GURL("https://accounts.google.com"), + value, options, + net::CookieMonster::SetCookiesCallback()); + } + + void SimulateRefreshTokenFetched( + SigninOAuthHelper::Consumer* consumer, + const std::string& email, + const std::string& display_email, + const std::string& refresh_token) { + consumer->OnSigninOAuthInformationAvailable( + email, display_email, refresh_token); + } + + TestingProfile* profile() { return profile_.get(); } + + private: + content::TestBrowserThreadBundle thread_bundle_; + scoped_ptr profile_; +}; + +TEST_F(OneClickSyncStarterWrapperTest, SignInWithRefreshToken) { + OneClickSigninHelper::StartSyncArgs args; + args.email = "foo@gmail.com"; + args.password = "password"; + args.refresh_token = "refresh_token"; + MockStarterWrapper* wrapper = new MockStarterWrapper( + args, OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS); + + EXPECT_CALL(*wrapper, + StartOneClickSigninSyncStarter("foo@gmail.com", + "refresh_token")); + wrapper->Start(); +} + +TEST_F(OneClickSyncStarterWrapperTest, SignInWithPasswordNoRefreshToken) { + OneClickSigninHelper::StartSyncArgs args; + args.email = "foo@gmail.com"; + args.password = "password"; + MockStarterWrapper* wrapper = new MockStarterWrapper( + args, OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS); + + EXPECT_CALL(*wrapper, StartSigninOAuthHelper()); + EXPECT_CALL(*wrapper, + StartOneClickSigninSyncStarter("foo@gmail.com", + "refresh_token")); + wrapper->Start(); + SimulateRefreshTokenFetched(wrapper, "foo@gmail.com", "foo@gmail.com", + "refresh_token"); +} + +TEST_F(OneClickSyncStarterWrapperTest, SignInWithWrongEmail) { + OneClickSigninHelper::StartSyncArgs args; + args.email = "foo@gmail.com"; + args.password = "password"; + MockStarterWrapper* wrapper = new MockStarterWrapper( + args, OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS); + + EXPECT_CALL(*wrapper, StartSigninOAuthHelper()); + EXPECT_CALL(*wrapper, DisplayErrorBubble(_)); + wrapper->Start(); + SimulateRefreshTokenFetched(wrapper, "bar@gmail.com", "bar@gmail.com", + "refresh_token"); +} + +TEST_F(OneClickSyncStarterWrapperTest, SignInWithEmptyPasswordValidCookie) { + OneClickSigninHelper::StartSyncArgs args; + args.email = "foo@gmail.com"; + args.profile = profile(); + MockStarterWrapper* wrapper = new MockStarterWrapper( + args, OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS); + SetCookie("LSID=1234; secure; httponly"); + + EXPECT_CALL(*wrapper, StartSigninOAuthHelper()); + EXPECT_CALL(*wrapper, + StartOneClickSigninSyncStarter("foo@gmail.com", + "refresh_token")); + wrapper->Start(); + base::RunLoop().RunUntilIdle(); + SimulateRefreshTokenFetched(wrapper, "foo@gmail.com", "foo@gmail.com", + "refresh_token"); +} + +TEST_F(OneClickSyncStarterWrapperTest, SignInWithEmptyPasswordNoCookie) { + OneClickSigninHelper::StartSyncArgs args; + args.email = "foo@gmail.com"; + args.profile = profile(); + MockStarterWrapper* wrapper = new MockStarterWrapper( + args, OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS); + + EXPECT_CALL(*wrapper, DisplayErrorBubble(_)); + wrapper->Start(); + base::RunLoop().RunUntilIdle(); +} + +TEST_F(OneClickSyncStarterWrapperTest, SignInWithEmptyPasswordInvalidCookie) { + OneClickSigninHelper::StartSyncArgs args; + args.email = "foo@gmail.com"; + args.profile = profile(); + MockStarterWrapper* wrapper = new MockStarterWrapper( + args, OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS); + SetCookie("LSID=1234; domain=google.com; secure; httponly"); + + EXPECT_CALL(*wrapper, DisplayErrorBubble(_)); + wrapper->Start(); + base::RunLoop().RunUntilIdle(); +} diff --git a/chrome/browser/ui/views/sync/one_click_signin_bubble_view.cc b/chrome/browser/ui/views/sync/one_click_signin_bubble_view.cc index d2e631ffd025..07baef1c5531 100644 --- a/chrome/browser/ui/views/sync/one_click_signin_bubble_view.cc +++ b/chrome/browser/ui/views/sync/one_click_signin_bubble_view.cc @@ -7,6 +7,7 @@ #include "base/callback_helpers.h" #include "base/logging.h" #include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/sync/one_click_signin_helper.h" #include "chrome/common/url_constants.h" #include "chrome/grit/chromium_strings.h" #include "chrome/grit/generated_resources.h" @@ -207,7 +208,7 @@ void OneClickSigninBubbleView::InitBubbleContent(views::GridLayout* layout) { } void OneClickSigninBubbleView::InitDialogContent(views::GridLayout* layout) { - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( signin_metrics::HISTOGRAM_CONFIRM_SHOWN); // Column set for title bar. @@ -318,7 +319,7 @@ bool OneClickSigninBubbleView::AcceleratorPressed( if (is_sync_dialog_) { if (accelerator.key_code() == ui::VKEY_RETURN) { - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( clicked_learn_more_ ? signin_metrics::HISTOGRAM_CONFIRM_LEARN_MORE_RETURN : signin_metrics::HISTOGRAM_CONFIRM_RETURN); @@ -326,7 +327,7 @@ bool OneClickSigninBubbleView::AcceleratorPressed( base::ResetAndReturn(&start_sync_callback_).Run( OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS); } else if (accelerator.key_code() == ui::VKEY_ESCAPE) { - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( clicked_learn_more_ ? signin_metrics::HISTOGRAM_CONFIRM_LEARN_MORE_ESCAPE : signin_metrics::HISTOGRAM_CONFIRM_ESCAPE); @@ -346,7 +347,7 @@ void OneClickSigninBubbleView::LinkClicked(views::Link* source, int event_flags) { if (source == learn_more_link_) { if (is_sync_dialog_ && !clicked_learn_more_) { - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( signin_metrics::HISTOGRAM_CONFIRM_LEARN_MORE); clicked_learn_more_ = true; } @@ -357,7 +358,7 @@ void OneClickSigninBubbleView::LinkClicked(views::Link* source, return; } else if (advanced_link_ && source == advanced_link_) { if (is_sync_dialog_) { - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( clicked_learn_more_ ? signin_metrics::HISTOGRAM_CONFIRM_LEARN_MORE_ADVANCED : signin_metrics::HISTOGRAM_CONFIRM_ADVANCED); @@ -378,19 +379,19 @@ void OneClickSigninBubbleView::ButtonPressed(views::Button* sender, if (is_sync_dialog_) { if (sender == ok_button_) - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( clicked_learn_more_ ? signin_metrics::HISTOGRAM_CONFIRM_LEARN_MORE_OK : signin_metrics::HISTOGRAM_CONFIRM_OK); if (sender == undo_button_) - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( clicked_learn_more_ ? signin_metrics::HISTOGRAM_CONFIRM_LEARN_MORE_UNDO : signin_metrics::HISTOGRAM_CONFIRM_UNDO); if (sender == close_button_) - signin_metrics::LogSigninConfirmHistogramValue( + OneClickSigninHelper::LogConfirmHistogramValue( clicked_learn_more_ ? signin_metrics::HISTOGRAM_CONFIRM_LEARN_MORE_CLOSE : signin_metrics::HISTOGRAM_CONFIRM_CLOSE); diff --git a/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc b/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc index b821530415d3..f58baf7535e8 100644 --- a/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc +++ b/chrome/browser/ui/webui/signin/inline_login_handler_impl.cc @@ -7,14 +7,10 @@ #include #include "base/bind.h" -#include "base/callback_helpers.h" -#include "base/metrics/histogram.h" -#include "base/prefs/pref_service.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" -#include "chrome/browser/browser_process.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_window.h" #include "chrome/browser/signin/about_signin_internals_factory.h" @@ -29,23 +25,18 @@ #include "chrome/browser/sync/profile_sync_service_factory.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/tab_modal_confirm_dialog.h" -#include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h" +#include "chrome/browser/ui/sync/one_click_signin_helper.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/webui/signin/inline_login_ui.h" #include "chrome/browser/ui/webui/signin/login_ui_service.h" #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" #include "chrome/common/url_constants.h" -#include "chrome/grit/chromium_strings.h" -#include "chrome/grit/generated_resources.h" #include "components/signin/core/browser/about_signin_internals.h" #include "components/signin/core/browser/account_tracker_service.h" #include "components/signin/core/browser/profile_oauth2_token_service.h" #include "components/signin/core/browser/signin_error_controller.h" #include "components/signin/core/browser/signin_metrics.h" #include "components/signin/core/common/profile_management_switches.h" -#include "components/signin/core/common/signin_pref_names.h" #include "content/public/browser/storage_partition.h" #include "content/public/browser/web_ui.h" #include "google_apis/gaia/gaia_auth_consumer.h" @@ -53,165 +44,10 @@ #include "google_apis/gaia/gaia_auth_util.h" #include "google_apis/gaia/gaia_constants.h" #include "google_apis/gaia/gaia_urls.h" -#include "grit/components_strings.h" #include "net/base/url_util.h" -#include "ui/base/l10n/l10n_util.h" namespace { -void LogHistogramValue(int action) { - UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action, - signin_metrics::HISTOGRAM_MAX); -} - -void RedirectToNtpOrAppsPage(content::WebContents* contents, - signin_metrics::Source source) { - // Do nothing if a navigation is pending, since this call can be triggered - // from DidStartLoading. This avoids deleting the pending entry while we are - // still navigating to it. See crbug/346632. - if (contents->GetController().GetPendingEntry()) - return; - - VLOG(1) << "RedirectToNtpOrAppsPage"; - // Redirect to NTP/Apps page and display a confirmation bubble - GURL url(source == signin_metrics::SOURCE_APPS_PAGE_LINK ? - chrome::kChromeUIAppsURL : chrome::kChromeUINewTabURL); - content::OpenURLParams params(url, - content::Referrer(), - CURRENT_TAB, - ui::PAGE_TRANSITION_AUTO_TOPLEVEL, - false); - contents->OpenURL(params); -} - -void RedirectToNtpOrAppsPageIfNecessary(content::WebContents* contents, - signin_metrics::Source source) { - if (source != signin_metrics::SOURCE_SETTINGS) - RedirectToNtpOrAppsPage(contents, source); -} - -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); - ~ConfirmEmailDialogDelegate() override; - - // TabModalConfirmDialogDelegate: - base::string16 GetTitle() override; - base::string16 GetDialogMessage() override; - base::string16 GetAcceptButtonTitle() override; - base::string16 GetCancelButtonTitle() override; - base::string16 GetLinkText() const override; - void OnAccepted() override; - void OnCanceled() override; - void OnClosed() override; - 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::GetDialogMessage() { - return l10n_util::GetStringFUTF16( - IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_MESSAGE, - base::UTF8ToUTF16(last_email_), base::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, - ui::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); -} - class InlineSigninHelper : public GaiaAuthConsumer { public: InlineSigninHelper( @@ -228,25 +64,6 @@ class InlineSigninHelper : public GaiaAuthConsumer { bool confirm_untrusted_signin); private: - // Handles cross account sign in error. If the supplied |email| does not match - // the last signed in email of the current profile, then Chrome will show a - // confirmation dialog before starting sync. It returns true if there is a - // cross account error, and false otherwise. - bool HandleCrossAccountError( - const std::string& refresh_token, - signin_metrics::Source source, - OneClickSigninSyncStarter::ConfirmationRequired confirmation_required, - OneClickSigninSyncStarter::StartSyncMode start_mode); - - // Callback used with ConfirmEmailDialogDelegate. - void ConfirmEmailAction( - content::WebContents* web_contents, - const std::string& refresh_token, - signin_metrics::Source source, - OneClickSigninSyncStarter::ConfirmationRequired confirmation_required, - OneClickSigninSyncStarter::StartSyncMode start_mode, - ConfirmEmailDialogDelegate::Action action); - // Overridden from GaiaAuthConsumer. void OnClientOAuthSuccess(const ClientOAuthResult& result) override; void OnClientOAuthFailure(const GoogleServiceAuthError& error) @@ -380,8 +197,14 @@ void InlineSigninHelper::OnClientOAuthSuccess(const ClientOAuthResult& result) { OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN; } - bool start_signin = !HandleCrossAccountError(result.refresh_token, source, - confirmation_required, start_mode); + bool start_signin = + !OneClickSigninHelper::HandleCrossAccountError( + profile_, "", + email_, password_, result.refresh_token, + OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT, + source, start_mode, + base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback, + handler_)); if (start_signin) { // Call OneClickSigninSyncStarter to exchange oauth code for tokens. // OneClickSigninSyncStarter will delete itself once the job is done. @@ -393,74 +216,9 @@ void InlineSigninHelper::OnClientOAuthSuccess(const ClientOAuthResult& result) { confirmation_required, signin::GetNextPageURLForPromoURL(current_url_), base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback, handler_)); - base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); } } -} - -bool InlineSigninHelper::HandleCrossAccountError( - const std::string& refresh_token, - signin_metrics::Source source, - OneClickSigninSyncStarter::ConfirmationRequired confirmation_required, - OneClickSigninSyncStarter::StartSyncMode start_mode) { - std::string last_email = - profile_->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername); - - if (last_email.empty() || gaia::AreEmailsSame(last_email, email_)) - return false; - - Browser* browser = chrome::FindLastActiveWithProfile( - profile_, chrome::GetActiveDesktop()); - content::WebContents* web_contents = - browser->tab_strip_model()->GetActiveWebContents(); - - ConfirmEmailDialogDelegate::AskForConfirmation( - web_contents, - last_email, - email_, - base::Bind(&InlineSigninHelper::ConfirmEmailAction, - base::Unretained(this), - web_contents, - refresh_token, - source, - confirmation_required, - start_mode)); - return true; -} -void InlineSigninHelper::ConfirmEmailAction( - content::WebContents* web_contents, - const std::string& refresh_token, - signin_metrics::Source source, - OneClickSigninSyncStarter::ConfirmationRequired confirmation_required, - OneClickSigninSyncStarter::StartSyncMode start_mode, - ConfirmEmailDialogDelegate::Action action) { - Browser* browser = chrome::FindLastActiveWithProfile( - profile_, chrome::GetActiveDesktop()); - switch (action) { - case ConfirmEmailDialogDelegate::CREATE_NEW_USER: - if (handler_) { - handler_->SyncStarterCallback( - OneClickSigninSyncStarter::SYNC_SETUP_FAILURE); - } - chrome::ShowSettingsSubPage(browser, - std::string(chrome::kCreateProfileSubPage)); - break; - case ConfirmEmailDialogDelegate::START_SYNC: - new OneClickSigninSyncStarter( - profile_, browser, email_, password_, refresh_token, - start_mode, web_contents, confirmation_required, GURL(), - base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback, handler_)); - break; - case ConfirmEmailDialogDelegate::CLOSE: - if (handler_) { - handler_->SyncStarterCallback( - OneClickSigninSyncStarter::SYNC_SETUP_FAILURE); - } - break; - default: - DCHECK(false) << "Invalid action"; - } base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); } @@ -513,81 +271,6 @@ void InlineLoginHandlerImpl::DidCommitProvisionalLoadForFrame( } } -// static -bool InlineLoginHandlerImpl::CanOffer(Profile* profile, - CanOfferFor can_offer_for, - const std::string& email, - std::string* error_message) { - if (error_message) - error_message->clear(); - - if (!profile) - return false; - - SigninManager* manager = SigninManagerFactory::GetForProfile(profile); - if (manager && !manager->IsSigninAllowed()) - return false; - - if (!ChromeSigninClient::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) - return true; - - // 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_metrics::HISTOGRAM_ACCOUNT_MISSMATCH, - signin_metrics::HISTOGRAM_MAX); - if (error_message) { - error_message->assign( - l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL, - base::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) { - ProfileInfoCache& cache = manager->GetProfileInfoCache(); - for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) { - std::string current_email = - base::UTF16ToUTF8(cache.GetUserNameOfProfileAtIndex(i)); - if (gaia::AreEmailsSame(email, current_email)) { - if (error_message) { - error_message->assign( - l10n_util::GetStringUTF8(IDS_SYNC_USER_NAME_IN_USE_ERROR)); - } - return false; - } - } - } - } - } - - return true; -} - void InlineLoginHandlerImpl::SetExtraInitParams(base::DictionaryValue& params) { params.SetString("service", "chromiumsync"); @@ -597,7 +280,7 @@ void InlineLoginHandlerImpl::SetExtraInitParams(base::DictionaryValue& params) { net::GetValueForKeyInQuery(current_url, "constrained", &is_constrained); content::WebContentsObserver::Observe(contents); - LogHistogramValue(signin_metrics::HISTOGRAM_SHOWN); + OneClickSigninHelper::LogHistogramValue(signin_metrics::HISTOGRAM_SHOWN); } void InlineLoginHandlerImpl::CompleteLogin(const base::ListValue* args) { @@ -659,24 +342,25 @@ void InlineLoginHandlerImpl::CompleteLogin(const base::ListValue* args) { dict->GetBoolean("chooseWhatToSync", &choose_what_to_sync); signin_metrics::Source source = signin::GetSourceForPromoURL(current_url); - LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED); + OneClickSigninHelper::LogHistogramValue(signin_metrics::HISTOGRAM_ACCEPTED); bool switch_to_advanced = choose_what_to_sync && (source != signin_metrics::SOURCE_SETTINGS); - LogHistogramValue( + OneClickSigninHelper::LogHistogramValue( switch_to_advanced ? signin_metrics::HISTOGRAM_WITH_ADVANCED : signin_metrics::HISTOGRAM_WITH_DEFAULTS); - CanOfferFor can_offer_for = CAN_OFFER_FOR_ALL; + OneClickSigninHelper::CanOfferFor can_offer_for = + OneClickSigninHelper::CAN_OFFER_FOR_ALL; switch (source) { case signin_metrics::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT: - can_offer_for = CAN_OFFER_FOR_SECONDARY_ACCOUNT; + can_offer_for = OneClickSigninHelper::CAN_OFFER_FOR_SECONDARY_ACCOUNT; break; case signin_metrics::SOURCE_REAUTH: { std::string primary_username = SigninManagerFactory::GetForProfile( Profile::FromWebUI(web_ui()))->GetAuthenticatedUsername(); if (!gaia::AreEmailsSame(default_email, primary_username)) - can_offer_for = CAN_OFFER_FOR_SECONDARY_ACCOUNT; + can_offer_for = OneClickSigninHelper::CAN_OFFER_FOR_SECONDARY_ACCOUNT; break; } default: @@ -685,8 +369,8 @@ void InlineLoginHandlerImpl::CompleteLogin(const base::ListValue* args) { } std::string error_msg; - bool can_offer = CanOffer(Profile::FromWebUI(web_ui()), can_offer_for, - email, &error_msg); + bool can_offer = OneClickSigninHelper::CanOffer( + contents, can_offer_for, email, &error_msg); if (!can_offer) { HandleLoginError(error_msg); return; @@ -751,7 +435,7 @@ void InlineLoginHandlerImpl::SyncStarterCallback( bool auto_close = signin::IsAutoCloseEnabledInURL(current_url); if (result == OneClickSigninSyncStarter::SYNC_SETUP_FAILURE) { - RedirectToNtpOrAppsPage(contents, source); + OneClickSigninHelper::RedirectToNtpOrAppsPage(contents, source); } else if (auto_close) { base::MessageLoop::current()->PostTask( FROM_HERE, @@ -759,7 +443,7 @@ void InlineLoginHandlerImpl::SyncStarterCallback( weak_factory_.GetWeakPtr(), signin::ShouldShowAccountManagement(current_url))); } else { - RedirectToNtpOrAppsPageIfNecessary(contents, source); + OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(contents, source); } } diff --git a/chrome/browser/ui/webui/signin/inline_login_handler_impl.h b/chrome/browser/ui/webui/signin/inline_login_handler_impl.h index faa8856cde9a..4214cb7d2502 100644 --- a/chrome/browser/ui/webui/signin/inline_login_handler_impl.h +++ b/chrome/browser/ui/webui/signin/inline_login_handler_impl.h @@ -37,26 +37,6 @@ class InlineLoginHandlerImpl : public InlineLoginHandler, void HandleLoginError(const std::string& error_msg); private: - friend class InlineLoginUIBrowserTest; - FRIEND_TEST_ALL_PREFIXES(InlineLoginUIBrowserTest, CanOfferNoProfile); - FRIEND_TEST_ALL_PREFIXES(InlineLoginUIBrowserTest, CanOffer); - FRIEND_TEST_ALL_PREFIXES(InlineLoginUIBrowserTest, CanOfferProfileConnected); - FRIEND_TEST_ALL_PREFIXES(InlineLoginUIBrowserTest, - CanOfferUsernameNotAllowed); - FRIEND_TEST_ALL_PREFIXES(InlineLoginUIBrowserTest, CanOfferWithRejectedEmail); - FRIEND_TEST_ALL_PREFIXES(InlineLoginUIBrowserTest, CanOfferNoSigninCookies); - - // Argument to CanOffer(). - enum CanOfferFor { - CAN_OFFER_FOR_ALL, - CAN_OFFER_FOR_SECONDARY_ACCOUNT - }; - - static bool CanOffer(Profile* profile, - CanOfferFor can_offer_for, - const std::string& email, - std::string* error_message); - // InlineLoginHandler overrides: void SetExtraInitParams(base::DictionaryValue& params) override; void CompleteLogin(const base::ListValue* args) override; diff --git a/chrome/browser/ui/webui/signin/inline_login_ui_browsertest.cc b/chrome/browser/ui/webui/signin/inline_login_ui_browsertest.cc index 1f822e6daa69..5ec0bbc7b95f 100644 --- a/chrome/browser/ui/webui/signin/inline_login_ui_browsertest.cc +++ b/chrome/browser/ui/webui/signin/inline_login_ui_browsertest.cc @@ -3,31 +3,21 @@ // found in the LICENSE file. #include "base/command_line.h" -#include "base/prefs/pref_service.h" -#include "base/prefs/scoped_user_pref_update.h" -#include "base/strings/utf_string_conversions.h" -#include "chrome/browser/content_settings/cookie_settings.h" -#include "chrome/browser/signin/signin_manager_factory.h" #include "chrome/browser/signin/signin_promo.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" -#include "chrome/browser/ui/webui/signin/inline_login_handler_impl.h" #include "chrome/browser/ui/webui/signin/inline_login_ui.h" #include "chrome/browser/ui/webui/signin/login_ui_service.h" #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h" #include "chrome/browser/ui/webui/signin/login_ui_test_utils.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/url_constants.h" -#include "chrome/grit/chromium_strings.h" -#include "chrome/grit/generated_resources.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/test_browser_window.h" #include "chrome/test/base/test_chrome_web_ui_controller_factory.h" #include "chrome/test/base/testing_browser_process.h" #include "chrome/test/base/ui_test_utils.h" -#include "components/signin/core/browser/signin_manager.h" #include "components/signin/core/common/profile_management_switches.h" -#include "components/signin/core/common/signin_pref_names.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/session_storage_namespace.h" @@ -46,13 +36,10 @@ #include "net/test/embedded_test_server/http_response.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" -#include "ui/base/l10n/l10n_util.h" using ::testing::_; -using ::testing::AtLeast; using ::testing::Invoke; using ::testing::InvokeWithoutArgs; -using ::testing::Return; using login_ui_test_utils::ExecuteJsToSigninInSigninFrame; using login_ui_test_utils::WaitUntilUIReady; @@ -119,58 +106,8 @@ bool AddToSet(std::set* set, class InlineLoginUIBrowserTest : public InProcessBrowserTest { public: InlineLoginUIBrowserTest() {} - - void SetUpSigninManager(const std::string& username); - void EnableSigninAllowed(bool enable); - void EnableOneClick(bool enable); - void AddEmailToOneClickRejectedList(const std::string& email); - void AllowSigninCookies(bool enable); - void SetAllowedUsernamePattern(const std::string& pattern); - - protected: - content::WebContents* web_contents() { return nullptr; } }; -void InlineLoginUIBrowserTest::SetUpSigninManager(const std::string& username) { - if (username.empty()) - return; - - SigninManagerBase* signin_manager = - SigninManagerFactory::GetForProfile(browser()->profile()); - signin_manager->SetAuthenticatedUsername(username); -} - -void InlineLoginUIBrowserTest::EnableSigninAllowed(bool enable) { - PrefService* pref_service = browser()->profile()->GetPrefs(); - pref_service->SetBoolean(prefs::kSigninAllowed, enable); -} - -void InlineLoginUIBrowserTest::EnableOneClick(bool enable) { - PrefService* pref_service = browser()->profile()->GetPrefs(); - pref_service->SetBoolean(prefs::kReverseAutologinEnabled, enable); -} - -void InlineLoginUIBrowserTest::AddEmailToOneClickRejectedList( - const std::string& email) { - PrefService* pref_service = browser()->profile()->GetPrefs(); - ListPrefUpdate updater(pref_service, - prefs::kReverseAutologinRejectedEmailList); - updater->AppendIfNotPresent(new base::StringValue(email)); -} - -void InlineLoginUIBrowserTest::AllowSigninCookies(bool enable) { - CookieSettings* cookie_settings = - CookieSettings::Factory::GetForProfile(browser()->profile()).get(); - cookie_settings->SetDefaultCookieSetting(enable ? CONTENT_SETTING_ALLOW - : CONTENT_SETTING_BLOCK); -} - -void InlineLoginUIBrowserTest::SetAllowedUsernamePattern( - const std::string& pattern) { - PrefService* local_state = g_browser_process->local_state(); - local_state->SetString(prefs::kGoogleServicesUsernamePattern, pattern); -} - #if defined(OS_LINUX) || defined(OS_WIN) // crbug.com/422868 #define MAYBE_DifferentStorageId DISABLED_DifferentStorageId @@ -247,90 +184,6 @@ IN_PROC_BROWSER_TEST_F(InlineLoginUIBrowserTest, OneProcessLimit) { ASSERT_NE(info1.pid, info3.pid); } -#if !defined(OS_CHROMEOS) - -IN_PROC_BROWSER_TEST_F(InlineLoginUIBrowserTest, CanOfferNoProfile) { - std::string error_message; - EXPECT_FALSE(InlineLoginHandlerImpl::CanOffer( - NULL, InlineLoginHandlerImpl::CAN_OFFER_FOR_ALL, - "user@gmail.com", &error_message)); - EXPECT_EQ("", error_message); -} - -IN_PROC_BROWSER_TEST_F(InlineLoginUIBrowserTest, CanOffer) { - EnableOneClick(true); - EXPECT_TRUE(InlineLoginHandlerImpl::CanOffer( - browser()->profile(), InlineLoginHandlerImpl::CAN_OFFER_FOR_ALL, - "user@gmail.com", NULL)); - - EnableOneClick(false); - - std::string error_message; - - EXPECT_TRUE(InlineLoginHandlerImpl::CanOffer( - browser()->profile(), InlineLoginHandlerImpl::CAN_OFFER_FOR_ALL, - "user@gmail.com", &error_message)); -} - -IN_PROC_BROWSER_TEST_F(InlineLoginUIBrowserTest, CanOfferProfileConnected) { - SetUpSigninManager("foo@gmail.com"); - EnableSigninAllowed(true); - - std::string error_message; - - EXPECT_TRUE(InlineLoginHandlerImpl::CanOffer( - browser()->profile(), InlineLoginHandlerImpl::CAN_OFFER_FOR_ALL, - "foo@gmail.com", &error_message)); - EXPECT_TRUE(InlineLoginHandlerImpl::CanOffer( - browser()->profile(), InlineLoginHandlerImpl::CAN_OFFER_FOR_ALL, - "foo", &error_message)); - EXPECT_FALSE(InlineLoginHandlerImpl::CanOffer( - browser()->profile(), InlineLoginHandlerImpl::CAN_OFFER_FOR_ALL, - "user@gmail.com", &error_message)); - EXPECT_EQ(l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL, - base::UTF8ToUTF16("foo@gmail.com")), - error_message); -} - -IN_PROC_BROWSER_TEST_F(InlineLoginUIBrowserTest, CanOfferUsernameNotAllowed) { - SetAllowedUsernamePattern("*.google.com"); - - std::string error_message; - EXPECT_FALSE(InlineLoginHandlerImpl::CanOffer( - browser()->profile(), InlineLoginHandlerImpl::CAN_OFFER_FOR_ALL, - "foo@gmail.com", &error_message)); - EXPECT_EQ(l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED), - error_message); -} - -IN_PROC_BROWSER_TEST_F(InlineLoginUIBrowserTest, CanOfferWithRejectedEmail) { - EnableSigninAllowed(true); - - AddEmailToOneClickRejectedList("foo@gmail.com"); - AddEmailToOneClickRejectedList("user@gmail.com"); - - std::string error_message; - EXPECT_TRUE(InlineLoginHandlerImpl::CanOffer( - browser()->profile(), InlineLoginHandlerImpl::CAN_OFFER_FOR_ALL, - "foo@gmail.com", &error_message)); - EXPECT_TRUE(InlineLoginHandlerImpl::CanOffer( - browser()->profile(), InlineLoginHandlerImpl::CAN_OFFER_FOR_ALL, - "user@gmail.com", &error_message)); -} - -IN_PROC_BROWSER_TEST_F(InlineLoginUIBrowserTest, CanOfferNoSigninCookies) { - AllowSigninCookies(false); - EnableSigninAllowed(true); - - std::string error_message; - EXPECT_FALSE(InlineLoginHandlerImpl::CanOffer( - browser()->profile(), InlineLoginHandlerImpl::CAN_OFFER_FOR_ALL, - "user@gmail.com", &error_message)); - EXPECT_EQ("", error_message); -} - -#endif // OS_CHROMEOS - class InlineLoginUISafeIframeBrowserTest : public InProcessBrowserTest { public: FooWebUIProvider& foo_provider() { return foo_provider_; } diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi index 579855071d17..3d1cb38bd2b8 100644 --- a/chrome/chrome_browser_ui.gypi +++ b/chrome/chrome_browser_ui.gypi @@ -2600,6 +2600,8 @@ 'browser/ui/sync/one_click_signin_bubble_delegate.h', 'browser/ui/sync/one_click_signin_bubble_links_delegate.cc', 'browser/ui/sync/one_click_signin_bubble_links_delegate.h', + 'browser/ui/sync/one_click_signin_helper.cc', + 'browser/ui/sync/one_click_signin_helper.h', 'browser/ui/sync/one_click_signin_sync_observer.cc', 'browser/ui/sync/one_click_signin_sync_observer.h', 'browser/ui/sync/one_click_signin_sync_starter.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 9bd06e5eb3ee..2e2ae40aa9f5 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -609,6 +609,7 @@ ], 'chrome_unit_tests_one_click_signin_sources': [ 'browser/ui/cocoa/one_click_signin_bubble_controller_unittest.mm', + 'browser/ui/sync/one_click_signin_helper_unittest.cc', 'browser/ui/sync/one_click_signin_sync_observer_unittest.cc', 'browser/ui/sync/one_click_signin_sync_starter_unittest.cc', 'browser/ui/views/sync/one_click_signin_bubble_view_unittest.cc', @@ -2162,6 +2163,7 @@ 'conditions': [ ['chromeos == 1', { 'sources!': [ + 'browser/ui/sync/one_click_signin_helper_unittest.cc', 'browser/ui/sync/one_click_signin_sync_starter_unittest.cc', ], }], diff --git a/components/signin/core/browser/signin_metrics.cc b/components/signin/core/browser/signin_metrics.cc index 4a2ea6bf8099..4f7c4793a0b6 100644 --- a/components/signin/core/browser/signin_metrics.cc +++ b/components/signin/core/browser/signin_metrics.cc @@ -102,9 +102,4 @@ void LogAuthError(GoogleServiceAuthError::State auth_error) { GoogleServiceAuthError::State::NUM_STATES); } -void LogSigninConfirmHistogramValue(int action) { - UMA_HISTOGRAM_ENUMERATION("Signin.OneClickConfirmation", action, - signin_metrics::HISTOGRAM_CONFIRM_MAX); -} - } // namespace signin_metrics diff --git a/components/signin/core/browser/signin_metrics.h b/components/signin/core/browser/signin_metrics.h index a23aedd2cac8..0c5e10fdd506 100644 --- a/components/signin/core/browser/signin_metrics.h +++ b/components/signin/core/browser/signin_metrics.h @@ -169,8 +169,6 @@ void LogExternalCcResultFetches( // Track when the current authentication error changed. void LogAuthError(GoogleServiceAuthError::State auth_error); -void LogSigninConfirmHistogramValue(int action); - } // namespace signin_metrics #endif // COMPONENTS_SIGNIN_CORE_BROWSER_SIGNIN_METRICS_H_ -- 2.11.4.GIT