diff options
author | blundell@chromium.org <blundell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-03 15:48:10 +0000 |
---|---|---|
committer | blundell@chromium.org <blundell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-03 15:48:10 +0000 |
commit | b47dbc3e7ef0bd0145d83ca4ac8bb7bc76a4d5a9 (patch) | |
tree | dad4f1d03572895e81bfecf151437a51f331a595 /components/signin | |
parent | f48405e6b24e0e737be3aa2df1f13b6d9334e979 (diff) | |
download | chromium_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.cc | 723 | ||||
-rw-r--r-- | components/signin/core/browser/account_reconcilor.h | 252 | ||||
-rw-r--r-- | components/signin/core/browser/signin_oauth_helper.cc | 68 | ||||
-rw-r--r-- | components/signin/core/browser/signin_oauth_helper.h | 61 |
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_ |