diff options
Diffstat (limited to 'chrome/browser/net')
-rw-r--r-- | chrome/browser/net/gaia/token_service.cc | 464 | ||||
-rw-r--r-- | chrome/browser/net/gaia/token_service.h | 281 | ||||
-rw-r--r-- | chrome/browser/net/gaia/token_service_unittest.cc | 533 | ||||
-rw-r--r-- | chrome/browser/net/gaia/token_service_unittest.h | 84 |
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_ |