summaryrefslogtreecommitdiffstats
path: root/chrome/browser/net
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/net')
-rw-r--r--chrome/browser/net/gaia/token_service.cc464
-rw-r--r--chrome/browser/net/gaia/token_service.h281
-rw-r--r--chrome/browser/net/gaia/token_service_unittest.cc533
-rw-r--r--chrome/browser/net/gaia/token_service_unittest.h84
4 files changed, 1362 insertions, 0 deletions
diff --git a/chrome/browser/net/gaia/token_service.cc b/chrome/browser/net/gaia/token_service.cc
new file mode 100644
index 0000000..031719e
--- /dev/null
+++ b/chrome/browser/net/gaia/token_service.cc
@@ -0,0 +1,464 @@
+// Copyright (c) 2011 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/net/gaia/token_service.h"
+
+#include "base/command_line.h"
+#include "base/string_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/net/gaia/gaia_auth_fetcher.h"
+#include "chrome/common/net/gaia/gaia_constants.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_source.h"
+#include "net/url_request/url_request_context_getter.h"
+
+using content::BrowserThread;
+
+// Unfortunately kNumServices must be defined in the .h.
+// TODO(chron): Sync doesn't use the TalkToken anymore so we can stop
+// requesting it.
+const char* TokenService::kServices[] = {
+ GaiaConstants::kGaiaService,
+ GaiaConstants::kSyncService,
+ GaiaConstants::kTalkService,
+ GaiaConstants::kDeviceManagementService,
+ GaiaConstants::kLSOService,
+};
+
+const char* kUnusedServiceScope = "unused-service-scope";
+
+// Unfortunately kNumOAuthServices must be defined in the .h.
+// For OAuth, Chrome uses the OAuth2 service scope as the service name.
+const char* TokenService::kOAuthServices[] = {
+ GaiaConstants::kSyncServiceOAuth,
+};
+
+TokenService::TokenService()
+ : profile_(NULL),
+ token_loading_query_(0) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+}
+
+TokenService::~TokenService() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ ResetCredentialsInMemory();
+}
+
+void TokenService::Initialize(const char* const source,
+ Profile* profile) {
+
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (!source_.empty()) {
+ // Already initialized.
+ return;
+ }
+ DCHECK(!profile_);
+ profile_ = profile;
+ getter_ = profile->GetRequestContext();
+ // Since the user can create a bookmark in incognito, sync may be running.
+ // Thus we have to go for explicit access.
+ web_data_service_ = profile->GetWebDataService(Profile::EXPLICIT_ACCESS);
+ source_ = std::string(source);
+
+ CommandLine* cmd_line = CommandLine::ForCurrentProcess();
+ // Allow the token service to be cleared from the command line.
+ if (cmd_line->HasSwitch(switches::kClearTokenService))
+ EraseTokensFromDB();
+
+ // Allow a token to be injected from the command line.
+ if (cmd_line->HasSwitch(switches::kSetToken)) {
+ std::string value = cmd_line->GetSwitchValueASCII(switches::kSetToken);
+ int separator = value.find(':');
+ std::string service = value.substr(0, separator);
+ std::string token = value.substr(separator + 1);
+ token_map_[service] = token;
+ SaveAuthTokenToDB(service, token);
+ }
+
+ registrar_.Add(this,
+ chrome::NOTIFICATION_TOKEN_UPDATED,
+ content::Source<Profile>(profile));
+}
+
+void TokenService::ResetCredentialsInMemory() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+ // Terminate any running fetchers. Callbacks will not return.
+ for (int i = 0; i < kNumServices; ++i) {
+ fetchers_[i].reset();
+ }
+ for (int i = 0; i < kNumOAuthServices; ++i) {
+ oauth_fetchers_[i].reset();
+ }
+
+ // Cancel pending loads. Callbacks will not return.
+ if (token_loading_query_) {
+ web_data_service_->CancelRequest(token_loading_query_);
+ token_loading_query_ = 0;
+ }
+
+ token_map_.clear();
+ credentials_ = GaiaAuthConsumer::ClientLoginResult();
+ oauth_token_.clear();
+ oauth_secret_.clear();
+}
+
+void TokenService::UpdateCredentials(
+ const GaiaAuthConsumer::ClientLoginResult& credentials) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ credentials_ = credentials;
+
+ SaveAuthTokenToDB(GaiaConstants::kGaiaLsid, credentials.lsid);
+ SaveAuthTokenToDB(GaiaConstants::kGaiaSid, credentials.sid);
+
+ // Cancel any currently running requests.
+ for (int i = 0; i < kNumServices; i++) {
+ fetchers_[i].reset();
+ }
+}
+
+void TokenService::UpdateOAuthCredentials(
+ const std::string& oauth_token,
+ const std::string& oauth_secret) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ oauth_token_ = oauth_token;
+ oauth_secret_ = oauth_secret;
+
+ SaveAuthTokenToDB(GaiaConstants::kGaiaOAuthToken, oauth_token);
+ SaveAuthTokenToDB(GaiaConstants::kGaiaOAuthSecret, oauth_secret);
+
+ // Cancel any currently running requests.
+ for (int i = 0; i < kNumOAuthServices; i++) {
+ oauth_fetchers_[i].reset();
+ }
+}
+
+void TokenService::LoadTokensFromDB() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (web_data_service_.get())
+ token_loading_query_ = web_data_service_->GetAllTokens(this);
+}
+
+void TokenService::SaveAuthTokenToDB(const std::string& service,
+ const std::string& auth_token) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (web_data_service_.get())
+ web_data_service_->SetTokenForService(service, auth_token);
+}
+
+void TokenService::EraseTokensFromDB() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ if (web_data_service_.get())
+ web_data_service_->RemoveAllTokens();
+}
+
+// static
+int TokenService::GetServiceIndex(const std::string& service) {
+ for (int i = 0; i < kNumServices; ++i) {
+ if (kServices[i] == service)
+ return i;
+ }
+ return -1;
+}
+
+bool TokenService::AreCredentialsValid() const {
+ return !credentials_.lsid.empty() && !credentials_.sid.empty();
+}
+
+bool TokenService::HasLsid() const {
+ return !credentials_.lsid.empty();
+}
+
+const std::string& TokenService::GetLsid() const {
+ return credentials_.lsid;
+}
+
+bool TokenService::HasOAuthCredentials() const {
+ return !oauth_token_.empty() && !oauth_secret_.empty();
+}
+
+const std::string& TokenService::GetOAuthToken() const {
+ return oauth_token_;
+}
+
+const std::string& TokenService::GetOAuthSecret() const {
+ return oauth_secret_;
+}
+
+void TokenService::StartFetchingTokens() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(AreCredentialsValid());
+ for (int i = 0; i < kNumServices; i++) {
+ fetchers_[i].reset(new GaiaAuthFetcher(this, source_, getter_));
+ fetchers_[i]->StartIssueAuthToken(credentials_.sid,
+ credentials_.lsid,
+ kServices[i]);
+ }
+}
+
+void TokenService::StartFetchingOAuthTokens() {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(HasOAuthCredentials());
+ for (int i = 0; i < kNumOAuthServices; i++) {
+ oauth_fetchers_[i].reset(
+ new GaiaOAuthFetcher(this, getter_, profile_, kUnusedServiceScope));
+ oauth_fetchers_[i]->StartOAuthWrapBridge(oauth_token_,
+ oauth_secret_,
+ GaiaConstants::kGaiaOAuthDuration,
+ kOAuthServices[i]);
+ }
+}
+
+// Services dependent on a token will check if a token is available.
+// If it isn't, they'll go to sleep until they get a token event.
+bool TokenService::HasTokenForService(const char* service) const {
+ return token_map_.count(service) > 0;
+}
+
+const std::string& TokenService::GetTokenForService(
+ const char* const service) const {
+
+ if (token_map_.count(service) > 0) {
+ // Note map[key] is not const.
+ return (*token_map_.find(service)).second;
+ }
+ return EmptyString();
+}
+
+bool TokenService::HasOAuthLoginToken() const {
+ return HasTokenForService(GaiaConstants::kGaiaOAuth2LoginRefreshToken);
+}
+
+const std::string& TokenService::GetOAuth2LoginRefreshToken() const {
+ return GetTokenForService(GaiaConstants::kGaiaOAuth2LoginRefreshToken);
+}
+
+const std::string& TokenService::GetOAuth2LoginAccessToken() const {
+ return GetTokenForService(GaiaConstants::kGaiaOAuth2LoginAccessToken);
+}
+
+// Note that this can fire twice or more for any given service.
+// It can fire once from the DB read, and then once from the initial
+// fetcher. Future fetches can cause more notification firings.
+// The DB read will not however fire a notification if the fetcher
+// returned first. So it's always safe to use the latest notification.
+void TokenService::FireTokenAvailableNotification(
+ const std::string& service,
+ const std::string& auth_token) {
+
+ TokenAvailableDetails details(service, auth_token);
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_TOKEN_AVAILABLE,
+ content::Source<TokenService>(this),
+ content::Details<const TokenAvailableDetails>(&details));
+}
+
+void TokenService::FireTokenRequestFailedNotification(
+ const std::string& service,
+ const GoogleServiceAuthError& error) {
+
+ TokenRequestFailedDetails details(service, error);
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_TOKEN_REQUEST_FAILED,
+ content::Source<TokenService>(this),
+ content::Details<const TokenRequestFailedDetails>(&details));
+}
+
+void TokenService::IssueAuthTokenForTest(const std::string& service,
+ const std::string& auth_token) {
+ token_map_[service] = auth_token;
+ FireTokenAvailableNotification(service, auth_token);
+}
+
+void TokenService::OnIssueAuthTokenSuccess(const std::string& service,
+ const std::string& auth_token) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ VLOG(1) << "Got an authorization token for " << service;
+ token_map_[service] = auth_token;
+ FireTokenAvailableNotification(service, auth_token);
+ SaveAuthTokenToDB(service, auth_token);
+ // If we got ClientLogin token for "lso" service, then start fetching OAuth2
+ // login scoped token pair.
+ if (service == GaiaConstants::kLSOService) {
+ int index = GetServiceIndex(service);
+ DCHECK_NE(-1, index);
+ fetchers_[index]->StartOAuthLoginTokenFetch(auth_token);
+ }
+}
+
+void TokenService::OnIssueAuthTokenFailure(const std::string& service,
+ const GoogleServiceAuthError& error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ LOG(WARNING) << "Auth token issuing failed for service:" << service;
+ FireTokenRequestFailedNotification(service, error);
+}
+
+void TokenService::OnOAuthLoginTokenSuccess(const std::string& refresh_token,
+ const std::string& access_token,
+ int expires_in_secs) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ VLOG(1) << "Got OAuth2 login token pair";
+ token_map_[GaiaConstants::kGaiaOAuth2LoginRefreshToken] = refresh_token;
+ token_map_[GaiaConstants::kGaiaOAuth2LoginAccessToken] = access_token;
+ SaveAuthTokenToDB(GaiaConstants::kGaiaOAuth2LoginRefreshToken,
+ refresh_token);
+ SaveAuthTokenToDB(GaiaConstants::kGaiaOAuth2LoginAccessToken,
+ access_token);
+ // We don't save expiration information for now.
+
+ FireTokenAvailableNotification(GaiaConstants::kGaiaOAuth2LoginRefreshToken,
+ refresh_token);
+}
+
+void TokenService::OnOAuthLoginTokenFailure(
+ const GoogleServiceAuthError& error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ LOG(WARNING) << "OAuth2 login token pair fetch failed:";
+ FireTokenRequestFailedNotification(
+ GaiaConstants::kGaiaOAuth2LoginRefreshToken, error);
+}
+
+void TokenService::OnOAuthGetAccessTokenSuccess(const std::string& token,
+ const std::string& secret) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ VLOG(1) << "TokenService::OnOAuthGetAccessTokenSuccess";
+ SaveAuthTokenToDB(GaiaConstants::kGaiaOAuthToken, token);
+ SaveAuthTokenToDB(GaiaConstants::kGaiaOAuthSecret, secret);
+ UpdateOAuthCredentials(token, secret);
+}
+
+void TokenService::OnOAuthGetAccessTokenFailure(
+ const GoogleServiceAuthError& error) {
+ VLOG(1) << "TokenService::OnOAuthGetAccessTokenFailure";
+}
+
+void TokenService::OnOAuthWrapBridgeSuccess(const std::string& service_scope,
+ const std::string& token,
+ const std::string& expires_in) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ VLOG(1) << "Got an access token for " << service_scope;
+ token_map_[service_scope] = token;
+ FireTokenAvailableNotification(service_scope, token);
+ SaveAuthTokenToDB(service_scope, token);
+}
+
+void TokenService::OnOAuthWrapBridgeFailure(
+ const std::string& service_scope,
+ const GoogleServiceAuthError& error) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ LOG(WARNING) << "Auth token issuing failed for service:" << service_scope;
+ FireTokenRequestFailedNotification(service_scope, error);
+}
+
+void TokenService::OnWebDataServiceRequestDone(WebDataService::Handle h,
+ const WDTypedResult* result) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(token_loading_query_);
+ token_loading_query_ = 0;
+
+ // If the fetch failed, there will be no result. In that case, we just don't
+ // load any tokens at all from the DB.
+ if (result) {
+ DCHECK(result->GetType() == TOKEN_RESULT);
+ const WDResult<std::map<std::string, std::string> > * token_result =
+ static_cast<const WDResult<std::map<std::string, std::string> > * > (
+ result);
+ LoadTokensIntoMemory(token_result->GetValue(), &token_map_);
+ }
+
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_TOKEN_LOADING_FINISHED,
+ content::Source<TokenService>(this),
+ content::NotificationService::NoDetails());
+}
+
+// Load tokens from the db_token map into the in memory token map.
+void TokenService::LoadTokensIntoMemory(
+ const std::map<std::string, std::string>& db_tokens,
+ std::map<std::string, std::string>* in_memory_tokens) {
+
+ for (int i = 0; i < kNumServices; i++) {
+ LoadSingleTokenIntoMemory(db_tokens, in_memory_tokens, kServices[i]);
+ }
+ LoadSingleTokenIntoMemory(db_tokens, in_memory_tokens,
+ GaiaConstants::kGaiaOAuth2LoginRefreshToken);
+ LoadSingleTokenIntoMemory(db_tokens, in_memory_tokens,
+ GaiaConstants::kGaiaOAuth2LoginAccessToken);
+
+ if (credentials_.lsid.empty() && credentials_.sid.empty()) {
+ // Look for GAIA SID and LSID tokens. If we have both, and the current
+ // crendentials are empty, update the credentials.
+ std::string lsid;
+ std::string sid;
+
+ if (db_tokens.count(GaiaConstants::kGaiaLsid) > 0)
+ lsid = db_tokens.find(GaiaConstants::kGaiaLsid)->second;
+
+ if (db_tokens.count(GaiaConstants::kGaiaSid) > 0)
+ sid = db_tokens.find(GaiaConstants::kGaiaSid)->second;
+
+ if (!lsid.empty() && !sid.empty()) {
+ UpdateCredentials(GaiaAuthConsumer::ClientLoginResult(sid,
+ lsid,
+ std::string(),
+ std::string()));
+ }
+ }
+
+ for (int i = 0; i < kNumOAuthServices; i++) {
+ LoadSingleTokenIntoMemory(db_tokens, in_memory_tokens, kOAuthServices[i]);
+ }
+
+ if (oauth_token_.empty() && oauth_secret_.empty()) {
+ // Look for GAIA OAuth1 access token and secret. If we have both, and the
+ // current crendentials are empty, update the credentials.
+ std::string oauth_token;
+ std::string oauth_secret;
+
+ if (db_tokens.count(GaiaConstants::kGaiaOAuthToken) > 0)
+ oauth_token = db_tokens.find(GaiaConstants::kGaiaOAuthToken)->second;
+
+ if (db_tokens.count(GaiaConstants::kGaiaOAuthSecret) > 0)
+ oauth_secret = db_tokens.find(GaiaConstants::kGaiaOAuthSecret)->second;
+
+ if (!oauth_token.empty() && !oauth_secret.empty()) {
+ UpdateOAuthCredentials(oauth_token, oauth_secret);
+ }
+ }
+}
+
+void TokenService::LoadSingleTokenIntoMemory(
+ const std::map<std::string, std::string>& db_tokens,
+ std::map<std::string, std::string>* in_memory_tokens,
+ const std::string& service) {
+ // OnIssueAuthTokenSuccess should come from the same thread.
+ // If a token is already present in the map, it could only have
+ // come from a DB read or from IssueAuthToken. Since we should never
+ // fetch from the DB twice in a browser session, it must be from
+ // OnIssueAuthTokenSuccess, which is a live fetcher.
+ //
+ // Network fetched tokens take priority over DB tokens, so exclude tokens
+ // which have already been loaded by the fetcher.
+ if (!in_memory_tokens->count(service) && db_tokens.count(service)) {
+ std::string db_token = db_tokens.find(service)->second;
+ if (!db_token.empty()) {
+ VLOG(1) << "Loading " << service << " token from DB: " << db_token;
+ (*in_memory_tokens)[service] = db_token;
+ FireTokenAvailableNotification(service, db_token);
+ // Failures are only for network errors.
+ }
+ }
+}
+
+void TokenService::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_EQ(type, chrome::NOTIFICATION_TOKEN_UPDATED);
+ TokenAvailableDetails* tok_details =
+ content::Details<TokenAvailableDetails>(details).ptr();
+ OnIssueAuthTokenSuccess(tok_details->service(), tok_details->token());
+}
diff --git a/chrome/browser/net/gaia/token_service.h b/chrome/browser/net/gaia/token_service.h
new file mode 100644
index 0000000..99c31d1
--- /dev/null
+++ b/chrome/browser/net/gaia/token_service.h
@@ -0,0 +1,281 @@
+// Copyright (c) 2011 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.
+//
+// The TokenService will supply authentication tokens for any service that
+// needs it, such as sync. Whenever the user logs in, a controller watching
+// the token service is expected either to call ClientLogin to derive a new
+// SID and LSID, or to use GAIA OAuth requests to derive an OAuth1 access
+// token for the OAuthLogin scope. Whenever such credentials are available,
+// the TokenService should be updated with new credentials. The controller
+// should then start fetching tokens, which will be written to the database
+// after retrieval, as well as provided to listeners.
+//
+// A token service controller like the ChromiumOS login is expected to:
+//
+// Initialize() // Soon as you can
+// LoadTokensFromDB() // When it's OK to talk to the database
+// UpdateCredentials() // When user logs in
+// StartFetchingTokens() // When it's safe to start fetching
+//
+// Typically a user of the TokenService is expected just to call:
+//
+// if (token_service.HasTokenForService(servicename)) {
+// SetMyToken(token_service.GetTokenForService(servicename));
+// }
+// RegisterSomeObserver(token_service);
+//
+// Whenever a token update occurs:
+// OnTokenAvailable(...) {
+// if (IsServiceICareAbout(notification.service())) {
+// SetMyToken(notification.token())
+// }
+// }
+
+#ifndef CHROME_BROWSER_NET_GAIA_TOKEN_SERVICE_H_
+#define CHROME_BROWSER_NET_GAIA_TOKEN_SERVICE_H_
+#pragma once
+
+#include <map>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/net/gaia/gaia_oauth_consumer.h"
+#include "chrome/browser/net/gaia/gaia_oauth_fetcher.h"
+#include "chrome/browser/webdata/web_data_service.h"
+#include "chrome/common/net/gaia/gaia_auth_consumer.h"
+#include "chrome/common/net/gaia/gaia_auth_fetcher.h"
+#include "chrome/common/net/gaia/google_service_auth_error.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+
+class Profile;
+class TokenServiceTest;
+
+namespace net {
+class URLRequestContextGetter;
+}
+
+// The TokenService is a Profile member, so all calls are expected
+// from the UI thread.
+class TokenService : public GaiaAuthConsumer,
+ public GaiaOAuthConsumer,
+ public WebDataServiceConsumer,
+ public content::NotificationObserver {
+ public:
+ TokenService();
+ virtual ~TokenService();
+
+ // Notification classes
+ class TokenAvailableDetails {
+ public:
+ TokenAvailableDetails() {}
+ TokenAvailableDetails(const std::string& service,
+ const std::string& token)
+ : service_(service), token_(token) {}
+ const std::string& service() const { return service_; }
+ const std::string& token() const { return token_; }
+ private:
+ std::string service_;
+ std::string token_;
+ };
+
+ class TokenRequestFailedDetails {
+ public:
+ TokenRequestFailedDetails()
+ : error_(GoogleServiceAuthError::NONE) {}
+ TokenRequestFailedDetails(const std::string& service,
+ const GoogleServiceAuthError& error)
+ : service_(service), error_(error) {}
+ const std::string& service() const { return service_; }
+ const GoogleServiceAuthError& error() const { return error_; }
+ private:
+ std::string service_;
+ GoogleServiceAuthError error_;
+ };
+
+ // Initialize this token service with a request source
+ // (usually from a GaiaAuthConsumer constant), and the profile.
+ // Typically you'd then update the credentials.
+ void Initialize(const char* const source, Profile* profile);
+
+ // Used to determine whether Initialize() has been called.
+ bool Initialized() const { return !source_.empty(); }
+
+ // Update ClientLogin credentials in the token service.
+ // Afterwards you can StartFetchingTokens.
+ void UpdateCredentials(
+ const GaiaAuthConsumer::ClientLoginResult& credentials);
+
+ // Update OAuth credentials in the token service.
+ // Afterwards you can StartFetchingOAuthTokens.
+ void UpdateOAuthCredentials(
+ const std::string& oauth_token,
+ const std::string& oauth_secret);
+
+ // Terminate any running requests and reset the TokenService to a clean
+ // slate. Resets in memory structures. Does not modify the DB.
+ // When this is done, no tokens will be left in memory and no
+ // user credentials will be left. Useful if a user is logging out.
+ // Initialize doesn't need to be called again but UpdateCredentials and
+ // UpdateOAuthCredentials do.
+ void ResetCredentialsInMemory();
+
+ // Async load all tokens for services we know of from the DB.
+ // You should do this at startup. Optionally you can do it again
+ // after you reset in memory credentials.
+ void LoadTokensFromDB();
+
+ // Clear all DB stored tokens for the current profile. Tokens may still be
+ // available in memory. If a DB load is pending it may still be serviced.
+ void EraseTokensFromDB();
+
+ // For legacy services with their own auth routines, they can just read
+ // the LSID out directly. Deprecated.
+ bool HasLsid() const;
+ const std::string& GetLsid() const;
+ // Did we get a proper LSID?
+ virtual bool AreCredentialsValid() const;
+
+ // For services with their own auth routines, they can read the OAuth token
+ // and secret directly. Deprecated (in the sense of discouraged).
+ bool HasOAuthCredentials() const;
+ const std::string& GetOAuthToken() const;
+ const std::string& GetOAuthSecret() const;
+
+ // Tokens will be fetched for all services(sync, talk) in the background.
+ // Results come back via event channel. Services can also poll before events
+ // are issued.
+ void StartFetchingTokens();
+ void StartFetchingOAuthTokens();
+ virtual bool HasTokenForService(const char* service) const;
+ const std::string& GetTokenForService(const char* const service) const;
+
+ // OAuth login token is an all-powerful token that allows creating OAuth2
+ // tokens for any other scope (i.e. down-scoping).
+ // Typical use is to create an OAuth2 token for appropriate scope and then
+ // use that token to call a Google API.
+ virtual bool HasOAuthLoginToken() const;
+ const std::string& GetOAuth2LoginRefreshToken() const;
+ const std::string& GetOAuth2LoginAccessToken() const;
+
+ // For tests only. Doesn't save to the WebDB.
+ void IssueAuthTokenForTest(const std::string& service,
+ const std::string& auth_token);
+
+ // GaiaAuthConsumer implementation.
+ virtual void OnIssueAuthTokenSuccess(const std::string& service,
+ const std::string& auth_token) OVERRIDE;
+ virtual void OnIssueAuthTokenFailure(
+ const std::string& service,
+ const GoogleServiceAuthError& error) OVERRIDE;
+ virtual void OnOAuthLoginTokenSuccess(const std::string& refresh_token,
+ const std::string& access_token,
+ int expires_in_secs) OVERRIDE;
+ virtual void OnOAuthLoginTokenFailure(const GoogleServiceAuthError& error)
+ OVERRIDE;
+
+ // GaiaOAuthConsumer implementation.
+ virtual void OnOAuthGetAccessTokenSuccess(const std::string& token,
+ const std::string& secret) OVERRIDE;
+ virtual void OnOAuthGetAccessTokenFailure(
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ virtual void OnOAuthWrapBridgeSuccess(const std::string& service_scope,
+ const std::string& token,
+ const std::string& expires_in) OVERRIDE;
+ virtual void OnOAuthWrapBridgeFailure(
+ const std::string& service_name,
+ const GoogleServiceAuthError& error) OVERRIDE;
+
+ // WebDataServiceConsumer implementation.
+ virtual void OnWebDataServiceRequestDone(
+ WebDataService::Handle h,
+ const WDTypedResult* result) OVERRIDE;
+
+ // content::NotificationObserver implementation.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ private:
+
+ void FireTokenAvailableNotification(const std::string& service,
+ const std::string& auth_token);
+
+ void FireTokenRequestFailedNotification(const std::string& service,
+ const GoogleServiceAuthError& error);
+
+ void LoadTokensIntoMemory(
+ const std::map<std::string, std::string>& db_tokens,
+ std::map<std::string, std::string>* in_memory_tokens);
+ void LoadSingleTokenIntoMemory(
+ const std::map<std::string, std::string>& db_tokens,
+ std::map<std::string, std::string>* in_memory_tokens,
+ const std::string& service);
+
+ void SaveAuthTokenToDB(const std::string& service,
+ const std::string& auth_token);
+
+ // Returns the index of the given service.
+ static int GetServiceIndex(const std::string& service);
+
+ // The profile with which this instance was initialized, or NULL.
+ Profile* profile_;
+
+ // Web data service to access tokens from.
+ scoped_refptr<WebDataService> web_data_service_;
+ // Getter to use for fetchers.
+ scoped_refptr<net::URLRequestContextGetter> getter_;
+ // Request handle to load Gaia tokens from DB.
+ WebDataService::Handle token_loading_query_;
+
+ // Gaia request source for Gaia accounting.
+ std::string source_;
+ // Credentials from ClientLogin for Issuing auth tokens.
+ GaiaAuthConsumer::ClientLoginResult credentials_;
+ // Credentials from Gaia OAuth (uber/login token)
+ std::string oauth_token_;
+ std::string oauth_secret_;
+
+ // Size of array of services capable of ClientLogin-based authentication.
+ // This value must be defined here.
+ // NOTE: The use of --enable-sync-oauth does not affect this count. The
+ // TokenService can continue to do some degree of ClientLogin token
+ // management, mostly related to persistence while Sync and possibly other
+ // services are using OAuth-based authentication.
+ static const int kNumServices = 5;
+ // List of services that are capable of ClientLogin-based authentication.
+ static const char* kServices[kNumServices];
+ // A bunch of fetchers suitable for ClientLogin token issuing. We don't care
+ // about the ordering, nor do we care which is for which service.
+ scoped_ptr<GaiaAuthFetcher> fetchers_[kNumServices];
+
+ // Size of array of services capable of OAuth-based authentication. This
+ // value must be defined here.
+ // NOTE: The use of --enable-sync-oauth does not affect this count. The
+ // TokenService can continue to do some degree of OAuth token
+ // management, mostly related to persistence while Sync and possibly other
+ // services are using ClientLogin-based authentication.
+ static const int kNumOAuthServices = 1;
+ // List of services that are capable of OAuth-based authentication.
+ static const char* kOAuthServices[kNumOAuthServices];
+ // A bunch of fetchers suitable for OAuth token issuing. We don't care about
+ // the ordering, nor do we care which is for which service.
+ scoped_ptr<GaiaOAuthFetcher> oauth_fetchers_[kNumOAuthServices];
+
+ // Map from service to token.
+ std::map<std::string, std::string> token_map_;
+
+ content::NotificationRegistrar registrar_;
+
+ friend class TokenServiceTest;
+ FRIEND_TEST_ALL_PREFIXES(TokenServiceTest, LoadTokensIntoMemoryBasic);
+ FRIEND_TEST_ALL_PREFIXES(TokenServiceTest, LoadTokensIntoMemoryAdvanced);
+ FRIEND_TEST_ALL_PREFIXES(TokenServiceTest, FullIntegrationNewServicesAdded);
+
+ DISALLOW_COPY_AND_ASSIGN(TokenService);
+};
+
+#endif // CHROME_BROWSER_NET_GAIA_TOKEN_SERVICE_H_
diff --git a/chrome/browser/net/gaia/token_service_unittest.cc b/chrome/browser/net/gaia/token_service_unittest.cc
new file mode 100644
index 0000000..c43cd5c
--- /dev/null
+++ b/chrome/browser/net/gaia/token_service_unittest.cc
@@ -0,0 +1,533 @@
+// Copyright (c) 2011 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.
+//
+// This file defines a unit test for the profile's token service.
+
+#include "chrome/browser/net/gaia/token_service_unittest.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "chrome/browser/password_manager/encryptor.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/net/gaia/gaia_auth_fetcher_unittest.h"
+#include "chrome/common/net/gaia/gaia_constants.h"
+#include "content/test/test_url_fetcher_factory.h"
+
+using content::BrowserThread;
+
+TokenAvailableTracker::TokenAvailableTracker() {}
+
+TokenAvailableTracker::~TokenAvailableTracker() {}
+
+void TokenAvailableTracker::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ TestNotificationTracker::Observe(type, source, details);
+ if (type == chrome::NOTIFICATION_TOKEN_AVAILABLE) {
+ content::Details<const TokenService::TokenAvailableDetails> full = details;
+ details_ = *full.ptr();
+ }
+}
+
+TokenFailedTracker::TokenFailedTracker() {}
+
+TokenFailedTracker::~TokenFailedTracker() {}
+
+void TokenFailedTracker::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ TestNotificationTracker::Observe(type, source, details);
+ if (type == chrome::NOTIFICATION_TOKEN_REQUEST_FAILED) {
+ content::Details<const TokenService::TokenRequestFailedDetails> full =
+ details;
+ details_ = *full.ptr();
+ }
+}
+
+TokenServiceTestHarness::TokenServiceTestHarness()
+ : ui_thread_(BrowserThread::UI, &message_loop_),
+ db_thread_(BrowserThread::DB) {
+}
+
+TokenServiceTestHarness::~TokenServiceTestHarness() {}
+
+void TokenServiceTestHarness::SetUp() {
+#if defined(OS_MACOSX)
+ Encryptor::UseMockKeychain(true);
+#endif
+ credentials_.sid = "sid";
+ credentials_.lsid = "lsid";
+ credentials_.token = "token";
+ credentials_.data = "data";
+ oauth_token_ = "oauth";
+ oauth_secret_ = "secret";
+
+ ASSERT_TRUE(db_thread_.Start());
+
+ profile_.reset(new TestingProfile());
+ profile_->CreateWebDataService(false);
+ WaitForDBLoadCompletion();
+
+ success_tracker_.ListenFor(chrome::NOTIFICATION_TOKEN_AVAILABLE,
+ content::Source<TokenService>(&service_));
+ failure_tracker_.ListenFor(chrome::NOTIFICATION_TOKEN_REQUEST_FAILED,
+ content::Source<TokenService>(&service_));
+
+ service_.Initialize("test", profile_.get());
+}
+
+void TokenServiceTestHarness::TearDown() {
+ // You have to destroy the profile before the db_thread_ stops.
+ if (profile_.get()) {
+ profile_.reset(NULL);
+ }
+
+ db_thread_.Stop();
+ MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+ MessageLoop::current()->Run();
+}
+
+void TokenServiceTestHarness::WaitForDBLoadCompletion() {
+ // The WebDB does all work on the DB thread. This will add an event
+ // to the end of the DB thread, so when we reach this task, all DB
+ // operations should be complete.
+ base::WaitableEvent done(false, false);
+ BrowserThread::PostTask(
+ BrowserThread::DB,
+ FROM_HERE,
+ base::Bind(&base::WaitableEvent::Signal, base::Unretained(&done)));
+ done.Wait();
+
+ // Notifications should be returned from the DB thread onto the UI thread.
+ message_loop_.RunAllPending();
+}
+
+class TokenServiceTest : public TokenServiceTestHarness {
+ public:
+ virtual void SetUp() {
+ TokenServiceTestHarness::SetUp();
+ service_.UpdateCredentials(credentials_);
+ service_.UpdateOAuthCredentials(oauth_token_, oauth_secret_);
+ }
+ protected:
+ void TestLoadSingleToken(
+ std::map<std::string, std::string>* db_tokens,
+ std::map<std::string, std::string>* memory_tokens,
+ const std::string& service) {
+ std::string token = service + "_token";
+ (*db_tokens)[service] = token;
+ size_t prev_success_size = success_tracker_.size();
+ service_.LoadTokensIntoMemory(*db_tokens, memory_tokens);
+
+ // Check notification.
+ EXPECT_EQ(prev_success_size + 1, success_tracker_.size());
+ TokenService::TokenAvailableDetails details = success_tracker_.details();
+ EXPECT_EQ(details.service(), service);
+ EXPECT_EQ(details.token(), token);
+ // Check memory tokens.
+ EXPECT_EQ(1U, memory_tokens->count(service));
+ EXPECT_EQ((*memory_tokens)[service], token);
+ }
+};
+
+TEST_F(TokenServiceTest, SanityCheck) {
+ EXPECT_TRUE(service_.HasLsid());
+ EXPECT_EQ(service_.GetLsid(), "lsid");
+ EXPECT_FALSE(service_.HasTokenForService("nonexistent service"));
+ EXPECT_TRUE(service_.HasOAuthCredentials());
+ EXPECT_EQ(service_.GetOAuthToken(), "oauth");
+ EXPECT_EQ(service_.GetOAuthSecret(), "secret");
+}
+
+TEST_F(TokenServiceTest, NoToken) {
+ EXPECT_FALSE(service_.HasTokenForService("nonexistent service"));
+ EXPECT_EQ(service_.GetTokenForService("nonexistent service"), std::string());
+}
+
+TEST_F(TokenServiceTest, NotificationSuccess) {
+ EXPECT_EQ(0U, success_tracker_.size());
+ EXPECT_EQ(0U, failure_tracker_.size());
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
+ EXPECT_EQ(1U, success_tracker_.size());
+ EXPECT_EQ(0U, failure_tracker_.size());
+
+ TokenService::TokenAvailableDetails details = success_tracker_.details();
+ // MSVC doesn't like this comparison as EQ.
+ EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
+ EXPECT_EQ(details.token(), "token");
+}
+
+TEST_F(TokenServiceTest, NotificationOAuthLoginTokenSuccess) {
+ EXPECT_EQ(0U, success_tracker_.size());
+ EXPECT_EQ(0U, failure_tracker_.size());
+ service_.OnOAuthLoginTokenSuccess("rt1", "at1", 3600);
+ EXPECT_EQ(1U, success_tracker_.size());
+ EXPECT_EQ(0U, failure_tracker_.size());
+
+ TokenService::TokenAvailableDetails details = success_tracker_.details();
+ // MSVC doesn't like this comparison as EQ.
+ EXPECT_TRUE(details.service() ==
+ GaiaConstants::kGaiaOAuth2LoginRefreshToken);
+ EXPECT_EQ(details.token(), "rt1");
+}
+
+TEST_F(TokenServiceTest, NotificationSuccessOAuth) {
+ EXPECT_EQ(0U, success_tracker_.size());
+ EXPECT_EQ(0U, failure_tracker_.size());
+ service_.OnOAuthWrapBridgeSuccess(
+ GaiaConstants::kSyncServiceOAuth, "token", "3600");
+ EXPECT_EQ(1U, success_tracker_.size());
+ EXPECT_EQ(0U, failure_tracker_.size());
+
+ TokenService::TokenAvailableDetails details = success_tracker_.details();
+ // MSVC doesn't like this comparison as EQ.
+ EXPECT_TRUE(details.service() == GaiaConstants::kSyncServiceOAuth);
+ EXPECT_EQ(details.token(), "token");
+}
+
+TEST_F(TokenServiceTest, NotificationFailed) {
+ EXPECT_EQ(0U, success_tracker_.size());
+ EXPECT_EQ(0U, failure_tracker_.size());
+ GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
+ service_.OnIssueAuthTokenFailure(GaiaConstants::kSyncService, error);
+ EXPECT_EQ(0U, success_tracker_.size());
+ EXPECT_EQ(1U, failure_tracker_.size());
+
+ TokenService::TokenRequestFailedDetails details = failure_tracker_.details();
+
+ // MSVC doesn't like this comparison as EQ.
+ EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
+ EXPECT_TRUE(details.error() == error); // Struct has no print function.
+}
+
+TEST_F(TokenServiceTest, NotificationOAuthLoginTokenFailed) {
+ EXPECT_EQ(0U, success_tracker_.size());
+ EXPECT_EQ(0U, failure_tracker_.size());
+ GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
+ service_.OnOAuthLoginTokenFailure(error);
+ EXPECT_EQ(0U, success_tracker_.size());
+ EXPECT_EQ(1U, failure_tracker_.size());
+
+ TokenService::TokenRequestFailedDetails details = failure_tracker_.details();
+
+ // MSVC doesn't like this comparison as EQ.
+ EXPECT_TRUE(details.service() ==
+ GaiaConstants::kGaiaOAuth2LoginRefreshToken);
+ EXPECT_TRUE(details.error() == error); // Struct has no print function.
+}
+
+TEST_F(TokenServiceTest, NotificationFailedOAuth) {
+ EXPECT_EQ(0U, success_tracker_.size());
+ EXPECT_EQ(0U, failure_tracker_.size());
+ GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
+ service_.OnOAuthWrapBridgeFailure(GaiaConstants::kSyncServiceOAuth, error);
+ EXPECT_EQ(0U, success_tracker_.size());
+ EXPECT_EQ(1U, failure_tracker_.size());
+
+ TokenService::TokenRequestFailedDetails details = failure_tracker_.details();
+
+ // MSVC doesn't like this comparison as EQ.
+ EXPECT_TRUE(details.service() == GaiaConstants::kSyncServiceOAuth);
+ EXPECT_TRUE(details.error() == error); // Struct has no print function.
+}
+
+TEST_F(TokenServiceTest, OnTokenSuccessUpdate) {
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
+
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token2");
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token2");
+
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "");
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "");
+}
+
+TEST_F(TokenServiceTest, OnOAuth2LoginTokenSuccessUpdate) {
+ std::string service = GaiaConstants::kGaiaOAuth2LoginRefreshToken;
+ service_.OnOAuthLoginTokenSuccess("rt1", "at1", 3600);
+ EXPECT_TRUE(service_.HasOAuthLoginToken());
+ EXPECT_EQ(service_.GetOAuth2LoginRefreshToken(), "rt1");
+
+ service_.OnOAuthLoginTokenSuccess("rt2", "at2", 3600);
+ EXPECT_TRUE(service_.HasOAuthLoginToken());
+ EXPECT_EQ(service_.GetOAuth2LoginRefreshToken(), "rt2");
+
+ service_.OnOAuthLoginTokenSuccess("rt3", "at3", 3600);
+ EXPECT_TRUE(service_.HasOAuthLoginToken());
+ EXPECT_EQ(service_.GetOAuth2LoginRefreshToken(), "rt3");
+}
+
+TEST_F(TokenServiceTest, OnTokenSuccess) {
+ // Don't "start fetching", just go ahead and issue the callback.
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+ // Gaia returns the entire result as the token so while this is a shared
+ // result with ClientLogin, it doesn't matter, we should still get it back.
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
+
+ // Try the OAuth service.
+ service_.OnOAuthWrapBridgeSuccess(
+ GaiaConstants::kSyncServiceOAuth, "token2", "3600");
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncServiceOAuth),
+ "token2");
+
+ // First didn't change.
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
+}
+
+TEST_F(TokenServiceTest, ResetSimple) {
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_TRUE(service_.HasLsid());
+ service_.OnOAuthGetAccessTokenSuccess("token2", "secret");
+ service_.OnOAuthWrapBridgeSuccess(
+ GaiaConstants::kSyncServiceOAuth, "token3", "4321");
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_TRUE(service_.HasOAuthCredentials());
+
+ service_.ResetCredentialsInMemory();
+
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_FALSE(service_.HasLsid());
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_FALSE(service_.HasOAuthCredentials());
+}
+
+TEST_F(TokenServiceTest, ResetComplex) {
+ TestURLFetcherFactory factory;
+ service_.StartFetchingTokens();
+ // You have to call delegates by hand with the test fetcher,
+ // Let's pretend only one returned.
+
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "eraseme");
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService),
+ "eraseme");
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+
+ service_.OnOAuthWrapBridgeSuccess(
+ GaiaConstants::kSyncServiceOAuth, "erasemetoo", "1234");
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService),
+ "eraseme");
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncServiceOAuth),
+ "erasemetoo");
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+
+ service_.ResetCredentialsInMemory();
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+ EXPECT_FALSE(service_.HasLsid());
+ EXPECT_FALSE(service_.HasOAuthCredentials());
+
+ // Now start using it again.
+ service_.UpdateCredentials(credentials_);
+ EXPECT_TRUE(service_.HasLsid());
+ service_.StartFetchingTokens();
+ service_.UpdateOAuthCredentials(oauth_token_, oauth_secret_);
+ EXPECT_TRUE(service_.HasOAuthCredentials());
+ service_.StartFetchingOAuthTokens();
+
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
+ service_.OnOAuthWrapBridgeSuccess(
+ GaiaConstants::kSyncServiceOAuth, "token2", "3600");
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kTalkService, "token3");
+
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), "token");
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncServiceOAuth),
+ "token2");
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kTalkService), "token3");
+}
+
+TEST_F(TokenServiceTest, FullIntegration) {
+ std::string result = "SID=sid\nLSID=lsid\nAuth=auth\n";
+
+ {
+ MockFactory<MockFetcher> factory;
+ factory.set_results(result);
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+ service_.StartFetchingTokens();
+ }
+
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kTalkService));
+ // Gaia returns the entire result as the token so while this is a shared
+ // result with ClientLogin, it doesn't matter, we should still get it back.
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kSyncService), result);
+ EXPECT_EQ(service_.GetTokenForService(GaiaConstants::kTalkService), result);
+
+ service_.ResetCredentialsInMemory();
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+}
+
+TEST_F(TokenServiceTest, LoadTokensIntoMemoryBasic) {
+ // Validate that the method sets proper data in notifications and map.
+ std::map<std::string, std::string> db_tokens;
+ std::map<std::string, std::string> memory_tokens;
+
+ service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
+ EXPECT_TRUE(db_tokens.empty());
+ EXPECT_TRUE(memory_tokens.empty());
+ EXPECT_EQ(0U, success_tracker_.size());
+
+ std::string service;
+ std::string token;
+ for (int i = 0; i < TokenService::kNumServices; ++i) {
+ service = TokenService::kServices[i];
+ TestLoadSingleToken(&db_tokens, &memory_tokens, service);
+ }
+ for (int i = 0; i < TokenService::kNumOAuthServices; ++i) {
+ service = TokenService::kOAuthServices[i];
+ TestLoadSingleToken(&db_tokens, &memory_tokens, service);
+ }
+ service = GaiaConstants::kGaiaOAuth2LoginRefreshToken;
+ TestLoadSingleToken(&db_tokens, &memory_tokens, service);
+ service = GaiaConstants::kGaiaOAuth2LoginAccessToken;
+ TestLoadSingleToken(&db_tokens, &memory_tokens, service);
+}
+
+TEST_F(TokenServiceTest, LoadTokensIntoMemoryAdvanced) {
+ // LoadTokensIntoMemory should avoid setting tokens already in the
+ // token map.
+ std::map<std::string, std::string> db_tokens;
+ std::map<std::string, std::string> memory_tokens;
+
+ db_tokens["ignore"] = "token";
+
+ service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
+ EXPECT_TRUE(memory_tokens.empty());
+ db_tokens[GaiaConstants::kSyncService] = "pepper";
+
+ service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
+ EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
+ EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "pepper");
+ EXPECT_EQ(1U, success_tracker_.size());
+ success_tracker_.Reset();
+
+ // SyncService token is already in memory. Pretend we got it off
+ // the disk as well, but an older token.
+ db_tokens[GaiaConstants::kSyncService] = "ignoreme";
+ db_tokens[GaiaConstants::kSyncServiceOAuth] = "tomato";
+ service_.LoadTokensIntoMemory(db_tokens, &memory_tokens);
+
+ EXPECT_EQ(2U, memory_tokens.size());
+ EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_EQ(memory_tokens[GaiaConstants::kSyncServiceOAuth], "tomato");
+ EXPECT_EQ(1U, success_tracker_.size());
+ EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
+ EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "pepper");
+}
+
+TEST_F(TokenServiceTest, WebDBLoadIntegration) {
+ service_.LoadTokensFromDB();
+ WaitForDBLoadCompletion();
+ EXPECT_EQ(0U, success_tracker_.size());
+
+ // Should result in DB write.
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
+ EXPECT_EQ(1U, success_tracker_.size());
+ service_.OnOAuthGetAccessTokenSuccess("token1", "secret");
+ service_.OnOAuthWrapBridgeSuccess(
+ GaiaConstants::kSyncServiceOAuth, "token2", "3600");
+ EXPECT_EQ(2U, success_tracker_.size());
+
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ // Clean slate.
+ service_.ResetCredentialsInMemory();
+ success_tracker_.Reset();
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+
+ service_.LoadTokensFromDB();
+ WaitForDBLoadCompletion();
+
+ EXPECT_EQ(2U, success_tracker_.size());
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+ EXPECT_TRUE(service_.HasLsid());
+ EXPECT_TRUE(service_.HasOAuthCredentials());
+}
+
+TEST_F(TokenServiceTest, MultipleLoadResetIntegration) {
+ // Should result in DB write.
+ service_.OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
+ service_.ResetCredentialsInMemory();
+ success_tracker_.Reset();
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_FALSE(service_.HasLsid());
+
+ service_.OnOAuthGetAccessTokenSuccess("token2", "secret");
+ service_.OnOAuthWrapBridgeSuccess(
+ GaiaConstants::kSyncServiceOAuth, "token3", "3600");
+ service_.ResetCredentialsInMemory();
+ success_tracker_.Reset();
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_FALSE(service_.HasOAuthCredentials());
+
+ service_.LoadTokensFromDB();
+ WaitForDBLoadCompletion();
+
+ service_.LoadTokensFromDB(); // Should do nothing.
+ WaitForDBLoadCompletion();
+
+ EXPECT_EQ(2U, success_tracker_.size());
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_FALSE(service_.HasTokenForService(GaiaConstants::kTalkService));
+ EXPECT_TRUE(service_.HasLsid());
+ EXPECT_TRUE(service_.HasOAuthCredentials());
+
+ // Reset it one more time so there's no surprises.
+ service_.ResetCredentialsInMemory();
+ success_tracker_.Reset();
+
+ service_.LoadTokensFromDB();
+ WaitForDBLoadCompletion();
+
+ EXPECT_EQ(2U, success_tracker_.size());
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncService));
+ EXPECT_TRUE(service_.HasTokenForService(GaiaConstants::kSyncServiceOAuth));
+ EXPECT_TRUE(service_.HasLsid());
+ EXPECT_TRUE(service_.HasOAuthCredentials());
+}
+
+#ifndef NDEBUG
+class TokenServiceCommandLineTest : public TokenServiceTestHarness {
+ public:
+ virtual void SetUp() {
+ CommandLine original_cl(*CommandLine::ForCurrentProcess());
+ CommandLine::ForCurrentProcess()->AppendSwitchASCII(
+ switches::kSetToken, "my_service:my_value");
+ TokenServiceTestHarness::SetUp();
+ service_.UpdateCredentials(credentials_);
+ service_.UpdateOAuthCredentials(oauth_token_, oauth_secret_);
+
+ *CommandLine::ForCurrentProcess() = original_cl;
+ }
+};
+
+TEST_F(TokenServiceCommandLineTest, TestValueOverride) {
+ EXPECT_TRUE(service_.HasTokenForService("my_service"));
+ EXPECT_EQ("my_value", service_.GetTokenForService("my_service"));
+}
+#endif // ifndef NDEBUG
diff --git a/chrome/browser/net/gaia/token_service_unittest.h b/chrome/browser/net/gaia/token_service_unittest.h
new file mode 100644
index 0000000..5f14feb
--- /dev/null
+++ b/chrome/browser/net/gaia/token_service_unittest.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2011 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.
+//
+// This file defines a unit test harness for the profile's token service.
+
+#ifndef CHROME_BROWSER_NET_GAIA_TOKEN_SERVICE_UNITTEST_H_
+#define CHROME_BROWSER_NET_GAIA_TOKEN_SERVICE_UNITTEST_H_
+#pragma once
+
+#include "base/message_loop.h"
+#include "base/synchronization/waitable_event.h"
+#include "chrome/browser/net/gaia/token_service.h"
+#include "chrome/browser/webdata/web_data_service.h"
+#include "chrome/common/net/gaia/gaia_auth_consumer.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
+#include "content/test/test_browser_thread.h"
+#include "content/test/test_notification_tracker.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// TestNotificationTracker doesn't do a deep copy on the notification details.
+// We have to in order to read it out, or we have a bad ptr, since the details
+// are a reference on the stack.
+class TokenAvailableTracker : public TestNotificationTracker {
+ public:
+ TokenAvailableTracker();
+ virtual ~TokenAvailableTracker();
+
+ const TokenService::TokenAvailableDetails& details() {
+ return details_;
+ }
+
+ private:
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ TokenService::TokenAvailableDetails details_;
+};
+
+class TokenFailedTracker : public TestNotificationTracker {
+ public:
+ TokenFailedTracker();
+ virtual ~TokenFailedTracker();
+
+ const TokenService::TokenRequestFailedDetails& details() {
+ return details_;
+ }
+
+ private:
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ TokenService::TokenRequestFailedDetails details_;
+};
+
+class TokenServiceTestHarness : public testing::Test {
+ public:
+ TokenServiceTestHarness();
+ virtual ~TokenServiceTestHarness();
+
+ virtual void SetUp() OVERRIDE;
+
+ virtual void TearDown() OVERRIDE;
+
+ void WaitForDBLoadCompletion();
+
+ MessageLoopForUI message_loop_;
+ content::TestBrowserThread ui_thread_; // Mostly so DCHECKS pass.
+ content::TestBrowserThread db_thread_; // WDS on here
+
+ TokenService service_;
+ TokenAvailableTracker success_tracker_;
+ TokenFailedTracker failure_tracker_;
+ GaiaAuthConsumer::ClientLoginResult credentials_;
+ std::string oauth_token_;
+ std::string oauth_secret_;
+ scoped_ptr<TestingProfile> profile_;
+};
+
+#endif // CHROME_BROWSER_NET_GAIA_TOKEN_SERVICE_UNITTEST_H_