From 0d02c33dbad9f55c60f0054453f602d7516bf99c Mon Sep 17 00:00:00 2001 From: "courage@chromium.org" Date: Thu, 19 Jun 2014 20:56:50 +0000 Subject: [PATCH] Add IdentityProvider-based AccountTracker to google_apis This is a re-implementation of extensions::AccountTracker, built on IdentityProvider instead of SigninManager and ProfileOAuth2TokenService. Removing the dependency on chrome/browser will allow GCM to use this code. BUG=374988 Review URL: https://codereview.chromium.org/336253002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278476 0039d316-1c4b-4281-b951-d872f2087c98 --- google_apis/gaia/account_tracker.cc | 293 +++++++ google_apis/gaia/account_tracker.h | 149 ++++ google_apis/gaia/account_tracker_unittest.cc | 816 ++++++++++++++++++ google_apis/gaia/fake_oauth2_token_service.cc | 42 +- google_apis/gaia/fake_oauth2_token_service.h | 22 + google_apis/google_apis.gyp | 3 + 6 files changed, 1324 insertions(+), 1 deletion(-) create mode 100644 google_apis/gaia/account_tracker.cc create mode 100644 google_apis/gaia/account_tracker.h create mode 100644 google_apis/gaia/account_tracker_unittest.cc diff --git a/google_apis/gaia/account_tracker.cc b/google_apis/gaia/account_tracker.cc new file mode 100644 index 00000000000000..65d28369be97d7 --- /dev/null +++ b/google_apis/gaia/account_tracker.cc @@ -0,0 +1,293 @@ +// 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 "google_apis/gaia/account_tracker.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "net/url_request/url_request_context_getter.h" + +namespace gaia { + +AccountTracker::AccountTracker( + IdentityProvider* identity_provider, + net::URLRequestContextGetter* request_context_getter) + : identity_provider_(identity_provider), + request_context_getter_(request_context_getter), + shutdown_called_(false) { + identity_provider_->AddObserver(this); + identity_provider_->GetTokenService()->AddObserver(this); +} + +AccountTracker::~AccountTracker() { + DCHECK(shutdown_called_); +} + +void AccountTracker::Shutdown() { + shutdown_called_ = true; + STLDeleteValues(&user_info_requests_); + identity_provider_->GetTokenService()->RemoveObserver(this); + identity_provider_->RemoveObserver(this); +} + +void AccountTracker::AddObserver(Observer* observer) { + observer_list_.AddObserver(observer); +} + +void AccountTracker::RemoveObserver(Observer* observer) { + observer_list_.RemoveObserver(observer); +} + +std::vector AccountTracker::GetAccounts() const { + const std::string active_account_id = + identity_provider_->GetActiveAccountId(); + std::vector accounts; + + for (std::map::const_iterator it = + accounts_.begin(); + it != accounts_.end(); + ++it) { + const AccountState& state = it->second; + bool is_visible = state.is_signed_in && !state.ids.gaia.empty(); + + if (it->first == active_account_id) { + if (is_visible) + accounts.insert(accounts.begin(), state.ids); + else + return std::vector(); + + } else if (is_visible) { + accounts.push_back(state.ids); + } + } + return accounts; +} + +AccountIds AccountTracker::FindAccountIdsByGaiaId(const std::string& gaia_id) { + for (std::map::const_iterator it = + accounts_.begin(); + it != accounts_.end(); + ++it) { + const AccountState& state = it->second; + if (state.ids.gaia == gaia_id) { + return state.ids; + } + } + + return AccountIds(); +} + +void AccountTracker::OnRefreshTokenAvailable(const std::string& account_id) { + // Ignore refresh tokens if there is no active account ID at all. + if (identity_provider_->GetActiveAccountId().empty()) + return; + + DVLOG(1) << "AVAILABLE " << account_id; + UpdateSignInState(account_id, true); +} + +void AccountTracker::OnRefreshTokenRevoked(const std::string& account_id) { + DVLOG(1) << "REVOKED " << account_id; + UpdateSignInState(account_id, false); +} + +void AccountTracker::OnActiveAccountLogin() { + std::vector accounts = + identity_provider_->GetTokenService()->GetAccounts(); + + DVLOG(1) << "LOGIN " << accounts.size() << " accounts available."; + + for (std::vector::const_iterator it = accounts.begin(); + it != accounts.end(); + ++it) { + OnRefreshTokenAvailable(*it); + } +} + +void AccountTracker::OnActiveAccountLogout() { + DVLOG(1) << "LOGOUT"; + StopTrackingAllAccounts(); +} + +void AccountTracker::SetAccountStateForTest(AccountIds ids, bool is_signed_in) { + accounts_[ids.account_key].ids = ids; + accounts_[ids.account_key].is_signed_in = is_signed_in; + + DVLOG(1) << "SetAccountStateForTest " << ids.account_key << ":" + << is_signed_in; + + if (VLOG_IS_ON(1)) { + for (std::map::const_iterator it = + accounts_.begin(); + it != accounts_.end(); + ++it) { + DVLOG(1) << it->first << ":" << it->second.is_signed_in; + } + } +} + +void AccountTracker::NotifyAccountAdded(const AccountState& account) { + DCHECK(!account.ids.gaia.empty()); + FOR_EACH_OBSERVER( + Observer, observer_list_, OnAccountAdded(account.ids)); +} + +void AccountTracker::NotifyAccountRemoved(const AccountState& account) { + DCHECK(!account.ids.gaia.empty()); + FOR_EACH_OBSERVER( + Observer, observer_list_, OnAccountRemoved(account.ids)); +} + +void AccountTracker::NotifySignInChanged(const AccountState& account) { + DCHECK(!account.ids.gaia.empty()); + FOR_EACH_OBSERVER(Observer, + observer_list_, + OnAccountSignInChanged(account.ids, account.is_signed_in)); +} + +void AccountTracker::UpdateSignInState(const std::string account_key, + bool is_signed_in) { + StartTrackingAccount(account_key); + AccountState& account = accounts_[account_key]; + bool needs_gaia_id = account.ids.gaia.empty(); + bool was_signed_in = account.is_signed_in; + account.is_signed_in = is_signed_in; + + if (needs_gaia_id && is_signed_in) + StartFetchingUserInfo(account_key); + + if (!needs_gaia_id && (was_signed_in != is_signed_in)) + NotifySignInChanged(account); +} + +void AccountTracker::StartTrackingAccount(const std::string account_key) { + if (!ContainsKey(accounts_, account_key)) { + DVLOG(1) << "StartTracking " << account_key; + AccountState account_state; + account_state.ids.account_key = account_key; + account_state.ids.email = account_key; + account_state.is_signed_in = false; + accounts_.insert(make_pair(account_key, account_state)); + } +} + +void AccountTracker::StopTrackingAccount(const std::string account_key) { + DVLOG(1) << "StopTracking " << account_key; + if (ContainsKey(accounts_, account_key)) { + AccountState& account = accounts_[account_key]; + if (!account.ids.gaia.empty()) { + UpdateSignInState(account_key, false); + NotifyAccountRemoved(account); + } + accounts_.erase(account_key); + } + + if (ContainsKey(user_info_requests_, account_key)) + DeleteFetcher(user_info_requests_[account_key]); +} + +void AccountTracker::StopTrackingAllAccounts() { + while (!accounts_.empty()) + StopTrackingAccount(accounts_.begin()->first); +} + +void AccountTracker::StartFetchingUserInfo(const std::string account_key) { + if (ContainsKey(user_info_requests_, account_key)) + DeleteFetcher(user_info_requests_[account_key]); + + DVLOG(1) << "StartFetching " << account_key; + AccountIdFetcher* fetcher = + new AccountIdFetcher(identity_provider_->GetTokenService(), + request_context_getter_.get(), + this, + account_key); + user_info_requests_[account_key] = fetcher; + fetcher->Start(); +} + +void AccountTracker::OnUserInfoFetchSuccess(AccountIdFetcher* fetcher, + const std::string& gaia_id) { + const std::string& account_key = fetcher->account_key(); + DCHECK(ContainsKey(accounts_, account_key)); + AccountState& account = accounts_[account_key]; + + account.ids.gaia = gaia_id; + NotifyAccountAdded(account); + + if (account.is_signed_in) + NotifySignInChanged(account); + + DeleteFetcher(fetcher); +} + +void AccountTracker::OnUserInfoFetchFailure(AccountIdFetcher* fetcher) { + LOG(WARNING) << "Failed to get UserInfo for " << fetcher->account_key(); + std::string key = fetcher->account_key(); + DeleteFetcher(fetcher); + StopTrackingAccount(key); +} + +void AccountTracker::DeleteFetcher(AccountIdFetcher* fetcher) { + DVLOG(1) << "DeleteFetcher " << fetcher->account_key(); + const std::string& account_key = fetcher->account_key(); + DCHECK(ContainsKey(user_info_requests_, account_key)); + DCHECK_EQ(fetcher, user_info_requests_[account_key]); + user_info_requests_.erase(account_key); + delete fetcher; +} + +AccountIdFetcher::AccountIdFetcher( + OAuth2TokenService* token_service, + net::URLRequestContextGetter* request_context_getter, + AccountTracker* tracker, + const std::string& account_key) + : OAuth2TokenService::Consumer("gaia_account_tracker"), + token_service_(token_service), + request_context_getter_(request_context_getter), + tracker_(tracker), + account_key_(account_key) { +} + +AccountIdFetcher::~AccountIdFetcher() {} + +void AccountIdFetcher::Start() { + login_token_request_ = token_service_->StartRequest( + account_key_, OAuth2TokenService::ScopeSet(), this); +} + +void AccountIdFetcher::OnGetTokenSuccess( + const OAuth2TokenService::Request* request, + const std::string& access_token, + const base::Time& expiration_time) { + DCHECK_EQ(request, login_token_request_.get()); + + gaia_oauth_client_.reset(new gaia::GaiaOAuthClient(request_context_getter_)); + + const int kMaxGetUserIdRetries = 3; + gaia_oauth_client_->GetUserId(access_token, kMaxGetUserIdRetries, this); +} + +void AccountIdFetcher::OnGetTokenFailure( + const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) { + LOG(ERROR) << "OnGetTokenFailure: " << error.ToString(); + DCHECK_EQ(request, login_token_request_.get()); + tracker_->OnUserInfoFetchFailure(this); +} + +void AccountIdFetcher::OnGetUserIdResponse(const std::string& gaia_id) { + tracker_->OnUserInfoFetchSuccess(this, gaia_id); +} + +void AccountIdFetcher::OnOAuthError() { + LOG(ERROR) << "OnOAuthError"; + tracker_->OnUserInfoFetchFailure(this); +} + +void AccountIdFetcher::OnNetworkError(int response_code) { + LOG(ERROR) << "OnNetworkError " << response_code; + tracker_->OnUserInfoFetchFailure(this); +} + +} // namespace gaia diff --git a/google_apis/gaia/account_tracker.h b/google_apis/gaia/account_tracker.h new file mode 100644 index 00000000000000..5a29ea624915cc --- /dev/null +++ b/google_apis/gaia/account_tracker.h @@ -0,0 +1,149 @@ +// 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 GOOGLE_APIS_GAIA_ACCOUNT_TRACKER_H_ +#define GOOGLE_APIS_GAIA_ACCOUNT_TRACKER_H_ + +#include +#include +#include + +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "google_apis/gaia/gaia_oauth_client.h" +#include "google_apis/gaia/identity_provider.h" +#include "google_apis/gaia/oauth2_token_service.h" + +class GoogleServiceAuthError; + +namespace net { +class URLRequestContextGetter; +} + +namespace gaia { + +struct AccountIds { + std::string account_key; // The account ID used by OAuth2TokenService. + std::string gaia; + std::string email; +}; + +class AccountIdFetcher; + +// The AccountTracker keeps track of what accounts exist on the +// profile and the state of their credentials. The tracker fetches the +// gaia ID of each account it knows about. +// +// The AccountTracker maintains these invariants: +// 1. Events are only fired after the gaia ID has been fetched. +// 2. Add/Remove and SignIn/SignOut pairs are always generated in order. +// 3. SignIn follows Add, and there will be a SignOut between SignIn & Remove. +// 4. If there is no primary account, there are no other accounts. +class AccountTracker : public OAuth2TokenService::Observer, + public IdentityProvider::Observer { + public: + AccountTracker(IdentityProvider* identity_provider, + net::URLRequestContextGetter* request_context_getter); + virtual ~AccountTracker(); + + class Observer { + public: + virtual void OnAccountAdded(const AccountIds& ids) = 0; + virtual void OnAccountRemoved(const AccountIds& ids) = 0; + virtual void OnAccountSignInChanged(const AccountIds& ids, + bool is_signed_in) = 0; + }; + + void Shutdown(); + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + // Returns the list of accounts that are signed in, and for which gaia IDs + // have been fetched. The primary account for the profile will be first + // in the vector. Additional accounts will be in order of their gaia IDs. + std::vector GetAccounts() const; + AccountIds FindAccountIdsByGaiaId(const std::string& gaia_id); + + // OAuth2TokenService::Observer implementation. + virtual void OnRefreshTokenAvailable(const std::string& account_key) OVERRIDE; + virtual void OnRefreshTokenRevoked(const std::string& account_key) OVERRIDE; + + void OnUserInfoFetchSuccess(AccountIdFetcher* fetcher, + const std::string& gaia_id); + void OnUserInfoFetchFailure(AccountIdFetcher* fetcher); + + // IdentityProvider::Observer implementation. + virtual void OnActiveAccountLogin() OVERRIDE; + virtual void OnActiveAccountLogout() OVERRIDE; + + // Sets the state of an account. Does not fire notifications. + void SetAccountStateForTest(AccountIds ids, bool is_signed_in); + + IdentityProvider* identity_provider() { return identity_provider_; } + + private: + struct AccountState { + AccountIds ids; + bool is_signed_in; + }; + + void NotifyAccountAdded(const AccountState& account); + void NotifyAccountRemoved(const AccountState& account); + void NotifySignInChanged(const AccountState& account); + + void UpdateSignInState(const std::string account_key, bool is_signed_in); + + void StartTrackingAccount(const std::string account_key); + void StopTrackingAccount(const std::string account_key); + void StopTrackingAllAccounts(); + void StartFetchingUserInfo(const std::string account_key); + void DeleteFetcher(AccountIdFetcher* fetcher); + + IdentityProvider* identity_provider_; // Not owned. + scoped_refptr request_context_getter_; + std::map user_info_requests_; + std::map accounts_; + ObserverList observer_list_; + bool shutdown_called_; +}; + +class AccountIdFetcher : public OAuth2TokenService::Consumer, + public gaia::GaiaOAuthClient::Delegate { + public: + AccountIdFetcher(OAuth2TokenService* token_service, + net::URLRequestContextGetter* request_context_getter, + AccountTracker* tracker, + const std::string& account_key); + virtual ~AccountIdFetcher(); + + const std::string& account_key() { return account_key_; } + + void Start(); + + // OAuth2TokenService::Consumer implementation. + 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; + + // gaia::GaiaOAuthClient::Delegate implementation. + virtual void OnGetUserIdResponse(const std::string& gaia_id) OVERRIDE; + virtual void OnOAuthError() OVERRIDE; + virtual void OnNetworkError(int response_code) OVERRIDE; + + private: + OAuth2TokenService* token_service_; + net::URLRequestContextGetter* request_context_getter_; + AccountTracker* tracker_; + const std::string account_key_; + + scoped_ptr login_token_request_; + scoped_ptr gaia_oauth_client_; +}; + +} // namespace extensions + +#endif // GOOGLE_APIS_GAIA_ACCOUNT_TRACKER_H_ diff --git a/google_apis/gaia/account_tracker_unittest.cc b/google_apis/gaia/account_tracker_unittest.cc new file mode 100644 index 00000000000000..fe8346eeb25fcb --- /dev/null +++ b/google_apis/gaia/account_tracker_unittest.cc @@ -0,0 +1,816 @@ +// 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 "google_apis/gaia/account_tracker.h" + +#include +#include + +#include "base/message_loop/message_loop.h" +#include "base/strings/stringprintf.h" +#include "google_apis/gaia/fake_identity_provider.h" +#include "google_apis/gaia/fake_oauth2_token_service.h" +#include "google_apis/gaia/gaia_oauth_client.h" +#include "net/http/http_status_code.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kPrimaryAccountKey[] = "primary_account@example.com"; + +enum TrackingEventType { + ADDED, + REMOVED, + SIGN_IN, + SIGN_OUT +}; + +std::string AccountKeyToObfuscatedId(const std::string email) { + return "obfid-" + email; +} + +class TrackingEvent { + public: + TrackingEvent(TrackingEventType type, + const std::string& account_key, + const std::string& gaia_id) + : type_(type), + account_key_(account_key), + gaia_id_(gaia_id) {} + + TrackingEvent(TrackingEventType type, + const std::string& account_key) + : type_(type), + account_key_(account_key), + gaia_id_(AccountKeyToObfuscatedId(account_key)) {} + + bool operator==(const TrackingEvent& event) const { + return type_ == event.type_ && account_key_ == event.account_key_ && + gaia_id_ == event.gaia_id_; + } + + std::string ToString() const { + const char * typestr = "INVALID"; + switch (type_) { + case ADDED: + typestr = "ADD"; + break; + case REMOVED: + typestr = "REM"; + break; + case SIGN_IN: + typestr = " IN"; + break; + case SIGN_OUT: + typestr = "OUT"; + break; + } + return base::StringPrintf("{ type: %s, email: %s, gaia: %s }", + typestr, + account_key_.c_str(), + gaia_id_.c_str()); + } + + private: + friend bool CompareByUser(TrackingEvent a, TrackingEvent b); + + TrackingEventType type_; + std::string account_key_; + std::string gaia_id_; +}; + +bool CompareByUser(TrackingEvent a, TrackingEvent b) { + return a.account_key_ < b.account_key_; +} + +std::string Str(const std::vector& events) { + std::string str = "["; + bool needs_comma = false; + for (std::vector::const_iterator it = + events.begin(); it != events.end(); ++it) { + if (needs_comma) + str += ",\n "; + needs_comma = true; + str += it->ToString(); + } + str += "]"; + return str; +} + +} // namespace + +namespace gaia { + +class AccountTrackerObserver : public AccountTracker::Observer { + public: + AccountTrackerObserver() {} + virtual ~AccountTrackerObserver() {} + + testing::AssertionResult CheckEvents(); + testing::AssertionResult CheckEvents(const TrackingEvent& e1); + testing::AssertionResult CheckEvents(const TrackingEvent& e1, + const TrackingEvent& e2); + testing::AssertionResult CheckEvents(const TrackingEvent& e1, + const TrackingEvent& e2, + const TrackingEvent& e3); + testing::AssertionResult CheckEvents(const TrackingEvent& e1, + const TrackingEvent& e2, + const TrackingEvent& e3, + const TrackingEvent& e4); + testing::AssertionResult CheckEvents(const TrackingEvent& e1, + const TrackingEvent& e2, + const TrackingEvent& e3, + const TrackingEvent& e4, + const TrackingEvent& e5); + testing::AssertionResult CheckEvents(const TrackingEvent& e1, + const TrackingEvent& e2, + const TrackingEvent& e3, + const TrackingEvent& e4, + const TrackingEvent& e5, + const TrackingEvent& e6); + void Clear(); + void SortEventsByUser(); + + // AccountTracker::Observer implementation + virtual void OnAccountAdded(const AccountIds& ids) OVERRIDE; + virtual void OnAccountRemoved(const AccountIds& ids) OVERRIDE; + virtual void OnAccountSignInChanged(const AccountIds& ids, bool is_signed_in) + OVERRIDE; + + private: + testing::AssertionResult CheckEvents( + const std::vector& events); + + std::vector events_; +}; + +void AccountTrackerObserver::OnAccountAdded(const AccountIds& ids) { + events_.push_back(TrackingEvent(ADDED, ids.email, ids.gaia)); +} + +void AccountTrackerObserver::OnAccountRemoved(const AccountIds& ids) { + events_.push_back(TrackingEvent(REMOVED, ids.email, ids.gaia)); +} + +void AccountTrackerObserver::OnAccountSignInChanged(const AccountIds& ids, + bool is_signed_in) { + events_.push_back( + TrackingEvent(is_signed_in ? SIGN_IN : SIGN_OUT, ids.email, ids.gaia)); +} + +void AccountTrackerObserver::Clear() { + events_.clear(); +} + +void AccountTrackerObserver::SortEventsByUser() { + std::stable_sort(events_.begin(), events_.end(), CompareByUser); +} + +testing::AssertionResult AccountTrackerObserver::CheckEvents() { + std::vector events; + return CheckEvents(events); +} + +testing::AssertionResult AccountTrackerObserver::CheckEvents( + const TrackingEvent& e1) { + std::vector events; + events.push_back(e1); + return CheckEvents(events); +} + +testing::AssertionResult AccountTrackerObserver::CheckEvents( + const TrackingEvent& e1, + const TrackingEvent& e2) { + std::vector events; + events.push_back(e1); + events.push_back(e2); + return CheckEvents(events); +} + +testing::AssertionResult AccountTrackerObserver::CheckEvents( + const TrackingEvent& e1, + const TrackingEvent& e2, + const TrackingEvent& e3) { + std::vector events; + events.push_back(e1); + events.push_back(e2); + events.push_back(e3); + return CheckEvents(events); +} + +testing::AssertionResult AccountTrackerObserver::CheckEvents( + const TrackingEvent& e1, + const TrackingEvent& e2, + const TrackingEvent& e3, + const TrackingEvent& e4) { + std::vector events; + events.push_back(e1); + events.push_back(e2); + events.push_back(e3); + events.push_back(e4); + return CheckEvents(events); +} + +testing::AssertionResult AccountTrackerObserver::CheckEvents( + const TrackingEvent& e1, + const TrackingEvent& e2, + const TrackingEvent& e3, + const TrackingEvent& e4, + const TrackingEvent& e5) { + std::vector events; + events.push_back(e1); + events.push_back(e2); + events.push_back(e3); + events.push_back(e4); + events.push_back(e5); + return CheckEvents(events); +} + +testing::AssertionResult AccountTrackerObserver::CheckEvents( + const TrackingEvent& e1, + const TrackingEvent& e2, + const TrackingEvent& e3, + const TrackingEvent& e4, + const TrackingEvent& e5, + const TrackingEvent& e6) { + std::vector events; + events.push_back(e1); + events.push_back(e2); + events.push_back(e3); + events.push_back(e4); + events.push_back(e5); + events.push_back(e6); + return CheckEvents(events); +} + +testing::AssertionResult AccountTrackerObserver::CheckEvents( + const std::vector& events) { + std::string maybe_newline = (events.size() + events_.size()) > 2 ? "\n" : ""; + testing::AssertionResult result( + (events_ == events) + ? testing::AssertionSuccess() + : (testing::AssertionFailure() + << "Expected " << maybe_newline << Str(events) << ", " + << maybe_newline << "Got " << maybe_newline << Str(events_))); + events_.clear(); + return result; +} + +class IdentityAccountTrackerTest : public testing::Test { + public: + IdentityAccountTrackerTest() {} + + virtual ~IdentityAccountTrackerTest() {} + + virtual void SetUp() OVERRIDE { + + fake_oauth2_token_service_.reset(new FakeOAuth2TokenService()); + + fake_identity_provider_.reset( + new FakeIdentityProvider(fake_oauth2_token_service_.get())); + + account_tracker_.reset( + new AccountTracker(fake_identity_provider_.get(), + new net::TestURLRequestContextGetter( + message_loop_.message_loop_proxy()))); + account_tracker_->AddObserver(&observer_); + } + + virtual void TearDown() OVERRIDE { + account_tracker_->RemoveObserver(&observer_); + account_tracker_->Shutdown(); + } + + AccountTrackerObserver* observer() { + return &observer_; + } + + AccountTracker* account_tracker() { + return account_tracker_.get(); + } + + // Helpers to pass fake events to the tracker. + + void NotifyLogin(const std::string account_key) { + identity_provider()->LogIn(account_key); + } + + void NotifyLogout() { identity_provider()->LogOut(); } + + void NotifyTokenAvailable(const std::string& username) { + fake_oauth2_token_service_->AddAccount(username); + } + + void NotifyTokenRevoked(const std::string& username) { + fake_oauth2_token_service_->RemoveAccount(username); + } + + // Helpers to fake access token and user info fetching + void IssueAccessToken(const std::string& username) { + fake_oauth2_token_service_->IssueAllTokensForAccount( + username, "access_token-" + username, base::Time::Max()); + } + + std::string GetValidTokenInfoResponse(const std::string account_key) { + return std::string("{ \"id\": \"") + AccountKeyToObfuscatedId(account_key) + + "\" }"; + } + + void ReturnOAuthUrlFetchResults(int fetcher_id, + net::HttpStatusCode response_code, + const std::string& response_string); + + void ReturnOAuthUrlFetchSuccess(const std::string& account_key); + void ReturnOAuthUrlFetchFailure(const std::string& account_key); + + void SetupPrimaryLogin() { + // Initial setup for tests that start with a signed in profile. + NotifyLogin(kPrimaryAccountKey); + NotifyTokenAvailable(kPrimaryAccountKey); + ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey); + observer()->Clear(); + } + + std::string active_account_id() { + return identity_provider()->GetActiveAccountId(); + } + + private: + FakeIdentityProvider* identity_provider() { + return static_cast( + account_tracker_->identity_provider()); + } + + base::MessageLoopForIO message_loop_; // net:: stuff needs IO message loop. + net::TestURLFetcherFactory test_fetcher_factory_; + scoped_ptr fake_oauth2_token_service_; + scoped_ptr fake_identity_provider_; + + scoped_ptr account_tracker_; + AccountTrackerObserver observer_; +}; + +void IdentityAccountTrackerTest::ReturnOAuthUrlFetchResults( + int fetcher_id, + net::HttpStatusCode response_code, + const std::string& response_string) { + + net::TestURLFetcher* fetcher = + test_fetcher_factory_.GetFetcherByID(fetcher_id); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(response_code); + fetcher->SetResponseString(response_string); + fetcher->delegate()->OnURLFetchComplete(fetcher); +} + +void IdentityAccountTrackerTest::ReturnOAuthUrlFetchSuccess( + const std::string& account_key) { + IssueAccessToken(account_key); + ReturnOAuthUrlFetchResults(gaia::GaiaOAuthClient::kUrlFetcherId, + net::HTTP_OK, + GetValidTokenInfoResponse(account_key)); +} + +void IdentityAccountTrackerTest::ReturnOAuthUrlFetchFailure( + const std::string& account_key) { + IssueAccessToken(account_key); + ReturnOAuthUrlFetchResults( + gaia::GaiaOAuthClient::kUrlFetcherId, net::HTTP_BAD_REQUEST, ""); +} + +// Primary tests just involve the Active account + +TEST_F(IdentityAccountTrackerTest, PrimaryNoEventsBeforeLogin) { + NotifyTokenAvailable(kPrimaryAccountKey); + NotifyTokenRevoked(kPrimaryAccountKey); + NotifyLogout(); + EXPECT_TRUE(observer()->CheckEvents()); +} + +TEST_F(IdentityAccountTrackerTest, PrimaryLoginThenTokenAvailable) { + NotifyLogin(kPrimaryAccountKey); + NotifyTokenAvailable(kPrimaryAccountKey); + EXPECT_TRUE(observer()->CheckEvents()); + + ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey); + EXPECT_TRUE( + observer()->CheckEvents(TrackingEvent(ADDED, kPrimaryAccountKey), + TrackingEvent(SIGN_IN, kPrimaryAccountKey))); +} + +TEST_F(IdentityAccountTrackerTest, PrimaryTokenAvailableThenLogin) { + NotifyTokenAvailable(kPrimaryAccountKey); + EXPECT_TRUE(observer()->CheckEvents()); + + NotifyLogin(kPrimaryAccountKey); + ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey); + EXPECT_TRUE( + observer()->CheckEvents(TrackingEvent(ADDED, kPrimaryAccountKey), + TrackingEvent(SIGN_IN, kPrimaryAccountKey))); +} + +TEST_F(IdentityAccountTrackerTest, PrimaryTokenAvailableAndRevokedThenLogin) { + NotifyTokenAvailable(kPrimaryAccountKey); + EXPECT_TRUE(observer()->CheckEvents()); + + NotifyLogin(kPrimaryAccountKey); + ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey); + EXPECT_TRUE( + observer()->CheckEvents(TrackingEvent(ADDED, kPrimaryAccountKey), + TrackingEvent(SIGN_IN, kPrimaryAccountKey))); +} + +TEST_F(IdentityAccountTrackerTest, PrimaryRevokeThenLogout) { + NotifyLogin(kPrimaryAccountKey); + NotifyTokenAvailable(kPrimaryAccountKey); + ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey); + observer()->Clear(); + + NotifyTokenRevoked(kPrimaryAccountKey); + EXPECT_TRUE( + observer()->CheckEvents(TrackingEvent(SIGN_OUT, kPrimaryAccountKey))); + + NotifyLogout(); + EXPECT_TRUE( + observer()->CheckEvents(TrackingEvent(REMOVED, kPrimaryAccountKey))); +} + +TEST_F(IdentityAccountTrackerTest, PrimaryRevokeThenLogin) { + NotifyLogin(kPrimaryAccountKey); + NotifyTokenAvailable(kPrimaryAccountKey); + ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey); + NotifyTokenRevoked(kPrimaryAccountKey); + observer()->Clear(); + + NotifyLogin(kPrimaryAccountKey); + EXPECT_TRUE(observer()->CheckEvents()); +} + +TEST_F(IdentityAccountTrackerTest, PrimaryRevokeThenTokenAvailable) { + NotifyLogin(kPrimaryAccountKey); + NotifyTokenAvailable(kPrimaryAccountKey); + ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey); + NotifyTokenRevoked(kPrimaryAccountKey); + observer()->Clear(); + + NotifyTokenAvailable(kPrimaryAccountKey); + EXPECT_TRUE( + observer()->CheckEvents(TrackingEvent(SIGN_IN, kPrimaryAccountKey))); +} + +TEST_F(IdentityAccountTrackerTest, PrimaryLogoutThenRevoke) { + NotifyLogin(kPrimaryAccountKey); + NotifyTokenAvailable(kPrimaryAccountKey); + ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey); + observer()->Clear(); + + NotifyLogout(); + EXPECT_TRUE( + observer()->CheckEvents(TrackingEvent(SIGN_OUT, kPrimaryAccountKey), + TrackingEvent(REMOVED, kPrimaryAccountKey))); + + NotifyTokenRevoked(kPrimaryAccountKey); + EXPECT_TRUE(observer()->CheckEvents()); +} + +TEST_F(IdentityAccountTrackerTest, PrimaryLogoutFetchCancelAvailable) { + NotifyLogin(kPrimaryAccountKey); + NotifyTokenAvailable(kPrimaryAccountKey); + // TokenAvailable kicks off a fetch. Logout without satisfying it. + NotifyLogout(); + EXPECT_TRUE(observer()->CheckEvents()); + + NotifyLogin(kPrimaryAccountKey); + NotifyTokenAvailable(kPrimaryAccountKey); + ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, kPrimaryAccountKey), + TrackingEvent(SIGN_IN, kPrimaryAccountKey))); +} + +// Non-primary accounts + +TEST_F(IdentityAccountTrackerTest, Available) { + SetupPrimaryLogin(); + + NotifyTokenAvailable("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); + + ReturnOAuthUrlFetchSuccess("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com"), + TrackingEvent(SIGN_IN, "user@example.com"))); +} + +TEST_F(IdentityAccountTrackerTest, Revoke) { + SetupPrimaryLogin(); + + account_tracker()->OnRefreshTokenRevoked("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); +} + +TEST_F(IdentityAccountTrackerTest, AvailableRevokeAvailable) { + SetupPrimaryLogin(); + + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + NotifyTokenRevoked("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com"), + TrackingEvent(SIGN_IN, "user@example.com"), + TrackingEvent(SIGN_OUT, "user@example.com"))); + + NotifyTokenAvailable("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(SIGN_IN, "user@example.com"))); +} + +TEST_F(IdentityAccountTrackerTest, AvailableRevokeAvailableWithPendingFetch) { + SetupPrimaryLogin(); + + NotifyTokenAvailable("user@example.com"); + NotifyTokenRevoked("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); + + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com"), + TrackingEvent(SIGN_IN, "user@example.com"))); +} + +TEST_F(IdentityAccountTrackerTest, AvailableRevokeRevoke) { + SetupPrimaryLogin(); + + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + NotifyTokenRevoked("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com"), + TrackingEvent(SIGN_IN, "user@example.com"), + TrackingEvent(SIGN_OUT, "user@example.com"))); + + NotifyTokenRevoked("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); +} + +TEST_F(IdentityAccountTrackerTest, AvailableAvailable) { + SetupPrimaryLogin(); + + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com"), + TrackingEvent(SIGN_IN, "user@example.com"))); + + NotifyTokenAvailable("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); +} + +TEST_F(IdentityAccountTrackerTest, TwoAccounts) { + SetupPrimaryLogin(); + + NotifyTokenAvailable("alpha@example.com"); + ReturnOAuthUrlFetchSuccess("alpha@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "alpha@example.com"), + TrackingEvent(SIGN_IN, "alpha@example.com"))); + + NotifyTokenAvailable("beta@example.com"); + ReturnOAuthUrlFetchSuccess("beta@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "beta@example.com"), + TrackingEvent(SIGN_IN, "beta@example.com"))); + + NotifyTokenRevoked("alpha@example.com"); + EXPECT_TRUE( + observer()->CheckEvents(TrackingEvent(SIGN_OUT, "alpha@example.com"))); + + NotifyTokenRevoked("beta@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(SIGN_OUT, "beta@example.com"))); +} + +TEST_F(IdentityAccountTrackerTest, AvailableTokenFetchFailAvailable) { + SetupPrimaryLogin(); + + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchFailure("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); + + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com"), + TrackingEvent(SIGN_IN, "user@example.com"))); +} + +TEST_F(IdentityAccountTrackerTest, MultiSignOutSignIn) { + SetupPrimaryLogin(); + + NotifyTokenAvailable("alpha@example.com"); + ReturnOAuthUrlFetchSuccess("alpha@example.com"); + NotifyTokenAvailable("beta@example.com"); + ReturnOAuthUrlFetchSuccess("beta@example.com"); + + observer()->SortEventsByUser(); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "alpha@example.com"), + TrackingEvent(SIGN_IN, "alpha@example.com"), + TrackingEvent(ADDED, "beta@example.com"), + TrackingEvent(SIGN_IN, "beta@example.com"))); + + NotifyLogout(); + observer()->SortEventsByUser(); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(SIGN_OUT, "alpha@example.com"), + TrackingEvent(REMOVED, "alpha@example.com"), + TrackingEvent(SIGN_OUT, "beta@example.com"), + TrackingEvent(REMOVED, "beta@example.com"), + TrackingEvent(SIGN_OUT, kPrimaryAccountKey), + TrackingEvent(REMOVED, kPrimaryAccountKey))); + + // No events fire at all while profile is signed out. + NotifyTokenRevoked("alpha@example.com"); + NotifyTokenAvailable("gamma@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); + + // Signing the profile in again will resume tracking all accounts. + NotifyLogin(kPrimaryAccountKey); + NotifyTokenAvailable(kPrimaryAccountKey); + ReturnOAuthUrlFetchSuccess("beta@example.com"); + ReturnOAuthUrlFetchSuccess("gamma@example.com"); + ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey); + observer()->SortEventsByUser(); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "beta@example.com"), + TrackingEvent(SIGN_IN, "beta@example.com"), + TrackingEvent(ADDED, "gamma@example.com"), + TrackingEvent(SIGN_IN, "gamma@example.com"), + TrackingEvent(ADDED, kPrimaryAccountKey), + TrackingEvent(SIGN_IN, kPrimaryAccountKey))); + + // Revoking the primary token does not affect other accounts. + NotifyTokenRevoked(kPrimaryAccountKey); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(SIGN_OUT, kPrimaryAccountKey))); + + NotifyTokenAvailable(kPrimaryAccountKey); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(SIGN_IN, kPrimaryAccountKey))); +} + +// Primary/non-primary interactions + +TEST_F(IdentityAccountTrackerTest, MultiNoEventsBeforeLogin) { + NotifyTokenAvailable(kPrimaryAccountKey); + NotifyTokenAvailable("user@example.com"); + NotifyTokenRevoked("user@example.com"); + NotifyTokenRevoked(kPrimaryAccountKey); + NotifyLogout(); + EXPECT_TRUE(observer()->CheckEvents()); +} + +TEST_F(IdentityAccountTrackerTest, MultiLogoutRemovesAllAccounts) { + NotifyLogin(kPrimaryAccountKey); + NotifyTokenAvailable(kPrimaryAccountKey); + ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey); + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + observer()->Clear(); + + NotifyLogout(); + observer()->SortEventsByUser(); + EXPECT_TRUE( + observer()->CheckEvents(TrackingEvent(SIGN_OUT, kPrimaryAccountKey), + TrackingEvent(REMOVED, kPrimaryAccountKey), + TrackingEvent(SIGN_OUT, "user@example.com"), + TrackingEvent(REMOVED, "user@example.com"))); +} + +TEST_F(IdentityAccountTrackerTest, MultiRevokePrimaryDoesNotRemoveAllAccounts) { + NotifyLogin(kPrimaryAccountKey); + NotifyTokenAvailable(kPrimaryAccountKey); + ReturnOAuthUrlFetchSuccess(kPrimaryAccountKey); + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + observer()->Clear(); + + NotifyTokenRevoked(kPrimaryAccountKey); + observer()->SortEventsByUser(); + EXPECT_TRUE( + observer()->CheckEvents(TrackingEvent(SIGN_OUT, kPrimaryAccountKey))); +} + +TEST_F(IdentityAccountTrackerTest, GetAccountsPrimary) { + SetupPrimaryLogin(); + + std::vector ids = account_tracker()->GetAccounts(); + EXPECT_EQ(1ul, ids.size()); + EXPECT_EQ(kPrimaryAccountKey, ids[0].account_key); + EXPECT_EQ(AccountKeyToObfuscatedId(kPrimaryAccountKey), ids[0].gaia); +} + +TEST_F(IdentityAccountTrackerTest, GetAccountsSignedOut) { + std::vector ids = account_tracker()->GetAccounts(); + EXPECT_EQ(0ul, ids.size()); +} + +TEST_F(IdentityAccountTrackerTest, GetAccountsOnlyReturnAccountsWithTokens) { + SetupPrimaryLogin(); + + NotifyTokenAvailable("alpha@example.com"); + NotifyTokenAvailable("beta@example.com"); + ReturnOAuthUrlFetchSuccess("beta@example.com"); + + std::vector ids = account_tracker()->GetAccounts(); + EXPECT_EQ(2ul, ids.size()); + EXPECT_EQ(kPrimaryAccountKey, ids[0].account_key); + EXPECT_EQ(AccountKeyToObfuscatedId(kPrimaryAccountKey), ids[0].gaia); + EXPECT_EQ("beta@example.com", ids[1].account_key); + EXPECT_EQ(AccountKeyToObfuscatedId("beta@example.com"), ids[1].gaia); +} + +TEST_F(IdentityAccountTrackerTest, GetAccountsSortOrder) { + SetupPrimaryLogin(); + + NotifyTokenAvailable("zeta@example.com"); + ReturnOAuthUrlFetchSuccess("zeta@example.com"); + NotifyTokenAvailable("alpha@example.com"); + ReturnOAuthUrlFetchSuccess("alpha@example.com"); + + // The primary account will be first in the vector. Remaining accounts + // will be sorted by gaia ID. + std::vector ids = account_tracker()->GetAccounts(); + EXPECT_EQ(3ul, ids.size()); + EXPECT_EQ(kPrimaryAccountKey, ids[0].account_key); + EXPECT_EQ(AccountKeyToObfuscatedId(kPrimaryAccountKey), ids[0].gaia); + EXPECT_EQ("alpha@example.com", ids[1].account_key); + EXPECT_EQ(AccountKeyToObfuscatedId("alpha@example.com"), ids[1].gaia); + EXPECT_EQ("zeta@example.com", ids[2].account_key); + EXPECT_EQ(AccountKeyToObfuscatedId("zeta@example.com"), ids[2].gaia); +} + +TEST_F(IdentityAccountTrackerTest, + GetAccountsReturnNothingWhenPrimarySignedOut) { + SetupPrimaryLogin(); + + NotifyTokenAvailable("zeta@example.com"); + ReturnOAuthUrlFetchSuccess("zeta@example.com"); + NotifyTokenAvailable("alpha@example.com"); + ReturnOAuthUrlFetchSuccess("alpha@example.com"); + + NotifyTokenRevoked(kPrimaryAccountKey); + + std::vector ids = account_tracker()->GetAccounts(); + EXPECT_EQ(0ul, ids.size()); +} + +TEST_F(IdentityAccountTrackerTest, FindAccountIdsByGaiaIdPrimary) { + SetupPrimaryLogin(); + + AccountIds ids = account_tracker()->FindAccountIdsByGaiaId( + AccountKeyToObfuscatedId(kPrimaryAccountKey)); + EXPECT_EQ(kPrimaryAccountKey, ids.account_key); + EXPECT_EQ(kPrimaryAccountKey, ids.email); + EXPECT_EQ(AccountKeyToObfuscatedId(kPrimaryAccountKey), ids.gaia); +} + +TEST_F(IdentityAccountTrackerTest, FindAccountIdsByGaiaIdNotFound) { + SetupPrimaryLogin(); + + AccountIds ids = account_tracker()->FindAccountIdsByGaiaId( + AccountKeyToObfuscatedId("notfound@example.com")); + EXPECT_TRUE(ids.account_key.empty()); + EXPECT_TRUE(ids.email.empty()); + EXPECT_TRUE(ids.gaia.empty()); +} + +TEST_F(IdentityAccountTrackerTest, + FindAccountIdsByGaiaIdReturnEmptyWhenPrimarySignedOut) { + SetupPrimaryLogin(); + + NotifyTokenAvailable("zeta@example.com"); + ReturnOAuthUrlFetchSuccess("zeta@example.com"); + NotifyTokenAvailable("alpha@example.com"); + ReturnOAuthUrlFetchSuccess("alpha@example.com"); + + NotifyTokenRevoked(kPrimaryAccountKey); + + AccountIds ids = + account_tracker()->FindAccountIdsByGaiaId(kPrimaryAccountKey); + EXPECT_TRUE(ids.account_key.empty()); + EXPECT_TRUE(ids.email.empty()); + EXPECT_TRUE(ids.gaia.empty()); + + ids = account_tracker()->FindAccountIdsByGaiaId("alpha@example.com"); + EXPECT_TRUE(ids.account_key.empty()); + EXPECT_TRUE(ids.email.empty()); + EXPECT_TRUE(ids.gaia.empty()); +} + +} // namespace gaia diff --git a/google_apis/gaia/fake_oauth2_token_service.cc b/google_apis/gaia/fake_oauth2_token_service.cc index f23f349726747f..0b01404bd8735d 100644 --- a/google_apis/gaia/fake_oauth2_token_service.cc +++ b/google_apis/gaia/fake_oauth2_token_service.cc @@ -4,11 +4,22 @@ #include "google_apis/gaia/fake_oauth2_token_service.h" -FakeOAuth2TokenService::FakeOAuth2TokenService() : request_context_(NULL) {} +FakeOAuth2TokenService::PendingRequest::PendingRequest() { +} + +FakeOAuth2TokenService::PendingRequest::~PendingRequest() { +} + +FakeOAuth2TokenService::FakeOAuth2TokenService() : request_context_(NULL) { +} FakeOAuth2TokenService::~FakeOAuth2TokenService() { } +std::vector FakeOAuth2TokenService::GetAccounts() { + return std::vector(account_ids_.begin(), account_ids_.end()); +} + void FakeOAuth2TokenService::FetchOAuth2Token( RequestImpl* request, const std::string& account_id, @@ -16,6 +27,13 @@ void FakeOAuth2TokenService::FetchOAuth2Token( const std::string& client_id, const std::string& client_secret, const ScopeSet& scopes) { + PendingRequest pending_request; + pending_request.account_id = account_id; + pending_request.client_id = client_id; + pending_request.client_secret = client_secret; + pending_request.scopes = scopes; + pending_request.request = request->AsWeakPtr(); + pending_requests_.push_back(pending_request); } void FakeOAuth2TokenService::InvalidateOAuth2Token( @@ -36,8 +54,30 @@ bool FakeOAuth2TokenService::RefreshTokenIsAvailable( void FakeOAuth2TokenService::AddAccount(const std::string& account_id) { account_ids_.insert(account_id); + FireRefreshTokenAvailable(account_id); } +void FakeOAuth2TokenService::RemoveAccount(const std::string& account_id) { + account_ids_.erase(account_id); + FireRefreshTokenRevoked(account_id); +} + +void FakeOAuth2TokenService::IssueAllTokensForAccount( + const std::string& account_id, + const std::string& access_token, + const base::Time& expiration) { + + // Walk the requests and notify the callbacks. + for (std::vector::iterator it = pending_requests_.begin(); + it != pending_requests_.end(); ++it) { + if (it->request && (account_id == it->account_id)) { + it->request->InformConsumer( + GoogleServiceAuthError::AuthErrorNone(), access_token, expiration); + } + } +} + + OAuth2AccessTokenFetcher* FakeOAuth2TokenService::CreateAccessTokenFetcher( const std::string& account_id, net::URLRequestContextGetter* getter, diff --git a/google_apis/gaia/fake_oauth2_token_service.h b/google_apis/gaia/fake_oauth2_token_service.h index 8c267e5b99a514..3bdb67500a9673 100644 --- a/google_apis/gaia/fake_oauth2_token_service.h +++ b/google_apis/gaia/fake_oauth2_token_service.h @@ -9,6 +9,7 @@ #include #include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" #include "google_apis/gaia/oauth2_token_service.h" namespace net { @@ -21,7 +22,15 @@ class FakeOAuth2TokenService : public OAuth2TokenService { FakeOAuth2TokenService(); virtual ~FakeOAuth2TokenService(); + virtual std::vector GetAccounts() OVERRIDE; + void AddAccount(const std::string& account_id); + void RemoveAccount(const std::string& account_id); + + // Helper routines to issue tokens for pending requests. + void IssueAllTokensForAccount(const std::string& account_id, + const std::string& access_token, + const base::Time& expiration); void set_request_context(net::URLRequestContextGetter* request_context) { request_context_ = request_context; @@ -45,6 +54,17 @@ class FakeOAuth2TokenService : public OAuth2TokenService { OVERRIDE; private: + struct PendingRequest { + PendingRequest(); + ~PendingRequest(); + + std::string account_id; + std::string client_id; + std::string client_secret; + ScopeSet scopes; + base::WeakPtr request; + }; + // OAuth2TokenService overrides. virtual net::URLRequestContextGetter* GetRequestContext() OVERRIDE; @@ -54,6 +74,8 @@ class FakeOAuth2TokenService : public OAuth2TokenService { OAuth2AccessTokenConsumer* consumer) OVERRIDE; std::set account_ids_; + std::vector pending_requests_; + net::URLRequestContextGetter* request_context_; // weak DISALLOW_COPY_AND_ASSIGN(FakeOAuth2TokenService); diff --git a/google_apis/google_apis.gyp b/google_apis/google_apis.gyp index a3dbb03045c9a8..77dfce9c5d67cf 100644 --- a/google_apis/google_apis.gyp +++ b/google_apis/google_apis.gyp @@ -95,6 +95,8 @@ 'drive/task_util.h', 'drive/time_util.cc', 'drive/time_util.h', + 'gaia/account_tracker.cc', + 'gaia/account_tracker.h', 'gaia/gaia_auth_consumer.cc', 'gaia/gaia_auth_consumer.h', 'gaia/gaia_auth_fetcher.cc', @@ -168,6 +170,7 @@ 'drive/request_sender_unittest.cc', 'drive/request_util_unittest.cc', 'drive/time_util_unittest.cc', + 'gaia/account_tracker_unittest.cc', 'gaia/gaia_auth_fetcher_unittest.cc', 'gaia/gaia_auth_util_unittest.cc', 'gaia/gaia_oauth_client_unittest.cc',