summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcourage@chromium.org <courage@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-22 02:29:23 +0000
committercourage@chromium.org <courage@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-22 02:29:23 +0000
commitd2db51aa31ca57ce620723f5b6758f09a5ae593b (patch)
treecf76733442e6d2aafc85ddb8d21f03da583de413
parentd8b42e288d8fc5994163eed9c77ba87a5c16e489 (diff)
downloadchromium_src-d2db51aa31ca57ce620723f5b6758f09a5ae593b.zip
chromium_src-d2db51aa31ca57ce620723f5b6758f09a5ae593b.tar.gz
chromium_src-d2db51aa31ca57ce620723f5b6758f09a5ae593b.tar.bz2
Identity API: Add AccountTracker and attach it to identity.onSignInChanged
The AccountTracker class fetches gaia IDs for all accounts signed into Chrome, and calls its observers any time an account is added, removed, or changes sign-in state. The IdentityAPI class observes the AccountTracker and routes the events to listening apps and extensions. BUG=305830 Review URL: https://codereview.chromium.org/30503002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@230017 0039d316-1c4b-4281-b951-d872f2087c98
-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
-rw-r--r--chrome/browser/signin/fake_profile_oauth2_token_service.cc11
-rw-r--r--chrome/browser/signin/fake_profile_oauth2_token_service.h5
-rw-r--r--chrome/chrome_browser_extensions.gypi2
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--google_apis/gaia/gaia_oauth_client.cc41
-rw-r--r--google_apis/gaia/gaia_oauth_client.h10
-rw-r--r--google_apis/gaia/gaia_oauth_client_unittest.cc16
12 files changed, 993 insertions, 48 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 <>
diff --git a/chrome/browser/signin/fake_profile_oauth2_token_service.cc b/chrome/browser/signin/fake_profile_oauth2_token_service.cc
index 6c864b1..6f783bf 100644
--- a/chrome/browser/signin/fake_profile_oauth2_token_service.cc
+++ b/chrome/browser/signin/fake_profile_oauth2_token_service.cc
@@ -31,11 +31,17 @@ bool FakeProfileOAuth2TokenService::RefreshTokenIsAvailable(
void FakeProfileOAuth2TokenService::IssueRefreshToken(
const std::string& token) {
+ IssueRefreshTokenForUser("account_id", token);
+}
+
+void FakeProfileOAuth2TokenService::IssueRefreshTokenForUser(
+ const std::string& account_id,
+ const std::string& token) {
refresh_token_ = token;
if (refresh_token_.empty())
- FireRefreshTokenRevoked("account_id");
+ FireRefreshTokenRevoked(account_id);
else
- FireRefreshTokenAvailable("account_id");
+ FireRefreshTokenAvailable(account_id);
// TODO(atwilson): Maybe we should also call FireRefreshTokensLoaded() here?
}
@@ -79,6 +85,7 @@ void FakeProfileOAuth2TokenService::CompleteRequests(
const base::Time& expiration) {
std::vector<FakeProfileOAuth2TokenService::PendingRequest> requests =
GetPendingRequests();
+
// Walk the requests and notify the callbacks.
for (std::vector<PendingRequest>::iterator it = pending_requests_.begin();
it != pending_requests_.end(); ++it) {
diff --git a/chrome/browser/signin/fake_profile_oauth2_token_service.h b/chrome/browser/signin/fake_profile_oauth2_token_service.h
index e8e7dc8..d7e4923 100644
--- a/chrome/browser/signin/fake_profile_oauth2_token_service.h
+++ b/chrome/browser/signin/fake_profile_oauth2_token_service.h
@@ -71,6 +71,11 @@ class FakeProfileOAuth2TokenService
// OnRefreshTokenRevoked().
void IssueRefreshToken(const std::string& token);
+ // TODO(fgorski,rogerta): Merge with UpdateCredentials when this class fully
+ // supports multiple accounts.
+ void IssueRefreshTokenForUser(const std::string& account_id,
+ const std::string& token);
+
// Gets a list of active requests (can be used by tests to validate that the
// correct request has been issued).
std::vector<PendingRequest> GetPendingRequests();
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 5fa7459..7027970 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -276,6 +276,8 @@
'browser/extensions/api/history/history_api.cc',
'browser/extensions/api/i18n/i18n_api.cc',
'browser/extensions/api/i18n/i18n_api.h',
+ 'browser/extensions/api/identity/account_tracker.cc',
+ 'browser/extensions/api/identity/account_tracker.h',
'browser/extensions/api/identity/experimental_identity_api.cc',
'browser/extensions/api/identity/experimental_identity_api.h',
'browser/extensions/api/identity/experimental_web_auth_flow.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index cfc2f3c..4825c55 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -809,6 +809,7 @@
'browser/extensions/api/extension_action/extension_action_prefs_unittest.cc',
'browser/extensions/api/extension_action/extension_browser_actions_api_unittest.cc',
'browser/extensions/api/file_system/file_system_api_unittest.cc',
+ 'browser/extensions/api/identity/account_tracker_unittest.cc',
'browser/extensions/api/identity/experimental_web_auth_flow_unittest.cc',
'browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc',
'browser/extensions/api/identity/identity_event_router_unittest.cc',
diff --git a/google_apis/gaia/gaia_oauth_client.cc b/google_apis/gaia/gaia_oauth_client.cc
index 86064d9..dc83dbe 100644
--- a/google_apis/gaia/gaia_oauth_client.cc
+++ b/google_apis/gaia/gaia_oauth_client.cc
@@ -52,6 +52,9 @@ class GaiaOAuthClient::Core
void GetUserEmail(const std::string& oauth_access_token,
int max_retries,
Delegate* delegate);
+ void GetUserId(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate);
void GetTokenInfo(const std::string& oauth_access_token,
int max_retries,
Delegate* delegate);
@@ -67,11 +70,15 @@ class GaiaOAuthClient::Core
TOKENS_FROM_AUTH_CODE,
REFRESH_TOKEN,
TOKEN_INFO,
- USER_INFO,
+ USER_EMAIL,
+ USER_ID,
};
virtual ~Core() {}
+ void GetUserInfo(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate);
void MakeGaiaRequest(const GURL& url,
const std::string& post_body,
int max_retries,
@@ -136,7 +143,22 @@ void GaiaOAuthClient::Core::GetUserEmail(const std::string& oauth_access_token,
Delegate* delegate) {
DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
DCHECK(!request_.get());
- request_type_ = USER_INFO;
+ request_type_ = USER_EMAIL;
+ GetUserInfo(oauth_access_token, max_retries, delegate);
+}
+
+void GaiaOAuthClient::Core::GetUserId(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate) {
+ DCHECK_EQ(request_type_, NO_PENDING_REQUEST);
+ DCHECK(!request_.get());
+ request_type_ = USER_ID;
+ GetUserInfo(oauth_access_token, max_retries, delegate);
+}
+
+void GaiaOAuthClient::Core::GetUserInfo(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate) {
delegate_ = delegate;
num_retries_ = 0;
request_.reset(net::URLFetcher::Create(
@@ -250,13 +272,20 @@ void GaiaOAuthClient::Core::HandleResponse(
request_type_ = NO_PENDING_REQUEST;
switch (type) {
- case USER_INFO: {
+ case USER_EMAIL: {
std::string email;
response_dict->GetString("email", &email);
delegate_->OnGetUserEmailResponse(email);
break;
}
+ case USER_ID: {
+ std::string id;
+ response_dict->GetString("id", &id);
+ delegate_->OnGetUserIdResponse(id);
+ break;
+ }
+
case TOKEN_INFO: {
delegate_->OnGetTokenInfoResponse(response_dict.Pass());
break;
@@ -328,6 +357,12 @@ void GaiaOAuthClient::GetUserEmail(const std::string& access_token,
return core_->GetUserEmail(access_token, max_retries, delegate);
}
+void GaiaOAuthClient::GetUserId(const std::string& access_token,
+ int max_retries,
+ Delegate* delegate) {
+ return core_->GetUserId(access_token, max_retries, delegate);
+}
+
void GaiaOAuthClient::GetTokenInfo(const std::string& access_token,
int max_retries,
Delegate* delegate) {
diff --git a/google_apis/gaia/gaia_oauth_client.h b/google_apis/gaia/gaia_oauth_client.h
index 94d3313..14a26a6 100644
--- a/google_apis/gaia/gaia_oauth_client.h
+++ b/google_apis/gaia/gaia_oauth_client.h
@@ -45,6 +45,8 @@ class GaiaOAuthClient {
int expires_in_seconds) {}
// Invoked on a successful response to the GetUserInfo request.
virtual void OnGetUserEmailResponse(const std::string& user_email) {}
+ // Invoked on a successful response to the GetUserId request.
+ virtual void OnGetUserIdResponse(const std::string& user_id) {}
// Invoked on a successful response to the GetTokenInfo request.
virtual void OnGetTokenInfoResponse(
scoped_ptr<DictionaryValue> token_info) {}
@@ -94,6 +96,14 @@ class GaiaOAuthClient {
int max_retries,
Delegate* delegate);
+ // Call the userinfo API, returning the user gaia ID associated
+ // with the given access token. The provided access token must have
+ // https://www.googleapis.com/auth/userinfo as one of its scopes.
+ // See |max_retries| docs above.
+ void GetUserId(const std::string& oauth_access_token,
+ int max_retries,
+ Delegate* delegate);
+
// Call the tokeninfo API, returning a dictionary of response values. The
// provided access token may have any scope, and basic results will be
// returned: issued_to, audience, scope, expires_in, access_type. In
diff --git a/google_apis/gaia/gaia_oauth_client_unittest.cc b/google_apis/gaia/gaia_oauth_client_unittest.cc
index cdeb483..32e7c66 100644
--- a/google_apis/gaia/gaia_oauth_client_unittest.cc
+++ b/google_apis/gaia/gaia_oauth_client_unittest.cc
@@ -130,6 +130,7 @@ const std::string kTestAccessToken = "1/fFAGRNJru1FTz70BzhT3Zg";
const std::string kTestRefreshToken =
"1/6BMfW9j53gdGImsixUH6kU5RsR4zwI9lUVX-tqf8JXQ";
const std::string kTestUserEmail = "a_user@gmail.com";
+const std::string kTestUserId = "8675309";
const int kTestExpiresIn = 3920;
const std::string kDummyGetTokensResult =
@@ -144,6 +145,9 @@ const std::string kDummyRefreshTokenResult =
const std::string kDummyUserInfoResult =
"{\"email\":\"" + kTestUserEmail + "\"}";
+const std::string kDummyUserIdResult =
+ "{\"id\":\"" + kTestUserId + "\"}";
+
const std::string kDummyTokenInfoResult =
"{\"issued_to\": \"1234567890.apps.googleusercontent.com\","
"\"audience\": \"1234567890.apps.googleusercontent.com\","
@@ -186,6 +190,7 @@ class MockGaiaOAuthClientDelegate : public gaia::GaiaOAuthClient::Delegate {
MOCK_METHOD2(OnRefreshTokenResponse, void(const std::string& access_token,
int expires_in_seconds));
MOCK_METHOD1(OnGetUserEmailResponse, void(const std::string& user_email));
+ MOCK_METHOD1(OnGetUserIdResponse, void(const std::string& user_id));
MOCK_METHOD0(OnOAuthError, void());
MOCK_METHOD1(OnNetworkError, void(int response_code));
@@ -311,6 +316,17 @@ TEST_F(GaiaOAuthClientTest, GetUserEmail) {
auth.GetUserEmail("access_token", 1, &delegate);
}
+TEST_F(GaiaOAuthClientTest, GetUserId) {
+ MockGaiaOAuthClientDelegate delegate;
+ EXPECT_CALL(delegate, OnGetUserIdResponse(kTestUserId)).Times(1);
+
+ MockOAuthFetcherFactory factory;
+ factory.set_results(kDummyUserIdResult);
+
+ GaiaOAuthClient auth(GetRequestContext());
+ auth.GetUserId("access_token", 1, &delegate);
+}
+
TEST_F(GaiaOAuthClientTest, GetTokenInfo) {
const DictionaryValue* captured_result;