summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions/api
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/extensions/api')
-rw-r--r--chrome/browser/extensions/api/identity/account_tracker.cc244
-rw-r--r--chrome/browser/extensions/api/identity/account_tracker.h140
-rw-r--r--chrome/browser/extensions/api/identity/account_tracker_unittest.cc504
-rw-r--r--chrome/browser/extensions/api/identity/identity_api.cc46
-rw-r--r--chrome/browser/extensions/api/identity/identity_api.h21
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 <>