diff options
Diffstat (limited to 'chrome/browser/extensions/api')
5 files changed, 912 insertions, 43 deletions
diff --git a/chrome/browser/extensions/api/identity/account_tracker.cc b/chrome/browser/extensions/api/identity/account_tracker.cc new file mode 100644 index 0000000..03db40a --- /dev/null +++ b/chrome/browser/extensions/api/identity/account_tracker.cc @@ -0,0 +1,244 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/identity/account_tracker.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/signin/profile_oauth2_token_service.h" +#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" +#include "chrome/browser/signin/signin_manager_base.h" +#include "content/public/browser/notification_details.h" + +namespace extensions { + +AccountTracker::AccountTracker(Profile* profile) : profile_(profile) { + registrar_.Add(this, + chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, + content::Source<Profile>(profile_)); + + ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)->AddObserver(this); + SigninGlobalError::GetForProfile(profile_)->AddProvider(this); +} + +AccountTracker::~AccountTracker() {} + +void AccountTracker::ReportAuthError(const std::string& account_id, + const GoogleServiceAuthError& error) { + account_errors_.insert(make_pair(account_id, error)); + SigninGlobalError::GetForProfile(profile_)->AuthStatusChanged(); + UpdateSignInState(account_id, false); +} + +void AccountTracker::Shutdown() { + STLDeleteValues(&user_info_requests_); + SigninGlobalError::GetForProfile(profile_)->RemoveProvider(this); + ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)-> + RemoveObserver(this); +} + +void AccountTracker::AddObserver(Observer* observer) { + observer_list_.AddObserver(observer); +} + +void AccountTracker::RemoveObserver(Observer* observer) { + observer_list_.RemoveObserver(observer); +} + +void AccountTracker::OnRefreshTokenAvailable(const std::string& account_id) { + DVLOG(1) << "AVAILABLE " << account_id; + account_errors_.erase(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::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case chrome::NOTIFICATION_GOOGLE_SIGNED_OUT: + StopTrackingAccount(content::Details<GoogleServiceSignoutDetails>( + details)->username); + break; + default: + NOTREACHED(); + } +} + +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) { + 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); + } + + account_errors_.erase(account_key); + + if (ContainsKey(user_info_requests_, account_key)) + DeleteFetcher(user_info_requests_[account_key]); +} + +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(profile_, 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); +} + +std::string AccountTracker::GetAccountId() const { + if (account_errors_.size() == 0) + return std::string(); + else + return account_errors_.begin()->first; +} + +GoogleServiceAuthError AccountTracker::GetAuthStatus() const { + if (account_errors_.size() == 0) + return GoogleServiceAuthError::AuthErrorNone(); + else + return account_errors_.begin()->second; +} + +void AccountTracker::DeleteFetcher(AccountIdFetcher* fetcher) { + 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(Profile* profile, + AccountTracker* tracker, + const std::string& account_key) + : profile_(profile), + tracker_(tracker), + account_key_(account_key) {} + +AccountIdFetcher::~AccountIdFetcher() {} + +void AccountIdFetcher::Start() { + ProfileOAuth2TokenService* service = + ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); + login_token_request_ = 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( + g_browser_process->system_request_context())); + + 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.error_message(); + 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 extensions diff --git a/chrome/browser/extensions/api/identity/account_tracker.h b/chrome/browser/extensions/api/identity/account_tracker.h new file mode 100644 index 0000000..de9b6d8 --- /dev/null +++ b/chrome/browser/extensions/api/identity/account_tracker.h @@ -0,0 +1,140 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_EXTENSIONS_API_IDENTITY_ACCOUNT_TRACKER_H_ +#define CHROME_BROWSER_EXTENSIONS_API_IDENTITY_ACCOUNT_TRACKER_H_ + +#include <map> +#include <string> + +#include "base/observer_list.h" +#include "chrome/browser/signin/signin_global_error.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/notification_source.h" +#include "google_apis/gaia/gaia_oauth_client.h" +#include "google_apis/gaia/oauth2_token_service.h" + +class GoogleServiceAuthError; +class Profile; + +namespace extensions { + +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. +class AccountTracker : public OAuth2TokenService::Observer, + public content::NotificationObserver, + public SigninGlobalError::AuthStatusProvider { + public: + explicit AccountTracker(Profile* profile); + 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 ReportAuthError(const std::string& account_key, + const GoogleServiceAuthError& error); + + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + // OAuth2TokenService::Observer implementation. + virtual void OnRefreshTokenAvailable(const std::string& account_key) OVERRIDE; + virtual void OnRefreshTokenRevoked(const std::string& account_key) OVERRIDE; + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + void OnUserInfoFetchSuccess(AccountIdFetcher* fetcher, + const std::string& gaia_id); + void OnUserInfoFetchFailure(AccountIdFetcher* fetcher); + + // AuthStatusProvider implementation. + virtual std::string GetAccountId() const OVERRIDE; + virtual GoogleServiceAuthError GetAuthStatus() const OVERRIDE; + + 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 StartFetchingUserInfo(const std::string& account_key); + void DeleteFetcher(AccountIdFetcher* fetcher); + + Profile* profile_; + std::map<std::string, AccountIdFetcher*> user_info_requests_; + std::map<std::string, AccountState> accounts_; + std::map<std::string, GoogleServiceAuthError> account_errors_; + ObserverList<Observer> observer_list_; + content::NotificationRegistrar registrar_; +}; + +class AccountIdFetcher : public OAuth2TokenService::Consumer, + public gaia::GaiaOAuthClient::Delegate { + public: + AccountIdFetcher(Profile* profile, + 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: + Profile* profile_; + AccountTracker* tracker_; + const std::string account_key_; + + scoped_ptr<OAuth2TokenService::Request> login_token_request_; + scoped_ptr<gaia::GaiaOAuthClient> gaia_oauth_client_; +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_API_IDENTITY_ACCOUNT_TRACKER_H_ diff --git a/chrome/browser/extensions/api/identity/account_tracker_unittest.cc b/chrome/browser/extensions/api/identity/account_tracker_unittest.cc new file mode 100644 index 0000000..2ac55ad --- /dev/null +++ b/chrome/browser/extensions/api/identity/account_tracker_unittest.cc @@ -0,0 +1,504 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/api/identity/account_tracker.h" + +#include <vector> + +#include "base/strings/stringprintf.h" +#include "chrome/browser/chrome_notification_types.h" +#include "chrome/browser/signin/fake_profile_oauth2_token_service.h" +#include "chrome/browser/signin/profile_oauth2_token_service_factory.h" +#include "chrome/browser/signin/signin_manager_base.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/browser/notification_service.h" +#include "content/public/test/test_browser_thread_bundle.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 kFakeGaiaId[] = "8675309"; + +enum TrackingEventType { + ADDED, + REMOVED, + SIGN_IN, + SIGN_OUT +}; + +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_(kFakeGaiaId) {} + + 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: + TrackingEventType type_; + std::string account_key_; + std::string gaia_id_; +}; + +std::string Str(const std::vector<TrackingEvent>& events) { + std::string str = "["; + bool needs_comma = false; + for (std::vector<TrackingEvent>::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 extensions { + +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); + + // 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<TrackingEvent>& events); + + std::vector<TrackingEvent> 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)); +} + +testing::AssertionResult AccountTrackerObserver::CheckEvents() { + std::vector<TrackingEvent> events; + return CheckEvents(events); +} + +testing::AssertionResult AccountTrackerObserver::CheckEvents( + const TrackingEvent& e1) { + std::vector<TrackingEvent> events; + events.push_back(e1); + return CheckEvents(events); +} + +testing::AssertionResult AccountTrackerObserver::CheckEvents( + const TrackingEvent& e1, + const TrackingEvent& e2) { + std::vector<TrackingEvent> 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<TrackingEvent> 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<TrackingEvent> 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 std::vector<TrackingEvent>& 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 { + TestingProfile::Builder builder; + builder.AddTestingFactory(ProfileOAuth2TokenServiceFactory::GetInstance(), + FakeProfileOAuth2TokenService::Build); + + test_profile_ = builder.Build(); + + fake_oauth2_token_service_ = static_cast<FakeProfileOAuth2TokenService*>( + ProfileOAuth2TokenServiceFactory::GetForProfile(test_profile_.get())); + + account_tracker_.reset(new AccountTracker(test_profile_.get())); + account_tracker_->AddObserver(&observer_); + } + + virtual void TearDown() OVERRIDE { + account_tracker_->RemoveObserver(&observer_); + account_tracker_->Shutdown(); + } + + Profile* profile() { + return test_profile_.get(); + } + + AccountTrackerObserver* observer() { + return &observer_; + } + + AccountTracker* account_tracker() { + return account_tracker_.get(); + } + + // Helpers to pass fake events to the tracker. + + void NotifyRemoveAccount(const std::string& username) { + GoogleServiceSigninSuccessDetails details(username, std::string()); + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_GOOGLE_SIGNED_OUT, + content::Source<Profile>(profile()), + content::Details<const GoogleServiceSigninSuccessDetails>(&details)); + } + + void NotifyTokenAvailable(const std::string& username) { + fake_oauth2_token_service_->IssueRefreshTokenForUser(username, + "refresh_token"); + } + + void NotifyTokenRevoked(const std::string& username) { + fake_oauth2_token_service_->IssueRefreshTokenForUser(username, + std::string()); + } + + // Helpers to fake access token and user info fetching + void IssueAccessToken() { + fake_oauth2_token_service_->IssueTokenForAllPendingRequests( + "access_token", base::Time::Max()); + } + + std::string GetValidTokenInfoResponse(const std::string email) { + return std::string("{ \"id\": \"") + kFakeGaiaId + "\" }"; + } + + 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); + + private: + scoped_ptr<TestingProfile> test_profile_; + net::TestURLFetcherFactory test_fetcher_factory_; + FakeProfileOAuth2TokenService* fake_oauth2_token_service_; + content::TestBrowserThreadBundle thread_bundle_; + + scoped_ptr<AccountTracker> 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(); + ReturnOAuthUrlFetchResults(gaia::GaiaOAuthClient::kUrlFetcherId, + net::HTTP_OK, + GetValidTokenInfoResponse(account_key)); +} + +void IdentityAccountTrackerTest::ReturnOAuthUrlFetchFailure( + const std::string& account_key) { + IssueAccessToken(); + ReturnOAuthUrlFetchResults( + gaia::GaiaOAuthClient::kUrlFetcherId, net::HTTP_BAD_REQUEST, ""); +} + +TEST_F(IdentityAccountTrackerTest, Available) { + NotifyTokenAvailable("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); + + ReturnOAuthUrlFetchSuccess("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "user@example.com", kFakeGaiaId))); +} + +TEST_F(IdentityAccountTrackerTest, Revoke) { + account_tracker()->OnRefreshTokenRevoked("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); +} + +TEST_F(IdentityAccountTrackerTest, Remove) { + NotifyRemoveAccount("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); +} + +TEST_F(IdentityAccountTrackerTest, AvailableRemoveFetchCancelAvailable) { + NotifyTokenAvailable("user@example.com"); + NotifyRemoveAccount("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); + + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "user@example.com", kFakeGaiaId))); +} + +TEST_F(IdentityAccountTrackerTest, AvailableRemoveAvailable) { + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + NotifyRemoveAccount("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_OUT, "user@example.com", kFakeGaiaId), + TrackingEvent(REMOVED, "user@example.com", kFakeGaiaId))); + + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "user@example.com", kFakeGaiaId))); +} + +TEST_F(IdentityAccountTrackerTest, AvailableRevokeAvailable) { + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + NotifyTokenRevoked("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_OUT, "user@example.com", kFakeGaiaId))); + + NotifyTokenAvailable("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(SIGN_IN, "user@example.com", kFakeGaiaId))); +} + +TEST_F(IdentityAccountTrackerTest, AvailableRevokeAvailableWithPendingFetch) { + 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", kFakeGaiaId), + TrackingEvent(SIGN_IN, "user@example.com", kFakeGaiaId))); +} + +TEST_F(IdentityAccountTrackerTest, AvailableRevokeRemove) { + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + NotifyTokenRevoked("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_OUT, "user@example.com", kFakeGaiaId))); + + NotifyRemoveAccount("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(REMOVED, "user@example.com", kFakeGaiaId))); +} + +TEST_F(IdentityAccountTrackerTest, AvailableRevokeRevoke) { + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + NotifyTokenRevoked("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_OUT, "user@example.com", kFakeGaiaId))); + + NotifyTokenRevoked("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); +} + +TEST_F(IdentityAccountTrackerTest, AvailableAvailable) { + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "user@example.com", kFakeGaiaId))); + + NotifyTokenAvailable("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); +} + +TEST_F(IdentityAccountTrackerTest, TwoAccounts) { + NotifyTokenAvailable("alpha@example.com"); + ReturnOAuthUrlFetchSuccess("alpha@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "alpha@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "alpha@example.com", kFakeGaiaId))); + + NotifyTokenAvailable("beta@example.com"); + ReturnOAuthUrlFetchSuccess("beta@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "beta@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "beta@example.com", kFakeGaiaId))); + + NotifyRemoveAccount("alpha@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(SIGN_OUT, "alpha@example.com", kFakeGaiaId), + TrackingEvent(REMOVED, "alpha@example.com", kFakeGaiaId))); + + NotifyRemoveAccount("beta@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(SIGN_OUT, "beta@example.com", kFakeGaiaId), + TrackingEvent(REMOVED, "beta@example.com", kFakeGaiaId))); +} + +TEST_F(IdentityAccountTrackerTest, GlobalErrors) { + NotifyTokenAvailable("alpha@example.com"); + ReturnOAuthUrlFetchSuccess("alpha@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "alpha@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "alpha@example.com", kFakeGaiaId))); + NotifyTokenAvailable("beta@example.com"); + ReturnOAuthUrlFetchSuccess("beta@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "beta@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "beta@example.com", kFakeGaiaId))); + + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + account_tracker()->GetAuthStatus()); + + account_tracker()->ReportAuthError( + "beta@example.com", + GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED)); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(SIGN_OUT, "beta@example.com", kFakeGaiaId))); + EXPECT_EQ(GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED), + account_tracker()->GetAuthStatus()); + + account_tracker()->ReportAuthError( + "alpha@example.com", + GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED)); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(SIGN_OUT, "alpha@example.com", kFakeGaiaId))); + EXPECT_EQ(GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED), + account_tracker()->GetAuthStatus()); + + NotifyRemoveAccount("alpha@example.com"); + EXPECT_EQ(GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED), + account_tracker()->GetAuthStatus()); + + NotifyTokenAvailable("beta@example.com"); + EXPECT_EQ(GoogleServiceAuthError::AuthErrorNone(), + account_tracker()->GetAuthStatus()); +} + +TEST_F(IdentityAccountTrackerTest, AvailableTokenFetchFailAvailable) { + NotifyTokenAvailable("alpha@example.com"); + ReturnOAuthUrlFetchFailure("alpha@example.com"); + EXPECT_TRUE(observer()->CheckEvents()); + + NotifyTokenAvailable("user@example.com"); + ReturnOAuthUrlFetchSuccess("user@example.com"); + EXPECT_TRUE(observer()->CheckEvents( + TrackingEvent(ADDED, "user@example.com", kFakeGaiaId), + TrackingEvent(SIGN_IN, "user@example.com", kFakeGaiaId))); +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/identity/identity_api.cc b/chrome/browser/extensions/api/identity/identity_api.cc index 491b65e..485711e 100644 --- a/chrome/browser/extensions/api/identity/identity_api.cc +++ b/chrome/browser/extensions/api/identity/identity_api.cc @@ -655,19 +655,12 @@ const base::Time& IdentityTokenCacheValue::expiration_time() const { IdentityAPI::IdentityAPI(Profile* profile) : profile_(profile), - error_(GoogleServiceAuthError::NONE), - initialized_(false) { + account_tracker_(profile), + identity_event_router_(profile) { + account_tracker_.AddObserver(this); } -IdentityAPI::~IdentityAPI() { -} - -void IdentityAPI::Initialize() { - SigninGlobalError::GetForProfile(profile_)->AddProvider(this); - ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)->AddObserver(this); - - initialized_ = true; -} +IdentityAPI::~IdentityAPI() {} IdentityMintRequestQueue* IdentityAPI::mint_queue() { return &mint_queue_; @@ -715,19 +708,14 @@ const IdentityAPI::CachedTokens& IdentityAPI::GetAllCachedTokens() { } void IdentityAPI::ReportAuthError(const GoogleServiceAuthError& error) { - error_ = error; - SigninGlobalError::GetForProfile(profile_)->AuthStatusChanged(); + ProfileOAuth2TokenService* token_service = + ProfileOAuth2TokenServiceFactory::GetForProfile(profile_); + account_tracker_.ReportAuthError(token_service->GetPrimaryAccountId(), error); } void IdentityAPI::Shutdown() { - if (!initialized_) - return; - - SigninGlobalError::GetForProfile(profile_)->RemoveProvider(this); - ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)-> - RemoveObserver(this); - - initialized_ = false; + account_tracker_.RemoveObserver(this); + account_tracker_.Shutdown(); } static base::LazyInstance<ProfileKeyedAPIFactory<IdentityAPI> > @@ -738,24 +726,18 @@ ProfileKeyedAPIFactory<IdentityAPI>* IdentityAPI::GetFactoryInstance() { return &g_factory.Get(); } -std::string IdentityAPI::GetAccountId() const { - return ProfileOAuth2TokenServiceFactory::GetForProfile(profile_)-> - GetPrimaryAccountId(); -} +void IdentityAPI::OnAccountAdded(const AccountIds& ids) {} -GoogleServiceAuthError IdentityAPI::GetAuthStatus() const { - return error_; -} +void IdentityAPI::OnAccountRemoved(const AccountIds& ids) {} -void IdentityAPI::OnRefreshTokenAvailable(const std::string& account_id) { - error_ = GoogleServiceAuthError::AuthErrorNone(); +void IdentityAPI::OnAccountSignInChanged(const AccountIds& ids, + bool is_signed_in) { + identity_event_router_.DispatchSignInEvent(ids.gaia, ids.email, is_signed_in); } template <> void ProfileKeyedAPIFactory<IdentityAPI>::DeclareFactoryDependencies() { DependsOn(ExtensionSystemFactory::GetInstance()); - // Need dependency on ProfileOAuth2TokenServiceFactory because it owns - // the SigninGlobalError instance. DependsOn(ProfileOAuth2TokenServiceFactory::GetInstance()); } diff --git a/chrome/browser/extensions/api/identity/identity_api.h b/chrome/browser/extensions/api/identity/identity_api.h index e37c603..a9a7746 100644 --- a/chrome/browser/extensions/api/identity/identity_api.h +++ b/chrome/browser/extensions/api/identity/identity_api.h @@ -13,7 +13,9 @@ #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" +#include "chrome/browser/extensions/api/identity/account_tracker.h" #include "chrome/browser/extensions/api/identity/gaia_web_auth_flow.h" +#include "chrome/browser/extensions/api/identity/identity_event_router.h" #include "chrome/browser/extensions/api/identity/identity_mint_queue.h" #include "chrome/browser/extensions/api/identity/identity_signin_flow.h" #include "chrome/browser/extensions/api/identity/web_auth_flow.h" @@ -239,8 +241,7 @@ class IdentityTokenCacheValue { }; class IdentityAPI : public ProfileKeyedAPI, - public SigninGlobalError::AuthStatusProvider, - public OAuth2TokenService::Observer { + public AccountTracker::Observer { public: struct TokenCacheKey { TokenCacheKey(const std::string& extension_id, @@ -255,7 +256,6 @@ class IdentityAPI : public ProfileKeyedAPI, explicit IdentityAPI(Profile* profile); virtual ~IdentityAPI(); - void Initialize(); // Request serialization queue for getAuthToken. IdentityMintRequestQueue* mint_queue(); @@ -278,12 +278,11 @@ class IdentityAPI : public ProfileKeyedAPI, virtual void Shutdown() OVERRIDE; static ProfileKeyedAPIFactory<IdentityAPI>* GetFactoryInstance(); - // AuthStatusProvider implementation. - virtual std::string GetAccountId() const OVERRIDE; - virtual GoogleServiceAuthError GetAuthStatus() const OVERRIDE; - - // OAuth2TokenService::Observer implementation: - virtual void OnRefreshTokenAvailable(const std::string& account_id) OVERRIDE; + // 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: friend class ProfileKeyedAPIFactory<IdentityAPI>; @@ -295,10 +294,10 @@ class IdentityAPI : public ProfileKeyedAPI, static const bool kServiceIsNULLWhileTesting = true; Profile* profile_; - GoogleServiceAuthError error_; - bool initialized_; IdentityMintRequestQueue mint_queue_; CachedTokens token_cache_; + AccountTracker account_tracker_; + IdentityEventRouter identity_event_router_; }; template <> |