| Index: chrome/browser/ui/sync/one_click_signin_helper.cc
|
| 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 0000000000000000000000000000000000000000..e72ae55f591a9048e5015fb0c38375171019e666
|
| --- /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 <algorithm>
|
| +#include <functional>
|
| +#include <utility>
|
| +#include <vector>
|
| +
|
| +#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<void(Action)> 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<content::WebContents> 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<SigninManagerCookieHelper> 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<std::string> 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<std::string>(),
|
| + 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<std::string, std::string>& 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<std::string> 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();
|
| +}
|
|
|