summaryrefslogtreecommitdiffstats
path: root/components/signin
diff options
context:
space:
mode:
authorblundell@chromium.org <blundell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-03 15:48:10 +0000
committerblundell@chromium.org <blundell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-03 15:48:10 +0000
commitb47dbc3e7ef0bd0145d83ca4ac8bb7bc76a4d5a9 (patch)
treedad4f1d03572895e81bfecf151437a51f331a595 /components/signin
parentf48405e6b24e0e737be3aa2df1f13b6d9334e979 (diff)
downloadchromium_src-b47dbc3e7ef0bd0145d83ca4ac8bb7bc76a4d5a9.zip
chromium_src-b47dbc3e7ef0bd0145d83ca4ac8bb7bc76a4d5a9.tar.gz
chromium_src-b47dbc3e7ef0bd0145d83ca4ac8bb7bc76a4d5a9.tar.bz2
Componentize AccountReconcilor.
This CL does the following: - Componentize SigninOAuthHelper - Replaces AccountReconcilor's calls to Profile with calls to SigninClient - Passes AccountReconcilor's dependencies in directly rather than having AccountReconcilor obtain them from factories via the Profile - Componentizes AccountReconcilor BUG=334196,333999 Review URL: https://codereview.chromium.org/219933002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@261422 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components/signin')
-rw-r--r--components/signin/core/browser/account_reconcilor.cc723
-rw-r--r--components/signin/core/browser/account_reconcilor.h252
-rw-r--r--components/signin/core/browser/signin_oauth_helper.cc68
-rw-r--r--components/signin/core/browser/signin_oauth_helper.h61
4 files changed, 1104 insertions, 0 deletions
diff --git a/components/signin/core/browser/account_reconcilor.cc b/components/signin/core/browser/account_reconcilor.cc
new file mode 100644
index 0000000..9725b0b
--- /dev/null
+++ b/components/signin/core/browser/account_reconcilor.cc
@@ -0,0 +1,723 @@
+// Copyright 2014 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 "components/signin/core/browser/account_reconcilor.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/message_loop/message_loop_proxy.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/time/time.h"
+#include "components/signin/core/browser/profile_oauth2_token_service.h"
+#include "components/signin/core/browser/signin_client.h"
+#include "components/signin/core/browser/signin_oauth_helper.h"
+#include "google_apis/gaia/gaia_auth_fetcher.h"
+#include "google_apis/gaia/gaia_auth_util.h"
+#include "google_apis/gaia/gaia_constants.h"
+#include "google_apis/gaia/gaia_oauth_client.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "net/cookies/canonical_cookie.h"
+
+// Fetches a refresh token from the given session in the GAIA cookie. This is
+// a best effort only. If it should fail, another reconcile action will occur
+// shortly anyway.
+class AccountReconcilor::RefreshTokenFetcher
+ : public SigninOAuthHelper,
+ public SigninOAuthHelper::Consumer {
+ public:
+ RefreshTokenFetcher(AccountReconcilor* reconcilor,
+ const std::string& account_id,
+ int session_index);
+ virtual ~RefreshTokenFetcher() {}
+
+ private:
+ // Overridden from GaiaAuthConsumer:
+ virtual void OnSigninOAuthInformationAvailable(
+ const std::string& email,
+ const std::string& display_email,
+ const std::string& refresh_token) OVERRIDE;
+
+ // Called when an error occurs while getting the information.
+ virtual void OnSigninOAuthInformationFailure(
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ AccountReconcilor* reconcilor_;
+ const std::string account_id_;
+ int session_index_;
+
+ DISALLOW_COPY_AND_ASSIGN(RefreshTokenFetcher);
+};
+
+AccountReconcilor::RefreshTokenFetcher::RefreshTokenFetcher(
+ AccountReconcilor* reconcilor,
+ const std::string& account_id,
+ int session_index)
+ : SigninOAuthHelper(reconcilor->client()->GetURLRequestContext(),
+ base::IntToString(session_index),
+ this),
+ reconcilor_(reconcilor),
+ account_id_(account_id),
+ session_index_(session_index) {
+ DCHECK(reconcilor_);
+ DCHECK(!account_id.empty());
+}
+
+void AccountReconcilor::RefreshTokenFetcher::OnSigninOAuthInformationAvailable(
+ const std::string& email,
+ const std::string& display_email,
+ const std::string& refresh_token) {
+ VLOG(1) << "RefreshTokenFetcher::OnSigninOAuthInformationAvailable:"
+ << " account=" << account_id_ << " email=" << email
+ << " displayEmail=" << display_email;
+
+ // TODO(rogerta): because of the problem with email vs displayEmail and
+ // emails that have been canonicalized, the argument |email| is used here
+ // to make sure the correct string is used when calling the token service.
+ // This will be cleaned up when chrome moves to using gaia obfuscated id.
+ reconcilor_->HandleRefreshTokenFetched(email, refresh_token);
+}
+
+void AccountReconcilor::RefreshTokenFetcher::OnSigninOAuthInformationFailure(
+ const GoogleServiceAuthError& error) {
+ VLOG(1) << "RefreshTokenFetcher::OnSigninOAuthInformationFailure:"
+ << " account=" << account_id_ << " session_index=" << session_index_;
+ reconcilor_->HandleRefreshTokenFetched(account_id_, std::string());
+}
+
+bool AccountReconcilor::EmailLessFunc::operator()(const std::string& s1,
+ const std::string& s2) const {
+ return gaia::CanonicalizeEmail(s1) < gaia::CanonicalizeEmail(s2);
+}
+
+class AccountReconcilor::UserIdFetcher
+ : public gaia::GaiaOAuthClient::Delegate {
+ public:
+ UserIdFetcher(AccountReconcilor* reconcilor,
+ const std::string& access_token,
+ const std::string& account_id);
+
+ // Returns the scopes needed by the UserIdFetcher.
+ static OAuth2TokenService::ScopeSet GetScopes();
+
+ private:
+ // Overriden from gaia::GaiaOAuthClient::Delegate.
+ virtual void OnGetUserIdResponse(const std::string& user_id) OVERRIDE;
+ virtual void OnOAuthError() OVERRIDE;
+ virtual void OnNetworkError(int response_code) OVERRIDE;
+
+ AccountReconcilor* const reconcilor_;
+ const std::string account_id_;
+ const std::string access_token_;
+ gaia::GaiaOAuthClient gaia_auth_client_;
+
+ DISALLOW_COPY_AND_ASSIGN(UserIdFetcher);
+};
+
+AccountReconcilor::UserIdFetcher::UserIdFetcher(AccountReconcilor* reconcilor,
+ const std::string& access_token,
+ const std::string& account_id)
+ : reconcilor_(reconcilor),
+ account_id_(account_id),
+ access_token_(access_token),
+ gaia_auth_client_(reconcilor_->client()->GetURLRequestContext()) {
+ DCHECK(reconcilor_);
+ DCHECK(!account_id_.empty());
+
+ const int kMaxRetries = 5;
+ gaia_auth_client_.GetUserId(access_token_, kMaxRetries, this);
+}
+
+// static
+OAuth2TokenService::ScopeSet AccountReconcilor::UserIdFetcher::GetScopes() {
+ OAuth2TokenService::ScopeSet scopes;
+ scopes.insert("https://www.googleapis.com/auth/userinfo.profile");
+ return scopes;
+}
+
+void AccountReconcilor::UserIdFetcher::OnGetUserIdResponse(
+ const std::string& user_id) {
+ VLOG(1) << "AccountReconcilor::OnGetUserIdResponse: " << account_id_;
+
+ // HandleSuccessfulAccountIdCheck() may delete |this|, so call it last.
+ reconcilor_->HandleSuccessfulAccountIdCheck(account_id_);
+}
+
+void AccountReconcilor::UserIdFetcher::OnOAuthError() {
+ VLOG(1) << "AccountReconcilor::OnOAuthError: " << account_id_;
+
+ // Invalidate the access token to force a refetch next time.
+ reconcilor_->token_service()->InvalidateToken(
+ account_id_, GetScopes(), access_token_);
+
+ // HandleFailedAccountIdCheck() may delete |this|, so call it last.
+ reconcilor_->HandleFailedAccountIdCheck(account_id_);
+}
+
+void AccountReconcilor::UserIdFetcher::OnNetworkError(int response_code) {
+ VLOG(1) << "AccountReconcilor::OnNetworkError: " << account_id_
+ << " response_code=" << response_code;
+
+ // TODO(rogerta): some response error should not be treated like
+ // permanent errors. Figure out appropriate ones.
+ // HandleFailedAccountIdCheck() may delete |this|, so call it last.
+ reconcilor_->HandleFailedAccountIdCheck(account_id_);
+}
+
+AccountReconcilor::AccountReconcilor(ProfileOAuth2TokenService* token_service,
+ SigninManagerBase* signin_manager,
+ SigninClient* client)
+ : OAuth2TokenService::Consumer("account_reconcilor"),
+ token_service_(token_service),
+ signin_manager_(signin_manager),
+ client_(client),
+ merge_session_helper_(token_service_,
+ client->GetURLRequestContext(),
+ this),
+ registered_with_token_service_(false),
+ is_reconcile_started_(false),
+ are_gaia_accounts_set_(false),
+ requests_(NULL) {
+ VLOG(1) << "AccountReconcilor::AccountReconcilor";
+}
+
+AccountReconcilor::~AccountReconcilor() {
+ VLOG(1) << "AccountReconcilor::~AccountReconcilor";
+ // Make sure shutdown was called first.
+ DCHECK(!registered_with_token_service_);
+ DCHECK(!reconciliation_timer_.IsRunning());
+ DCHECK(!requests_);
+ DCHECK_EQ(0u, user_id_fetchers_.size());
+ DCHECK_EQ(0u, refresh_token_fetchers_.size());
+}
+
+void AccountReconcilor::Initialize(bool start_reconcile_if_tokens_available) {
+ VLOG(1) << "AccountReconcilor::Initialize";
+ RegisterWithSigninManager();
+
+ // If this user is not signed in, the reconcilor should do nothing but
+ // wait for signin.
+ if (IsProfileConnected()) {
+ RegisterForCookieChanges();
+ RegisterWithTokenService();
+ StartPeriodicReconciliation();
+
+ // Start a reconcile if the tokens are already loaded.
+ if (start_reconcile_if_tokens_available &&
+ token_service_->GetAccounts().size() > 0) {
+ StartReconcile();
+ }
+ }
+}
+
+void AccountReconcilor::Shutdown() {
+ VLOG(1) << "AccountReconcilor::Shutdown";
+ merge_session_helper_.CancelAll();
+ merge_session_helper_.RemoveObserver(this);
+ gaia_fetcher_.reset();
+ DeleteFetchers();
+ UnregisterWithSigninManager();
+ UnregisterWithTokenService();
+ UnregisterForCookieChanges();
+ StopPeriodicReconciliation();
+}
+
+void AccountReconcilor::AddMergeSessionObserver(
+ MergeSessionHelper::Observer* observer) {
+ merge_session_helper_.AddObserver(observer);
+}
+
+void AccountReconcilor::RemoveMergeSessionObserver(
+ MergeSessionHelper::Observer* observer) {
+ merge_session_helper_.RemoveObserver(observer);
+}
+
+void AccountReconcilor::DeleteFetchers() {
+ delete[] requests_;
+ requests_ = NULL;
+
+ user_id_fetchers_.clear();
+ refresh_token_fetchers_.clear();
+}
+
+bool AccountReconcilor::AreAllRefreshTokensChecked() const {
+ return chrome_accounts_.size() ==
+ (valid_chrome_accounts_.size() + invalid_chrome_accounts_.size());
+}
+
+void AccountReconcilor::RegisterForCookieChanges() {
+ // First clear any existing registration to avoid DCHECKs that can otherwise
+ // go off in some embedders on reauth (e.g., ChromeSigninClient).
+ UnregisterForCookieChanges();
+ client_->SetCookieChangedCallback(
+ base::Bind(&AccountReconcilor::OnCookieChanged, base::Unretained(this)));
+}
+
+void AccountReconcilor::UnregisterForCookieChanges() {
+ client_->SetCookieChangedCallback(SigninClient::CookieChangedCallback());
+}
+
+void AccountReconcilor::RegisterWithSigninManager() {
+ signin_manager_->AddObserver(this);
+}
+
+void AccountReconcilor::UnregisterWithSigninManager() {
+ signin_manager_->RemoveObserver(this);
+}
+
+void AccountReconcilor::RegisterWithTokenService() {
+ VLOG(1) << "AccountReconcilor::RegisterWithTokenService";
+ // During re-auth, the reconcilor will get a callback about successful signin
+ // even when the profile is already connected. Avoid re-registering
+ // with the token service since this will DCHECK.
+ if (registered_with_token_service_)
+ return;
+
+ token_service_->AddObserver(this);
+ registered_with_token_service_ = true;
+}
+
+void AccountReconcilor::UnregisterWithTokenService() {
+ if (!registered_with_token_service_)
+ return;
+
+ token_service_->RemoveObserver(this);
+ registered_with_token_service_ = false;
+}
+
+bool AccountReconcilor::IsProfileConnected() {
+ return !signin_manager_->GetAuthenticatedUsername().empty();
+}
+
+void AccountReconcilor::StartPeriodicReconciliation() {
+ VLOG(1) << "AccountReconcilor::StartPeriodicReconciliation";
+ // TODO(rogerta): pick appropriate thread and timeout value.
+ reconciliation_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromSeconds(300),
+ this,
+ &AccountReconcilor::PeriodicReconciliation);
+}
+
+void AccountReconcilor::StopPeriodicReconciliation() {
+ VLOG(1) << "AccountReconcilor::StopPeriodicReconciliation";
+ reconciliation_timer_.Stop();
+}
+
+void AccountReconcilor::PeriodicReconciliation() {
+ VLOG(1) << "AccountReconcilor::PeriodicReconciliation";
+ StartReconcile();
+}
+
+void AccountReconcilor::OnCookieChanged(const net::CanonicalCookie* cookie) {
+ if (cookie->Name() == "LSID" &&
+ cookie->Domain() == GaiaUrls::GetInstance()->gaia_url().host() &&
+ cookie->IsSecure() && cookie->IsHttpOnly()) {
+ VLOG(1) << "AccountReconcilor::OnCookieChanged: LSID changed";
+#ifdef OS_CHROMEOS
+ // On Chrome OS it is possible that O2RT is not available at this moment
+ // because profile data transfer is still in progress.
+ if (!token_service_->GetAccounts().size()) {
+ VLOG(1) << "AccountReconcilor::OnCookieChanged: cookie change is ingored"
+ "because profile data transfer is in progress.";
+ return;
+ }
+#endif
+ StartReconcile();
+ }
+}
+
+void AccountReconcilor::OnRefreshTokenAvailable(const std::string& account_id) {
+ VLOG(1) << "AccountReconcilor::OnRefreshTokenAvailable: " << account_id;
+ StartReconcile();
+}
+
+void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) {
+ VLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id;
+ StartRemoveAction(account_id);
+}
+
+void AccountReconcilor::OnRefreshTokensLoaded() {}
+
+void AccountReconcilor::GoogleSigninSucceeded(const std::string& username,
+ const std::string& password) {
+ VLOG(1) << "AccountReconcilor::GoogleSigninSucceeded: signed in";
+ RegisterForCookieChanges();
+ RegisterWithTokenService();
+ StartPeriodicReconciliation();
+}
+
+void AccountReconcilor::GoogleSignedOut(const std::string& username) {
+ VLOG(1) << "AccountReconcilor::GoogleSignedOut: signed out";
+ UnregisterWithTokenService();
+ UnregisterForCookieChanges();
+ StopPeriodicReconciliation();
+}
+
+void AccountReconcilor::PerformMergeAction(const std::string& account_id) {
+ VLOG(1) << "AccountReconcilor::PerformMergeAction: " << account_id;
+ merge_session_helper_.LogIn(account_id);
+}
+
+void AccountReconcilor::StartRemoveAction(const std::string& account_id) {
+ VLOG(1) << "AccountReconcilor::StartRemoveAction: " << account_id;
+ GetAccountsFromCookie(base::Bind(&AccountReconcilor::FinishRemoveAction,
+ base::Unretained(this),
+ account_id));
+}
+
+void AccountReconcilor::FinishRemoveAction(
+ const std::string& account_id,
+ const GoogleServiceAuthError& error,
+ const std::vector<std::pair<std::string, bool> >& accounts) {
+ VLOG(1) << "AccountReconcilor::FinishRemoveAction:"
+ << " account=" << account_id << " error=" << error.ToString();
+ if (error.state() == GoogleServiceAuthError::NONE) {
+ AbortReconcile();
+ std::vector<std::string> accounts_only;
+ for (std::vector<std::pair<std::string, bool> >::const_iterator i =
+ accounts.begin();
+ i != accounts.end();
+ ++i) {
+ accounts_only.push_back(i->first);
+ }
+ merge_session_helper_.LogOut(account_id, accounts_only);
+ }
+ // Wait for the next ReconcileAction if there is an error.
+}
+
+void AccountReconcilor::PerformAddToChromeAction(const std::string& account_id,
+ int session_index) {
+ VLOG(1) << "AccountReconcilor::PerformAddToChromeAction:"
+ << " account=" << account_id << " session_index=" << session_index;
+
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+ refresh_token_fetchers_.push_back(
+ new RefreshTokenFetcher(this, account_id, session_index));
+#endif
+}
+
+void AccountReconcilor::PerformLogoutAllAccountsAction() {
+ VLOG(1) << "AccountReconcilor::PerformLogoutAllAccountsAction";
+ merge_session_helper_.LogOutAllAccounts();
+}
+
+void AccountReconcilor::StartReconcile() {
+ if (!IsProfileConnected() || is_reconcile_started_)
+ return;
+
+ is_reconcile_started_ = true;
+
+ // Reset state for validating gaia cookie.
+ are_gaia_accounts_set_ = false;
+ gaia_accounts_.clear();
+ GetAccountsFromCookie(base::Bind(
+ &AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts,
+ base::Unretained(this)));
+
+ // Reset state for validating oauth2 tokens.
+ primary_account_.clear();
+ chrome_accounts_.clear();
+ DeleteFetchers();
+ valid_chrome_accounts_.clear();
+ invalid_chrome_accounts_.clear();
+ add_to_cookie_.clear();
+ add_to_chrome_.clear();
+ ValidateAccountsFromTokenService();
+}
+
+void AccountReconcilor::GetAccountsFromCookie(
+ GetAccountsFromCookieCallback callback) {
+ get_gaia_accounts_callbacks_.push_back(callback);
+ if (!gaia_fetcher_) {
+ // There is no list account request in flight.
+ gaia_fetcher_.reset(new GaiaAuthFetcher(
+ this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
+ gaia_fetcher_->StartListAccounts();
+ }
+}
+
+void AccountReconcilor::OnListAccountsSuccess(const std::string& data) {
+ gaia_fetcher_.reset();
+
+ // Get account information from response data.
+ std::vector<std::pair<std::string, bool> > gaia_accounts;
+ bool valid_json = gaia::ParseListAccountsData(data, &gaia_accounts);
+ if (!valid_json) {
+ VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: parsing error";
+ } else if (gaia_accounts.size() > 0) {
+ VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: "
+ << "Gaia " << gaia_accounts.size() << " accounts, "
+ << "Primary is '" << gaia_accounts[0].first << "'";
+ } else {
+ VLOG(1) << "AccountReconcilor::OnListAccountsSuccess: No accounts";
+ }
+
+ // There must be at least one callback waiting for result.
+ DCHECK(!get_gaia_accounts_callbacks_.empty());
+
+ GoogleServiceAuthError error =
+ !valid_json ? GoogleServiceAuthError(
+ GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE)
+ : GoogleServiceAuthError::AuthErrorNone();
+ get_gaia_accounts_callbacks_.front().Run(error, gaia_accounts);
+ get_gaia_accounts_callbacks_.pop_front();
+
+ MayBeDoNextListAccounts();
+}
+
+void AccountReconcilor::OnListAccountsFailure(
+ const GoogleServiceAuthError& error) {
+ gaia_fetcher_.reset();
+ VLOG(1) << "AccountReconcilor::OnListAccountsFailure: " << error.ToString();
+ std::vector<std::pair<std::string, bool> > empty_accounts;
+
+ // There must be at least one callback waiting for result.
+ DCHECK(!get_gaia_accounts_callbacks_.empty());
+
+ get_gaia_accounts_callbacks_.front().Run(error, empty_accounts);
+ get_gaia_accounts_callbacks_.pop_front();
+
+ MayBeDoNextListAccounts();
+}
+
+void AccountReconcilor::MayBeDoNextListAccounts() {
+ if (!get_gaia_accounts_callbacks_.empty()) {
+ gaia_fetcher_.reset(new GaiaAuthFetcher(
+ this, GaiaConstants::kChromeSource, client_->GetURLRequestContext()));
+ gaia_fetcher_->StartListAccounts();
+ }
+}
+
+void AccountReconcilor::ContinueReconcileActionAfterGetGaiaAccounts(
+ const GoogleServiceAuthError& error,
+ const std::vector<std::pair<std::string, bool> >& accounts) {
+ if (error.state() == GoogleServiceAuthError::NONE) {
+ gaia_accounts_ = accounts;
+ are_gaia_accounts_set_ = true;
+ FinishReconcile();
+ } else {
+ AbortReconcile();
+ }
+}
+
+void AccountReconcilor::ValidateAccountsFromTokenService() {
+ primary_account_ = signin_manager_->GetAuthenticatedUsername();
+ DCHECK(!primary_account_.empty());
+
+ chrome_accounts_ = token_service_->GetAccounts();
+ DCHECK_GT(chrome_accounts_.size(), 0u);
+
+ VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: "
+ << "Chrome " << chrome_accounts_.size() << " accounts, "
+ << "Primary is '" << primary_account_ << "'";
+
+ DCHECK(!requests_);
+ requests_ =
+ new scoped_ptr<OAuth2TokenService::Request>[chrome_accounts_.size()];
+ const OAuth2TokenService::ScopeSet scopes =
+ AccountReconcilor::UserIdFetcher::GetScopes();
+ for (size_t i = 0; i < chrome_accounts_.size(); ++i) {
+ requests_[i] =
+ token_service_->StartRequest(chrome_accounts_[i], scopes, this);
+ }
+
+ DCHECK_EQ(0u, user_id_fetchers_.size());
+ user_id_fetchers_.resize(chrome_accounts_.size());
+}
+
+void AccountReconcilor::OnGetTokenSuccess(
+ const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) {
+ size_t index;
+ for (index = 0; index < chrome_accounts_.size(); ++index) {
+ if (request == requests_[index].get())
+ break;
+ }
+ DCHECK(index < chrome_accounts_.size());
+
+ const std::string& account_id = chrome_accounts_[index];
+
+ VLOG(1) << "AccountReconcilor::OnGetTokenSuccess: valid " << account_id;
+
+ DCHECK(!user_id_fetchers_[index]);
+ user_id_fetchers_[index] = new UserIdFetcher(this, access_token, account_id);
+}
+
+void AccountReconcilor::OnGetTokenFailure(
+ const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) {
+ size_t index;
+ for (index = 0; index < chrome_accounts_.size(); ++index) {
+ if (request == requests_[index].get())
+ break;
+ }
+ DCHECK(index < chrome_accounts_.size());
+
+ const std::string& account_id = chrome_accounts_[index];
+
+ VLOG(1) << "AccountReconcilor::OnGetTokenFailure: invalid " << account_id;
+ HandleFailedAccountIdCheck(account_id);
+}
+
+void AccountReconcilor::FinishReconcile() {
+ // Make sure that the process of validating the gaia cookie and the oauth2
+ // tokens individually is done before proceeding with reconciliation.
+ if (!are_gaia_accounts_set_ || !AreAllRefreshTokensChecked())
+ return;
+
+ VLOG(1) << "AccountReconcilor::FinishReconcile";
+
+ DeleteFetchers();
+
+ DCHECK(add_to_cookie_.empty());
+ DCHECK(add_to_chrome_.empty());
+ bool are_primaries_equal =
+ gaia_accounts_.size() > 0 &&
+ gaia::AreEmailsSame(primary_account_, gaia_accounts_[0].first);
+
+ if (are_primaries_equal) {
+ // Determine if we need to merge accounts from gaia cookie to chrome.
+ for (size_t i = 0; i < gaia_accounts_.size(); ++i) {
+ const std::string& gaia_account = gaia_accounts_[i].first;
+ if (gaia_accounts_[i].second &&
+ valid_chrome_accounts_.find(gaia_account) ==
+ valid_chrome_accounts_.end()) {
+ add_to_chrome_.push_back(std::make_pair(gaia_account, i));
+ }
+ }
+
+ // Determine if we need to merge accounts from chrome into gaia cookie.
+ for (EmailSet::const_iterator i = valid_chrome_accounts_.begin();
+ i != valid_chrome_accounts_.end();
+ ++i) {
+ bool add_to_cookie = true;
+ for (size_t j = 0; j < gaia_accounts_.size(); ++j) {
+ if (gaia::AreEmailsSame(gaia_accounts_[j].first, *i)) {
+ add_to_cookie = !gaia_accounts_[j].second;
+ break;
+ }
+ }
+ if (add_to_cookie)
+ add_to_cookie_.push_back(*i);
+ }
+ } else {
+ VLOG(1) << "AccountReconcilor::FinishReconcile: rebuild cookie";
+ // Really messed up state. Blow away the gaia cookie completely and
+ // rebuild it, making sure the primary account as specified by the
+ // SigninManager is the first session in the gaia cookie.
+ PerformLogoutAllAccountsAction();
+ add_to_cookie_.push_back(primary_account_);
+ for (EmailSet::const_iterator i = valid_chrome_accounts_.begin();
+ i != valid_chrome_accounts_.end();
+ ++i) {
+ if (*i != primary_account_)
+ add_to_cookie_.push_back(*i);
+ }
+ }
+
+ // For each account known to chrome but not in the gaia cookie,
+ // PerformMergeAction().
+ for (size_t i = 0; i < add_to_cookie_.size(); ++i)
+ PerformMergeAction(add_to_cookie_[i]);
+
+ // For each account in the gaia cookie not known to chrome,
+ // PerformAddToChromeAction.
+ for (std::vector<std::pair<std::string, int> >::const_iterator i =
+ add_to_chrome_.begin();
+ i != add_to_chrome_.end();
+ ++i) {
+ PerformAddToChromeAction(i->first, i->second);
+ }
+
+ CalculateIfReconcileIsDone();
+ ScheduleStartReconcileIfChromeAccountsChanged();
+}
+
+void AccountReconcilor::AbortReconcile() {
+ VLOG(1) << "AccountReconcilor::AbortReconcile: we'll try again later";
+ DeleteFetchers();
+ add_to_cookie_.clear();
+ add_to_chrome_.clear();
+ CalculateIfReconcileIsDone();
+}
+
+void AccountReconcilor::CalculateIfReconcileIsDone() {
+ is_reconcile_started_ = !add_to_cookie_.empty() || !add_to_chrome_.empty();
+ if (!is_reconcile_started_)
+ VLOG(1) << "AccountReconcilor::CalculateIfReconcileIsDone: done";
+}
+
+void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() {
+ if (is_reconcile_started_)
+ return;
+
+ // Start a reconcile as the token accounts have changed.
+ VLOG(1) << "AccountReconcilor::StartReconcileIfChromeAccountsChanged";
+ std::vector<std::string> reconciled_accounts(chrome_accounts_);
+ std::vector<std::string> new_chrome_accounts(token_service_->GetAccounts());
+ std::sort(reconciled_accounts.begin(), reconciled_accounts.end());
+ std::sort(new_chrome_accounts.begin(), new_chrome_accounts.end());
+ if (reconciled_accounts != new_chrome_accounts) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&AccountReconcilor::StartReconcile, base::Unretained(this)));
+ }
+}
+
+void AccountReconcilor::MergeSessionCompleted(
+ const std::string& account_id,
+ const GoogleServiceAuthError& error) {
+ VLOG(1) << "AccountReconcilor::MergeSessionCompleted: account_id="
+ << account_id;
+
+ // Remove the account from the list that is being merged.
+ for (std::vector<std::string>::iterator i = add_to_cookie_.begin();
+ i != add_to_cookie_.end();
+ ++i) {
+ if (account_id == *i) {
+ add_to_cookie_.erase(i);
+ break;
+ }
+ }
+
+ CalculateIfReconcileIsDone();
+ ScheduleStartReconcileIfChromeAccountsChanged();
+}
+
+void AccountReconcilor::HandleSuccessfulAccountIdCheck(
+ const std::string& account_id) {
+ valid_chrome_accounts_.insert(account_id);
+ FinishReconcile();
+}
+
+void AccountReconcilor::HandleFailedAccountIdCheck(
+ const std::string& account_id) {
+ invalid_chrome_accounts_.insert(account_id);
+ FinishReconcile();
+}
+
+void AccountReconcilor::HandleRefreshTokenFetched(
+ const std::string& account_id,
+ const std::string& refresh_token) {
+ if (!refresh_token.empty()) {
+ token_service_->UpdateCredentials(account_id, refresh_token);
+ }
+
+ // Remove the account from the list that is being updated.
+ for (std::vector<std::pair<std::string, int> >::iterator i =
+ add_to_chrome_.begin();
+ i != add_to_chrome_.end();
+ ++i) {
+ if (gaia::AreEmailsSame(account_id, i->first)) {
+ add_to_chrome_.erase(i);
+ break;
+ }
+ }
+
+ CalculateIfReconcileIsDone();
+}
diff --git a/components/signin/core/browser/account_reconcilor.h b/components/signin/core/browser/account_reconcilor.h
new file mode 100644
index 0000000..018dfd2
--- /dev/null
+++ b/components/signin/core/browser/account_reconcilor.h
@@ -0,0 +1,252 @@
+// Copyright 2014 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 COMPONENTS_SIGNIN_CORE_BROWSER_ACCOUNT_RECONCILOR_H_
+#define COMPONENTS_SIGNIN_CORE_BROWSER_ACCOUNT_RECONCILOR_H_
+
+#include <deque>
+#include <functional>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/signin/core/browser/signin_manager.h"
+#include "google_apis/gaia/gaia_auth_consumer.h"
+#include "google_apis/gaia/google_service_auth_error.h"
+#include "google_apis/gaia/merge_session_helper.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+
+class GaiaAuthFetcher;
+class ProfileOAuth2TokenService;
+class SigninClient;
+class SigninOAuthHelper;
+
+namespace net {
+class CanonicalCookie;
+}
+
+class AccountReconcilor : public KeyedService,
+ public GaiaAuthConsumer,
+ public MergeSessionHelper::Observer,
+ public OAuth2TokenService::Consumer,
+ public OAuth2TokenService::Observer,
+ public SigninManagerBase::Observer {
+ public:
+ AccountReconcilor(ProfileOAuth2TokenService* token_service,
+ SigninManagerBase* signin_manager,
+ SigninClient* client);
+ virtual ~AccountReconcilor();
+
+ void Initialize(bool start_reconcile_if_tokens_available);
+
+ // KeyedService implementation.
+ virtual void Shutdown() OVERRIDE;
+
+ // Add or remove observers for the merge session notification.
+ void AddMergeSessionObserver(MergeSessionHelper::Observer* observer);
+ void RemoveMergeSessionObserver(MergeSessionHelper::Observer* observer);
+
+ ProfileOAuth2TokenService* token_service() { return token_service_; }
+ SigninClient* client() { return client_; }
+
+ private:
+ // An std::set<> for use with email addresses that uses
+ // gaia::CanonicalizeEmail() during comparisons.
+ // TODO(rogerta): this is a workaround for the fact that SigninManager and
+ // SigninOAuthHelper use the gaia "email" property when adding accounts to
+ // the token service, whereas gaia::ParseListAccountsData() returns email
+ // addresses that have been passed through gaia::CanonicalizeEmail(). These
+ // two types of email addresses are not directly comparable.
+ class EmailLessFunc : public std::less<std::string> {
+ public:
+ bool operator()(const std::string& s1, const std::string& s2) const;
+ };
+ typedef std::set<std::string, EmailLessFunc> EmailSet;
+
+ class RefreshTokenFetcher;
+ class UserIdFetcher;
+
+ bool IsPeriodicReconciliationRunning() const {
+ return reconciliation_timer_.IsRunning();
+ }
+
+ bool IsRegisteredWithTokenService() const {
+ return registered_with_token_service_;
+ }
+
+ bool AreGaiaAccountsSet() const { return are_gaia_accounts_set_; }
+
+ bool AreAllRefreshTokensChecked() const;
+
+ const std::vector<std::pair<std::string, bool> >& GetGaiaAccountsForTesting()
+ const {
+ return gaia_accounts_;
+ }
+
+ const EmailSet& GetValidChromeAccountsForTesting() const {
+ return valid_chrome_accounts_;
+ }
+
+ const EmailSet& GetInvalidChromeAccountsForTesting() const {
+ return invalid_chrome_accounts_;
+ }
+
+ // Used during GetAccountsFromCookie.
+ // Stores a callback for the next action to perform.
+ typedef base::Callback<
+ void(const GoogleServiceAuthError& error,
+ const std::vector<std::pair<std::string, bool> >&)>
+ GetAccountsFromCookieCallback;
+
+ friend class AccountReconcilorTest;
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, SigninManagerRegistration);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, Reauth);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, ProfileAlreadyConnected);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, GetAccountsFromCookieSuccess);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, GetAccountsFromCookieFailure);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, ValidateAccountsFromTokens);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest,
+ ValidateAccountsFromTokensFailedUserInfo);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest,
+ ValidateAccountsFromTokensFailedTokenRequest);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, StartReconcileNoop);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, StartReconcileNoopWithDots);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, StartReconcileNoopMultiple);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, StartReconcileAddToCookie);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, StartReconcileAddToChrome);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, StartReconcileBadPrimary);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, StartReconcileOnlyOnce);
+ FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest,
+ StartReconcileWithSessionInfoExpiredDefault);
+
+ // Register and unregister with dependent services.
+ void RegisterForCookieChanges();
+ void UnregisterForCookieChanges();
+ void RegisterWithSigninManager();
+ void UnregisterWithSigninManager();
+ void RegisterWithTokenService();
+ void UnregisterWithTokenService();
+
+ bool IsProfileConnected();
+
+ void DeleteFetchers();
+
+ // Start and stop the periodic reconciliation.
+ void StartPeriodicReconciliation();
+ void StopPeriodicReconciliation();
+ void PeriodicReconciliation();
+
+ // All actions with side effects. Virtual so that they can be overridden
+ // in tests.
+ virtual void PerformMergeAction(const std::string& account_id);
+ virtual void PerformAddToChromeAction(const std::string& account_id,
+ int session_index);
+ virtual void PerformLogoutAllAccountsAction();
+
+ // Used to remove an account from chrome and the cookie jar.
+ virtual void StartRemoveAction(const std::string& account_id);
+ virtual void FinishRemoveAction(
+ const std::string& account_id,
+ const GoogleServiceAuthError& error,
+ const std::vector<std::pair<std::string, bool> >& accounts);
+
+ // Used during periodic reconciliation.
+ void StartReconcile();
+ void FinishReconcile();
+ void AbortReconcile();
+ void CalculateIfReconcileIsDone();
+ void ScheduleStartReconcileIfChromeAccountsChanged();
+ void HandleSuccessfulAccountIdCheck(const std::string& account_id);
+ void HandleFailedAccountIdCheck(const std::string& account_id);
+ void HandleRefreshTokenFetched(const std::string& account_id,
+ const std::string& refresh_token);
+
+ void GetAccountsFromCookie(GetAccountsFromCookieCallback callback);
+ void ContinueReconcileActionAfterGetGaiaAccounts(
+ const GoogleServiceAuthError& error,
+ const std::vector<std::pair<std::string, bool> >& accounts);
+ void ValidateAccountsFromTokenService();
+
+ void OnCookieChanged(const net::CanonicalCookie* cookie);
+
+ // Overriden from GaiaAuthConsumer.
+ virtual void OnListAccountsSuccess(const std::string& data) OVERRIDE;
+ virtual void OnListAccountsFailure(const GoogleServiceAuthError& error)
+ OVERRIDE;
+
+ // Overriden from MergeSessionHelper::Observer.
+ virtual void MergeSessionCompleted(const std::string& account_id,
+ const GoogleServiceAuthError& error)
+ OVERRIDE;
+
+ // Overriden from OAuth2TokenService::Consumer.
+ virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) OVERRIDE;
+ virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ // Overriden from OAuth2TokenService::Observer.
+ virtual void OnRefreshTokenAvailable(const std::string& account_id) OVERRIDE;
+ virtual void OnRefreshTokenRevoked(const std::string& account_id) OVERRIDE;
+ virtual void OnRefreshTokensLoaded() OVERRIDE;
+
+ // Overriden from SigninManagerBase::Observer.
+ virtual void GoogleSigninSucceeded(const std::string& username,
+ const std::string& password) OVERRIDE;
+ virtual void GoogleSignedOut(const std::string& username) OVERRIDE;
+
+ void MayBeDoNextListAccounts();
+
+ // The ProfileOAuth2TokenService associated with this reconcilor.
+ ProfileOAuth2TokenService* token_service_;
+
+ // The SigninManager associated with this reconcilor.
+ SigninManagerBase* signin_manager_;
+
+ // The SigninClient associated with this reconcilor.
+ SigninClient* client_;
+
+ base::RepeatingTimer<AccountReconcilor> reconciliation_timer_;
+ MergeSessionHelper merge_session_helper_;
+ scoped_ptr<GaiaAuthFetcher> gaia_fetcher_;
+ bool registered_with_token_service_;
+
+ // True while the reconcilor is busy checking or managing the accounts in
+ // this profile.
+ bool is_reconcile_started_;
+
+ // Used during reconcile action.
+ // These members are used used to validate the gaia cookie. |gaia_accounts_|
+ // holds the state of google accounts in the gaia cookie. Each element is
+ // a pair that holds the email address of the account and a boolean that
+ // indicates whether the account is valid or not. The accounts in the vector
+ // are ordered the in same way as the gaia cookie.
+ bool are_gaia_accounts_set_;
+ std::vector<std::pair<std::string, bool> > gaia_accounts_;
+
+ // Used during reconcile action.
+ // These members are used to validate the tokens in OAuth2TokenService.
+ std::string primary_account_;
+ std::vector<std::string> chrome_accounts_;
+ scoped_ptr<OAuth2TokenService::Request>* requests_;
+ ScopedVector<UserIdFetcher> user_id_fetchers_;
+ ScopedVector<SigninOAuthHelper> refresh_token_fetchers_;
+ EmailSet valid_chrome_accounts_;
+ EmailSet invalid_chrome_accounts_;
+ std::vector<std::string> add_to_cookie_;
+ std::vector<std::pair<std::string, int> > add_to_chrome_;
+
+ std::deque<GetAccountsFromCookieCallback> get_gaia_accounts_callbacks_;
+
+ DISALLOW_COPY_AND_ASSIGN(AccountReconcilor);
+};
+
+#endif // COMPONENTS_SIGNIN_CORE_BROWSER_ACCOUNT_RECONCILOR_H_
diff --git a/components/signin/core/browser/signin_oauth_helper.cc b/components/signin/core/browser/signin_oauth_helper.cc
new file mode 100644
index 0000000..db51a59
--- /dev/null
+++ b/components/signin/core/browser/signin_oauth_helper.cc
@@ -0,0 +1,68 @@
+// Copyright 2014 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 "components/signin/core/browser/signin_oauth_helper.h"
+
+#include "base/message_loop/message_loop.h"
+#include "google_apis/gaia/gaia_auth_fetcher.h"
+#include "google_apis/gaia/gaia_constants.h"
+
+SigninOAuthHelper::SigninOAuthHelper(net::URLRequestContextGetter* getter,
+ const std::string& session_index,
+ Consumer* consumer)
+ : gaia_auth_fetcher_(this, GaiaConstants::kChromeSource, getter),
+ consumer_(consumer) {
+ DCHECK(consumer_);
+ DCHECK(getter);
+ DCHECK(!session_index.empty());
+ gaia_auth_fetcher_.StartCookieForOAuthLoginTokenExchange(session_index);
+}
+
+SigninOAuthHelper::~SigninOAuthHelper() {}
+
+void SigninOAuthHelper::OnClientOAuthSuccess(const ClientOAuthResult& result) {
+ refresh_token_ = result.refresh_token;
+ gaia_auth_fetcher_.StartOAuthLogin(result.access_token,
+ GaiaConstants::kGaiaService);
+}
+
+void SigninOAuthHelper::OnClientOAuthFailure(
+ const GoogleServiceAuthError& error) {
+ VLOG(1) << "SigninOAuthHelper::OnClientOAuthFailure: " << error.ToString();
+ consumer_->OnSigninOAuthInformationFailure(error);
+}
+
+void SigninOAuthHelper::OnClientLoginSuccess(const ClientLoginResult& result) {
+ gaia_auth_fetcher_.StartGetUserInfo(result.lsid);
+}
+
+void SigninOAuthHelper::OnClientLoginFailure(
+ const GoogleServiceAuthError& error) {
+ VLOG(1) << "SigninOAuthHelper::OnClientLoginFailure: " << error.ToString();
+ consumer_->OnSigninOAuthInformationFailure(error);
+}
+
+void SigninOAuthHelper::OnGetUserInfoSuccess(const UserInfoMap& data) {
+ UserInfoMap::const_iterator email_iter = data.find("email");
+ UserInfoMap::const_iterator display_email_iter = data.find("displayEmail");
+ if (email_iter == data.end() || display_email_iter == data.end()) {
+ VLOG(1) << "SigninOAuthHelper::OnGetUserInfoSuccess: no email found:"
+ << " email=" << email_iter->second
+ << " displayEmail=" << display_email_iter->second;
+ consumer_->OnSigninOAuthInformationFailure(
+ GoogleServiceAuthError(GoogleServiceAuthError::SERVICE_ERROR));
+ } else {
+ VLOG(1) << "SigninOAuthHelper::OnGetUserInfoSuccess:"
+ << " email=" << email_iter->second
+ << " displayEmail=" << display_email_iter->second;
+ consumer_->OnSigninOAuthInformationAvailable(
+ email_iter->second, display_email_iter->second, refresh_token_);
+ }
+}
+
+void SigninOAuthHelper::OnGetUserInfoFailure(
+ const GoogleServiceAuthError& error) {
+ VLOG(1) << "SigninOAuthHelper::OnGetUserInfoFailure : " << error.ToString();
+ consumer_->OnSigninOAuthInformationFailure(error);
+}
diff --git a/components/signin/core/browser/signin_oauth_helper.h b/components/signin/core/browser/signin_oauth_helper.h
new file mode 100644
index 0000000..d5c046c
--- /dev/null
+++ b/components/signin/core/browser/signin_oauth_helper.h
@@ -0,0 +1,61 @@
+// Copyright 2014 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 COMPONENTS_SIGNIN_CORE_BROWSER_SIGNIN_OAUTH_HELPER_H_
+#define COMPONENTS_SIGNIN_CORE_BROWSER_SIGNIN_OAUTH_HELPER_H_
+
+#include <string>
+
+#include "base/memory/scoped_ptr.h"
+#include "google_apis/gaia/gaia_auth_consumer.h"
+#include "google_apis/gaia/gaia_auth_fetcher.h"
+
+// Retrieves the OAuth2 information from an already signed in cookie jar.
+// The information retrieved is: username, refresh token.
+class SigninOAuthHelper : public GaiaAuthConsumer {
+ public:
+ // Implemented by users of SigninOAuthHelper to know then helper is finished.
+ class Consumer {
+ public:
+ virtual ~Consumer() {}
+
+ // Called when all the information is retrieved successfully. |email|
+ // and |display_email| correspond to the gaia properties called "email"
+ // and "displayEmail" associated with the signed in account. |refresh_token|
+ // is the account's login-scoped oauth2 refresh token.
+ virtual void OnSigninOAuthInformationAvailable(
+ const std::string& email,
+ const std::string& display_email,
+ const std::string& refresh_token) {}
+
+ // Called when an error occurs while getting the information.
+ virtual void OnSigninOAuthInformationFailure(
+ const GoogleServiceAuthError& error) {}
+ };
+
+ explicit SigninOAuthHelper(net::URLRequestContextGetter* getter,
+ const std::string& session_index,
+ Consumer* consumer);
+ virtual ~SigninOAuthHelper();
+
+ private:
+ // Overridden from GaiaAuthConsumer.
+ virtual void OnClientOAuthSuccess(const ClientOAuthResult& result) OVERRIDE;
+ virtual void OnClientOAuthFailure(const GoogleServiceAuthError& error)
+ OVERRIDE;
+ virtual void OnClientLoginSuccess(const ClientLoginResult& result) OVERRIDE;
+ virtual void OnClientLoginFailure(const GoogleServiceAuthError& error)
+ OVERRIDE;
+ virtual void OnGetUserInfoSuccess(const UserInfoMap& data) OVERRIDE;
+ virtual void OnGetUserInfoFailure(const GoogleServiceAuthError& error)
+ OVERRIDE;
+
+ GaiaAuthFetcher gaia_auth_fetcher_;
+ std::string refresh_token_;
+ Consumer* consumer_;
+
+ DISALLOW_COPY_AND_ASSIGN(SigninOAuthHelper);
+};
+
+#endif // COMPONENTS_SIGNIN_CORE_BROWSER_SIGNIN_OAUTH_HELPER_H_