diff options
Diffstat (limited to 'chromium/google_apis/gaia')
18 files changed, 2204 insertions, 199 deletions
diff --git a/chromium/google_apis/gaia/fake_gaia.cc b/chromium/google_apis/gaia/fake_gaia.cc new file mode 100644 index 00000000000..9f4a8122a22 --- /dev/null +++ b/chromium/google_apis/gaia/fake_gaia.cc @@ -0,0 +1,143 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "google_apis/gaia/fake_gaia.h" + +#include "base/base_paths.h" +#include "base/file_util.h" +#include "base/files/file_path.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "google_apis/gaia/gaia_urls.h" +#include "net/base/url_util.h" +#include "net/http/http_status_code.h" +#include "net/test/embedded_test_server/http_request.h" +#include "net/test/embedded_test_server/http_response.h" +#include "url/url_parse.h" + +using namespace net::test_server; + +namespace { +const base::FilePath::CharType kServiceLogin[] = + FILE_PATH_LITERAL("google_apis/test/service_login.html"); +} + +FakeGaia::AccessTokenInfo::AccessTokenInfo() + : expires_in(3600) {} + +FakeGaia::AccessTokenInfo::~AccessTokenInfo() {} + +FakeGaia::FakeGaia() { + base::FilePath source_root_dir; + PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir); + CHECK(base::ReadFileToString( + source_root_dir.Append(base::FilePath(kServiceLogin)), + &service_login_response_)); +} + +FakeGaia::~FakeGaia() {} + +scoped_ptr<HttpResponse> FakeGaia::HandleRequest(const HttpRequest& request) { + GaiaUrls* gaia_urls = GaiaUrls::GetInstance(); + + // The scheme and host of the URL is actually not important but required to + // get a valid GURL in order to parse |request.relative_url|. + GURL request_url = GURL("http://localhost").Resolve(request.relative_url); + std::string request_path = request_url.path(); + + scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse()); + if (request_path == gaia_urls->service_login_url().path()) { + http_response->set_code(net::HTTP_OK); + http_response->set_content(service_login_response_); + http_response->set_content_type("text/html"); + } else if (request_path == gaia_urls->service_login_auth_url().path()) { + std::string continue_url = gaia_urls->service_login_url().spec(); + GetQueryParameter(request.content, "continue", &continue_url); + http_response->set_code(net::HTTP_OK); + const std::string redirect_js = + "document.location.href = '" + continue_url + "'"; + http_response->set_content( + "<HTML><HEAD><SCRIPT>\n" + redirect_js + "\n</SCRIPT></HEAD></HTML>"); + http_response->set_content_type("text/html"); + } else if (request_path == gaia_urls->oauth2_token_url().path()) { + std::string refresh_token; + const AccessTokenInfo* token_info = NULL; + if (GetQueryParameter(request.content, "refresh_token", &refresh_token) && + (token_info = GetAccessTokenInfo(refresh_token))) { + base::DictionaryValue response_dict; + response_dict.SetString("access_token", token_info->token); + response_dict.SetInteger("expires_in", 3600); + FormatJSONResponse(response_dict, http_response.get()); + } else { + http_response->set_code(net::HTTP_BAD_REQUEST); + } + } else if (request_path == gaia_urls->oauth2_token_info_url().path()) { + const AccessTokenInfo* token_info = NULL; + std::string access_token; + if (GetQueryParameter(request.content, "access_token", &access_token)) { + for (AccessTokenInfoMap::const_iterator entry( + access_token_info_map_.begin()); + entry != access_token_info_map_.end(); + ++entry) { + if (entry->second.token == access_token) { + token_info = &(entry->second); + break; + } + } + } + + if (token_info) { + base::DictionaryValue response_dict; + response_dict.SetString("issued_to", token_info->issued_to); + response_dict.SetString("audience", token_info->audience); + response_dict.SetString("user_id", token_info->user_id); + std::vector<std::string> scope_vector(token_info->scopes.begin(), + token_info->scopes.end()); + response_dict.SetString("scope", JoinString(scope_vector, " ")); + response_dict.SetInteger("expires_in", token_info->expires_in); + response_dict.SetString("email", token_info->email); + FormatJSONResponse(response_dict, http_response.get()); + } else { + http_response->set_code(net::HTTP_BAD_REQUEST); + } + } else { + // Request not understood. + return scoped_ptr<HttpResponse>(); + } + + return http_response.PassAs<HttpResponse>(); +} + +void FakeGaia::IssueOAuthToken(const std::string& refresh_token, + const AccessTokenInfo& token_info) { + access_token_info_map_[refresh_token] = token_info; +} + +void FakeGaia::FormatJSONResponse(const base::DictionaryValue& response_dict, + BasicHttpResponse* http_response) { + std::string response_json; + base::JSONWriter::Write(&response_dict, &response_json); + http_response->set_content(response_json); + http_response->set_code(net::HTTP_OK); +} + +const FakeGaia::AccessTokenInfo* FakeGaia::GetAccessTokenInfo( + const std::string& refresh_token) const { + AccessTokenInfoMap::const_iterator entry = + access_token_info_map_.find(refresh_token); + return entry == access_token_info_map_.end() ? NULL : &entry->second; +} + +// static +bool FakeGaia::GetQueryParameter(const std::string& query, + const std::string& key, + std::string* value) { + // Name and scheme actually don't matter, but are required to get a valid URL + // for parsing. + GURL query_url("http://localhost?" + query); + return net::GetValueForKeyInQuery(query_url, key, value); +} diff --git a/chromium/google_apis/gaia/fake_gaia.h b/chromium/google_apis/gaia/fake_gaia.h new file mode 100644 index 00000000000..24f6891c420 --- /dev/null +++ b/chromium/google_apis/gaia/fake_gaia.h @@ -0,0 +1,86 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GOOGLE_APIS_GAIA_FAKE_GAIA_H_ +#define GOOGLE_APIS_GAIA_FAKE_GAIA_H_ + +#include <map> +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace base { +class DictionaryValue; +} + +namespace net { +namespace test_server { +class BasicHttpResponse; +struct HttpRequest; +class HttpResponse; +} +} + +// This is a test helper that implements a fake GAIA service for use in browser +// tests. It's mainly intended for use with EmbeddedTestServer, for which it can +// be registered as an additional request handler. +class FakeGaia { + public: + typedef std::set<std::string> ScopeSet; + + // Access token details used for token minting and the token info endpoint. + struct AccessTokenInfo { + AccessTokenInfo(); + ~AccessTokenInfo(); + + std::string token; + std::string issued_to; + std::string audience; + std::string user_id; + ScopeSet scopes; + int expires_in; + std::string email; + }; + + FakeGaia(); + ~FakeGaia(); + + // Handles a request and returns a response if the request was recognized as a + // GAIA request. Note that this respects the switches::kGaiaUrl and friends so + // that this can used with EmbeddedTestServer::RegisterRequestHandler(). + scoped_ptr<net::test_server::HttpResponse> HandleRequest( + const net::test_server::HttpRequest& request); + + // Configures an OAuth2 token that'll be returned when a client requests an + // access token for the given refresh token. + void IssueOAuthToken(const std::string& refresh_token, + const AccessTokenInfo& token_info); + + private: + typedef std::map<std::string, AccessTokenInfo> AccessTokenInfoMap; + + // Formats a JSON response with the data in |response_dict|. + void FormatJSONResponse(const base::DictionaryValue& response_dict, + net::test_server::BasicHttpResponse* http_response); + + // Returns the access token associated with |refresh_token| or NULL if not + // found. + const AccessTokenInfo* GetAccessTokenInfo( + const std::string& refresh_token) const; + + // Extracts the parameter named |key| from |query| and places it in |value|. + // Returns false if no parameter is found. + static bool GetQueryParameter(const std::string& query, + const std::string& key, + std::string* value); + + AccessTokenInfoMap access_token_info_map_; + std::string service_login_response_; + + DISALLOW_COPY_AND_ASSIGN(FakeGaia); +}; + +#endif // GOOGLE_APIS_GAIA_FAKE_GAIA_H_ diff --git a/chromium/google_apis/gaia/gaia_auth_fetcher.cc b/chromium/google_apis/gaia/gaia_auth_fetcher.cc index 67756deb99b..f0e26445a52 100644 --- a/chromium/google_apis/gaia/gaia_auth_fetcher.cc +++ b/chromium/google_apis/gaia/gaia_auth_fetcher.cc @@ -103,7 +103,7 @@ const char GaiaAuthFetcher::kMergeSessionFormat[] = "source=%s"; // static const char GaiaAuthFetcher::kUberAuthTokenURLFormat[] = - "%s?source=%s&" + "?source=%s&" "issueuberauth=1"; const char GaiaAuthFetcher::kOAuthLoginFormat[] = "service=%s&source=%s"; @@ -179,8 +179,8 @@ GaiaAuthFetcher::GaiaAuthFetcher(GaiaAuthConsumer* consumer, oauth2_revoke_gurl_(GaiaUrls::GetInstance()->oauth2_revoke_url()), get_user_info_gurl_(GaiaUrls::GetInstance()->get_user_info_url()), merge_session_gurl_(GaiaUrls::GetInstance()->merge_session_url()), - uberauth_token_gurl_(base::StringPrintf(kUberAuthTokenURLFormat, - GaiaUrls::GetInstance()->oauth1_login_url().c_str(), source.c_str())), + uberauth_token_gurl_(GaiaUrls::GetInstance()->oauth1_login_url().Resolve( + base::StringPrintf(kUberAuthTokenURLFormat, source.c_str()))), oauth_login_gurl_(GaiaUrls::GetInstance()->oauth1_login_url()), client_login_to_oauth2_gurl_( GaiaUrls::GetInstance()->client_login_to_oauth2_url()), @@ -517,7 +517,7 @@ void GaiaAuthFetcher::StartLsoForOAuthLoginTokenExchange( DVLOG(1) << "Starting OAuth login token exchange with auth_token"; request_body_ = MakeGetAuthCodeBody(); client_login_to_oauth2_gurl_ = - GURL(GaiaUrls::GetInstance()->client_login_to_oauth2_url()); + GaiaUrls::GetInstance()->client_login_to_oauth2_url(); fetcher_.reset(CreateGaiaFetcher(getter_, request_body_, @@ -551,11 +551,12 @@ void GaiaAuthFetcher::StartCookieForOAuthLoginTokenExchange( DVLOG(1) << "Starting OAuth login token fetch with cookie jar"; request_body_ = MakeGetAuthCodeBody(); - std::string url = GaiaUrls::GetInstance()->client_login_to_oauth2_url(); - if (!session_index.empty()) - url += "?authuser=" + session_index; - - client_login_to_oauth2_gurl_ = GURL(url); + client_login_to_oauth2_gurl_ = + GaiaUrls::GetInstance()->client_login_to_oauth2_url(); + if (!session_index.empty()) { + client_login_to_oauth2_gurl_ = + client_login_to_oauth2_gurl_.Resolve("?authuser=" + session_index); + } fetcher_.reset(CreateGaiaFetcher(getter_, request_body_, @@ -684,7 +685,7 @@ GoogleServiceAuthError GaiaAuthFetcher::GenerateAuthError( if (error == kCaptchaError) { GURL image_url( - GaiaUrls::GetInstance()->captcha_url_prefix() + captcha_url); + GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url)); GURL unlock_url(url); return GoogleServiceAuthError::FromClientLoginCaptchaChallenge( captcha_token, image_url, unlock_url); @@ -737,7 +738,7 @@ GoogleServiceAuthError GaiaAuthFetcher::GenerateOAuthLoginError( if (error == kCaptchaErrorCode) { GURL image_url( - GaiaUrls::GetInstance()->captcha_url_prefix() + captcha_url); + GaiaUrls::GetInstance()->captcha_base_url().Resolve(captcha_url)); GURL unlock_url(url); return GoogleServiceAuthError::FromClientLoginCaptchaChallenge( captcha_token, image_url, unlock_url); diff --git a/chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc b/chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc index 74fed98f47c..f554b5ed855 100644 --- a/chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc +++ b/chromium/google_apis/gaia/gaia_auth_fetcher_unittest.cc @@ -27,8 +27,8 @@ #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" -using ::testing::_; using ::testing::Invoke; +using ::testing::_; namespace { static const char kGetAuthCodeValidCookie[] = @@ -111,9 +111,9 @@ class GaiaAuthFetcherTest : public testing::Test { oauth2_token_source_(GaiaUrls::GetInstance()->oauth2_token_url()), token_auth_source_(GaiaUrls::GetInstance()->token_auth_url()), merge_session_source_(GaiaUrls::GetInstance()->merge_session_url()), - uberauth_token_source_(base::StringPrintf( - "%s?source=&issueuberauth=1", - GaiaUrls::GetInstance()->oauth1_login_url().c_str())), + uberauth_token_source_( + GaiaUrls::GetInstance()->oauth1_login_url().Resolve( + "?source=&issueuberauth=1")), oauth_login_gurl_(GaiaUrls::GetInstance()->oauth1_login_url()) {} void RunParsingTest(const std::string& data, diff --git a/chromium/google_apis/gaia/gaia_constants.cc b/chromium/google_apis/gaia/gaia_constants.cc index d1d932fb037..1b1581d1545 100644 --- a/chromium/google_apis/gaia/gaia_constants.cc +++ b/chromium/google_apis/gaia/gaia_constants.cc @@ -40,9 +40,6 @@ const char kChromeSyncManagedOAuth2Scope[] = const char kGoogleTalkOAuth2Scope[] = "https://www.googleapis.com/auth/googletalk"; -// Service for LSO endpoint of Google that exposes OAuth APIs. -const char kLSOService[] = "lso"; - // Used to mint uber auth tokens when needed. const char kGaiaSid[] = "sid"; const char kGaiaLsid[] = "lsid"; diff --git a/chromium/google_apis/gaia/gaia_constants.h b/chromium/google_apis/gaia/gaia_constants.h index 72cae741b9b..3b45eee0dcd 100644 --- a/chromium/google_apis/gaia/gaia_constants.h +++ b/chromium/google_apis/gaia/gaia_constants.h @@ -23,7 +23,6 @@ extern const char kAnyApiOAuth2Scope[]; extern const char kChromeSyncOAuth2Scope[]; extern const char kChromeSyncManagedOAuth2Scope[]; extern const char kGoogleTalkOAuth2Scope[]; -extern const char kLSOService[]; // Used with uber auth tokens when needed. extern const char kGaiaSid[]; diff --git a/chromium/google_apis/gaia/gaia_switches.cc b/chromium/google_apis/gaia/gaia_switches.cc index c2430b0b737..16228c3837b 100644 --- a/chromium/google_apis/gaia/gaia_switches.cc +++ b/chromium/google_apis/gaia/gaia_switches.cc @@ -8,11 +8,10 @@ namespace switches { const char kClientLoginToOAuth2Url[] = "client-login-to-oauth2-url"; const char kGaiaUrl[] = "gaia-url"; -const char kGoogleApisHost[] = "google-apis-host"; +const char kGoogleApisUrl[] = "google-apis-url"; const char kLsoUrl[] = "lso-url"; const char kOAuth1LoginScope[] = "oauth1-login-scope"; -const char kOAuth2IssueTokenUrl[] = "oauth2-issue-token-url"; -const char kOAuth2TokenUrl[] = "oauth2-token-url"; -const char kOAuthUserInfoUrl[] = "oauth-user-info-url"; +const char kOAuthWrapBridgeUserInfoScope[] = + "oauth-wrap-bridge-user-info-scope"; } // namespace switches diff --git a/chromium/google_apis/gaia/gaia_switches.h b/chromium/google_apis/gaia/gaia_switches.h index 727a35557a9..0700f36c70c 100644 --- a/chromium/google_apis/gaia/gaia_switches.h +++ b/chromium/google_apis/gaia/gaia_switches.h @@ -14,29 +14,20 @@ extern const char kClientLoginToOAuth2Url[]; // "https://accounts.google.com". extern const char kGaiaUrl[]; -// Specifies the backend server used for Google API calls. The https:// prefix -// and the trailing slash should be omitted. -// The default value is "www.googleapis.com". -extern const char kGoogleApisHost[]; +// Specifies the backend server used for Google API calls. +// The default value is "https://www.googleapis.com". +extern const char kGoogleApisUrl[]; // Specifies the backend server used for lso authentication calls. // "https://accounts.google.com". extern const char kLsoUrl[]; -// TODO(zelidrag): Get rid of all following since all URLs should be -// controlled only with --gaia-host, --lso-host and --google-apis-host. - // Specifies custom OAuth1 login scope for testing purposes. extern const char kOAuth1LoginScope[]; -// Specifies custom OAuth2 issue token URL for testing purposes. -extern const char kOAuth2IssueTokenUrl[]; - -// Specifies custom OAuth2 token URL for testing purposes. -extern const char kOAuth2TokenUrl[]; +// Overrides OAuth wrap bridge user info scope. +extern const char kOAuthWrapBridgeUserInfoScope[]; -// Specifies custom OAuth user info URL for testing purposes. -extern const char kOAuthUserInfoUrl[]; } // namespace switches #endif // GOOGLE_APIS_GAIA_GAIA_SWITCHES_H_ diff --git a/chromium/google_apis/gaia/gaia_urls.cc b/chromium/google_apis/gaia/gaia_urls.cc index 578205cb09b..e5e5b8f32d0 100644 --- a/chromium/google_apis/gaia/gaia_urls.cc +++ b/chromium/google_apis/gaia/gaia_urls.cc @@ -13,38 +13,39 @@ namespace { // Gaia service constants const char kDefaultGaiaUrl[] = "https://accounts.google.com"; -const char kDefaultGoogleApisBaseUrl[] = "www.googleapis.com"; -const char kCaptchaUrlPrefixSuffix[] = "/"; +const char kDefaultGoogleApisBaseUrl[] = "https://www.googleapis.com"; // API calls from accounts.google.com -const char kClientLoginUrlSuffix[] = "/ClientLogin"; -const char kServiceLoginUrlSuffix[] = "/ServiceLogin"; -const char kServiceLogoutUrlSuffix[] = "/Logout"; -const char kIssueAuthTokenUrlSuffix[] = "/IssueAuthToken"; -const char kGetUserInfoUrlSuffix[] = "/GetUserInfo"; -const char kTokenAuthUrlSuffix[] = "/TokenAuth"; -const char kMergeSessionUrlSuffix[] = "/MergeSession"; -const char kOAuthGetAccessTokenUrlSuffix[] = "/OAuthGetAccessToken"; -const char kOAuthWrapBridgeUrlSuffix[] = "/OAuthWrapBridge"; -const char kOAuth1LoginUrlSuffix[] = "/OAuthLogin"; -const char kOAuthRevokeTokenUrlSuffix[] = "/AuthSubRevokeToken"; +const char kClientLoginUrlSuffix[] = "ClientLogin"; +const char kServiceLoginUrlSuffix[] = "ServiceLogin"; +const char kServiceLoginAuthUrlSuffix[] = "ServiceLoginAuth"; +const char kServiceLogoutUrlSuffix[] = "Logout"; +const char kIssueAuthTokenUrlSuffix[] = "IssueAuthToken"; +const char kGetUserInfoUrlSuffix[] = "GetUserInfo"; +const char kTokenAuthUrlSuffix[] = "TokenAuth"; +const char kMergeSessionUrlSuffix[] = "MergeSession"; +const char kOAuthGetAccessTokenUrlSuffix[] = "OAuthGetAccessToken"; +const char kOAuthWrapBridgeUrlSuffix[] = "OAuthWrapBridge"; +const char kOAuth1LoginUrlSuffix[] = "OAuthLogin"; +const char kOAuthRevokeTokenUrlSuffix[] = "AuthSubRevokeToken"; + +// OAuth scopes +const char kOAuth1LoginScope[] = "https://www.google.com/accounts/OAuthLogin"; +const char kOAuthWrapBridgeUserInfoScope[] = + "https://www.googleapis.com/auth/userinfo.email"; // API calls from accounts.google.com (LSO) -const char kGetOAuthTokenUrlSuffix[] = "/o/oauth/GetOAuthToken/"; -const char kClientLoginToOAuth2UrlSuffix[] = "/o/oauth2/programmatic_auth"; -const char kOAuth2AuthUrlSuffix[] = "/o/oauth2/auth"; -const char kOAuth2RevokeUrlSuffix[] = "/o/oauth2/revoke"; -const char kOAuth2TokenUrlSuffix[] = "/o/oauth2/token"; -const char kClientOAuthUrlSuffix[] = "/ClientOAuth"; +const char kGetOAuthTokenUrlSuffix[] = "o/oauth/GetOAuthToken/"; +const char kClientLoginToOAuth2UrlSuffix[] = "o/oauth2/programmatic_auth"; +const char kOAuth2AuthUrlSuffix[] = "o/oauth2/auth"; +const char kOAuth2RevokeUrlSuffix[] = "o/oauth2/revoke"; +const char kOAuth2TokenUrlSuffix[] = "o/oauth2/token"; +const char kClientOAuthUrlSuffix[] = "ClientOAuth"; // API calls from www.googleapis.com -const char kOAuth2IssueTokenUrlSuffix[] = "/oauth2/v2/IssueToken"; -const char kOAuth2TokenInfoUrlSuffix[] = "/oauth2/v2/tokeninfo"; -const char kOAuthUserInfoUrlSuffix[] = "/oauth2/v1/userinfo"; -const char kOAuthWrapBridgeUserInfoScopeUrlSuffix[] = "/auth/userinfo.email"; - -const char kOAuth1LoginScope[] = - "https://www.google.com/accounts/OAuthLogin"; +const char kOAuth2IssueTokenUrlSuffix[] = "oauth2/v2/IssueToken"; +const char kOAuth2TokenInfoUrlSuffix[] = "oauth2/v2/tokeninfo"; +const char kOAuthUserInfoUrlSuffix[] = "oauth2/v1/userinfo"; void GetSwitchValueWithDefault(const char* switch_value, const char* default_value, @@ -57,6 +58,16 @@ void GetSwitchValueWithDefault(const char* switch_value, } } +GURL GetURLSwitchValueWithDefault(const char* switch_value, + const char* default_value) { + std::string string_value; + GetSwitchValueWithDefault(switch_value, default_value, &string_value); + const GURL result(string_value); + DCHECK(result.is_valid()); + return result; +} + + } // namespace GaiaUrls* GaiaUrls::GetInstance() { @@ -64,27 +75,15 @@ GaiaUrls* GaiaUrls::GetInstance() { } GaiaUrls::GaiaUrls() { - std::string gaia_url_str; - GetSwitchValueWithDefault(switches::kGaiaUrl, - kDefaultGaiaUrl, - &gaia_url_str); - gaia_url_ = GURL(gaia_url_str); - DCHECK(gaia_url_.is_valid()); - - GetSwitchValueWithDefault(switches::kLsoUrl, - kDefaultGaiaUrl, - &lso_origin_url_); - - std::string google_apis_base; - GetSwitchValueWithDefault(switches::kGoogleApisHost, - kDefaultGoogleApisBaseUrl, - &google_apis_base); - - captcha_url_prefix_ = "http://" + gaia_url_.host() + - (gaia_url_.has_port() ? ":" + gaia_url_.port() : "") + - kCaptchaUrlPrefixSuffix; + gaia_url_ = GetURLSwitchValueWithDefault(switches::kGaiaUrl, kDefaultGaiaUrl); + lso_origin_url_ = + GetURLSwitchValueWithDefault(switches::kLsoUrl, kDefaultGaiaUrl); + google_apis_origin_url_ = GetURLSwitchValueWithDefault( + switches::kGoogleApisUrl, kDefaultGoogleApisBaseUrl); - google_apis_origin_url_ = "https://" + google_apis_base; + captcha_base_url_ = + GURL("http://" + gaia_url_.host() + + (gaia_url_.has_port() ? ":" + gaia_url_.port() : "")); oauth2_chrome_client_id_ = google_apis::GetOAuth2ClientID(google_apis::CLIENT_MAIN); @@ -92,115 +91,111 @@ GaiaUrls::GaiaUrls() { google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_MAIN); // URLs from accounts.google.com. - gaia_login_form_realm_ = gaia_url_str + "/"; - client_login_url_ = gaia_url_str + kClientLoginUrlSuffix; - service_login_url_ = gaia_url_str + kServiceLoginUrlSuffix; - service_logout_url_ = gaia_url_str + kServiceLogoutUrlSuffix; - issue_auth_token_url_ = gaia_url_str + kIssueAuthTokenUrlSuffix; - get_user_info_url_ = gaia_url_str + kGetUserInfoUrlSuffix; - token_auth_url_ = gaia_url_str + kTokenAuthUrlSuffix; - merge_session_url_ = gaia_url_str + kMergeSessionUrlSuffix; - oauth_get_access_token_url_ = gaia_url_str + kOAuthGetAccessTokenUrlSuffix; - oauth_wrap_bridge_url_ = gaia_url_str + kOAuthWrapBridgeUrlSuffix; - oauth_revoke_token_url_ = gaia_url_str + kOAuthRevokeTokenUrlSuffix; - oauth1_login_url_ = gaia_url_str + kOAuth1LoginUrlSuffix; + client_login_url_ = gaia_url_.Resolve(kClientLoginUrlSuffix); + service_login_url_ = gaia_url_.Resolve(kServiceLoginUrlSuffix); + service_login_auth_url_ = gaia_url_.Resolve(kServiceLoginAuthUrlSuffix); + service_logout_url_ = gaia_url_.Resolve(kServiceLogoutUrlSuffix); + issue_auth_token_url_ = gaia_url_.Resolve(kIssueAuthTokenUrlSuffix); + get_user_info_url_ = gaia_url_.Resolve(kGetUserInfoUrlSuffix); + token_auth_url_ = gaia_url_.Resolve(kTokenAuthUrlSuffix); + merge_session_url_ = gaia_url_.Resolve(kMergeSessionUrlSuffix); + oauth_get_access_token_url_ = + gaia_url_.Resolve(kOAuthGetAccessTokenUrlSuffix); + oauth_wrap_bridge_url_ = gaia_url_.Resolve(kOAuthWrapBridgeUrlSuffix); + oauth_revoke_token_url_ = gaia_url_.Resolve(kOAuthRevokeTokenUrlSuffix); + oauth1_login_url_ = gaia_url_.Resolve(kOAuth1LoginUrlSuffix); // URLs from accounts.google.com (LSO). - get_oauth_token_url_ = lso_origin_url_ + kGetOAuthTokenUrlSuffix; - std::string client_login_to_oauth2_url = lso_origin_url_ + - kClientLoginToOAuth2UrlSuffix; - oauth2_auth_url_ = lso_origin_url_ + kOAuth2AuthUrlSuffix; - std::string oauth2_token_url = lso_origin_url_ + kOAuth2TokenUrlSuffix; - oauth2_revoke_url_ = lso_origin_url_ + kOAuth2RevokeUrlSuffix; + get_oauth_token_url_ = lso_origin_url_.Resolve(kGetOAuthTokenUrlSuffix); + client_login_to_oauth2_url_ = + lso_origin_url_.Resolve(kClientLoginToOAuth2UrlSuffix); + oauth2_auth_url_ = lso_origin_url_.Resolve(kOAuth2AuthUrlSuffix); + oauth2_token_url_ = lso_origin_url_.Resolve(kOAuth2TokenUrlSuffix); + oauth2_revoke_url_ = lso_origin_url_.Resolve(kOAuth2RevokeUrlSuffix); // URLs from www.googleapis.com. - oauth_wrap_bridge_user_info_scope_ = google_apis_origin_url_ + - kOAuthWrapBridgeUserInfoScopeUrlSuffix; - std::string oauth2_issue_token_url = google_apis_origin_url_ + - kOAuth2IssueTokenUrlSuffix; - oauth2_token_info_url_ = google_apis_origin_url_ + kOAuth2TokenInfoUrlSuffix; - std::string oauth_user_info_url = google_apis_origin_url_ + - kOAuthUserInfoUrlSuffix; - - // TODO(zelidrag): Get rid of all these switches since all URLs should be - // controlled only with --gaia-url, --lso-url and --google-apis-host. + oauth2_issue_token_url_ = + google_apis_origin_url_.Resolve(kOAuth2IssueTokenUrlSuffix); + oauth2_token_info_url_ = + google_apis_origin_url_.Resolve(kOAuth2TokenInfoUrlSuffix); + oauth_user_info_url_ = + google_apis_origin_url_.Resolve(kOAuthUserInfoUrlSuffix); + + gaia_login_form_realm_ = gaia_url_; + + // OAuth scopes. + GetSwitchValueWithDefault(switches::kOAuthWrapBridgeUserInfoScope, + kOAuthWrapBridgeUserInfoScope, + &oauth_wrap_bridge_user_info_scope_); GetSwitchValueWithDefault(switches::kOAuth1LoginScope, kOAuth1LoginScope, &oauth1_login_scope_); - GetSwitchValueWithDefault(switches::kClientLoginToOAuth2Url, - client_login_to_oauth2_url.c_str(), - &client_login_to_oauth2_url_); - GetSwitchValueWithDefault(switches::kOAuth2TokenUrl, - oauth2_token_url.c_str(), - &oauth2_token_url_); - GetSwitchValueWithDefault(switches::kOAuth2IssueTokenUrl, - oauth2_issue_token_url.c_str(), - &oauth2_issue_token_url_); - GetSwitchValueWithDefault(switches::kOAuthUserInfoUrl, - oauth_user_info_url.c_str(), - &oauth_user_info_url_); } GaiaUrls::~GaiaUrls() { } -const std::string& GaiaUrls::captcha_url_prefix() const { - return captcha_url_prefix_; -} - const GURL& GaiaUrls::gaia_url() const { return gaia_url_; } -const std::string& GaiaUrls::client_login_url() const { +const GURL& GaiaUrls::captcha_base_url() const { + return captcha_base_url_; +} + +const GURL& GaiaUrls::client_login_url() const { return client_login_url_; } -const std::string& GaiaUrls::service_login_url() const { +const GURL& GaiaUrls::service_login_url() const { return service_login_url_; } -const std::string& GaiaUrls::service_logout_url() const { +const GURL& GaiaUrls::service_login_auth_url() const { + return service_login_auth_url_; +} + +const GURL& GaiaUrls::service_logout_url() const { return service_logout_url_; } -const std::string& GaiaUrls::issue_auth_token_url() const { +const GURL& GaiaUrls::issue_auth_token_url() const { return issue_auth_token_url_; } -const std::string& GaiaUrls::get_user_info_url() const { +const GURL& GaiaUrls::get_user_info_url() const { return get_user_info_url_; } -const std::string& GaiaUrls::token_auth_url() const { +const GURL& GaiaUrls::token_auth_url() const { return token_auth_url_; } -const std::string& GaiaUrls::merge_session_url() const { +const GURL& GaiaUrls::merge_session_url() const { return merge_session_url_; } -const std::string& GaiaUrls::get_oauth_token_url() const { +const GURL& GaiaUrls::get_oauth_token_url() const { return get_oauth_token_url_; } -const std::string& GaiaUrls::oauth_get_access_token_url() const { +const GURL& GaiaUrls::oauth_get_access_token_url() const { return oauth_get_access_token_url_; } -const std::string& GaiaUrls::oauth_wrap_bridge_url() const { +const GURL& GaiaUrls::oauth_wrap_bridge_url() const { return oauth_wrap_bridge_url_; } -const std::string& GaiaUrls::oauth_user_info_url() const { +const GURL& GaiaUrls::oauth_user_info_url() const { return oauth_user_info_url_; } -const std::string& GaiaUrls::oauth_revoke_token_url() const { +const GURL& GaiaUrls::oauth_revoke_token_url() const { return oauth_revoke_token_url_; } -const std::string& GaiaUrls::oauth1_login_url() const { +const GURL& GaiaUrls::oauth1_login_url() const { return oauth1_login_url_; } @@ -220,30 +215,30 @@ const std::string& GaiaUrls::oauth2_chrome_client_secret() const { return oauth2_chrome_client_secret_; } -const std::string& GaiaUrls::client_login_to_oauth2_url() const { +const GURL& GaiaUrls::client_login_to_oauth2_url() const { return client_login_to_oauth2_url_; } -const std::string& GaiaUrls::oauth2_auth_url() const { +const GURL& GaiaUrls::oauth2_auth_url() const { return oauth2_auth_url_; } -const std::string& GaiaUrls::oauth2_token_url() const { +const GURL& GaiaUrls::oauth2_token_url() const { return oauth2_token_url_; } -const std::string& GaiaUrls::oauth2_issue_token_url() const { +const GURL& GaiaUrls::oauth2_issue_token_url() const { return oauth2_issue_token_url_; } -const std::string& GaiaUrls::oauth2_token_info_url() const { +const GURL& GaiaUrls::oauth2_token_info_url() const { return oauth2_token_info_url_; } -const std::string& GaiaUrls::oauth2_revoke_url() const { +const GURL& GaiaUrls::oauth2_revoke_url() const { return oauth2_revoke_url_; } -const std::string& GaiaUrls::gaia_login_form_realm() const { - return gaia_login_form_realm_; +const GURL& GaiaUrls::gaia_login_form_realm() const { + return gaia_url_; } diff --git a/chromium/google_apis/gaia/gaia_urls.h b/chromium/google_apis/gaia/gaia_urls.h index 28c72792f0b..e06b95d11e1 100644 --- a/chromium/google_apis/gaia/gaia_urls.h +++ b/chromium/google_apis/gaia/gaia_urls.h @@ -16,36 +16,36 @@ class GaiaUrls { static GaiaUrls* GetInstance(); // The URLs for different calls in the Google Accounts programmatic login API. - const std::string& captcha_url_prefix() const; - const GURL& gaia_url() const; - const std::string& client_login_url() const; - const std::string& service_login_url() const; - const std::string& service_logout_url() const; - const std::string& issue_auth_token_url() const; - const std::string& get_user_info_url() const; - const std::string& token_auth_url() const; - const std::string& merge_session_url() const; - const std::string& get_oauth_token_url() const; - const std::string& oauth_get_access_token_url() const; - const std::string& oauth_wrap_bridge_url() const; - const std::string& oauth_user_info_url() const; - const std::string& oauth_revoke_token_url() const; - const std::string& oauth1_login_url() const; + const GURL& captcha_base_url() const; + const GURL& client_login_url() const; + const GURL& service_login_url() const; + const GURL& service_login_auth_url() const; + const GURL& service_logout_url() const; + const GURL& issue_auth_token_url() const; + const GURL& get_user_info_url() const; + const GURL& token_auth_url() const; + const GURL& merge_session_url() const; + const GURL& get_oauth_token_url() const; + const GURL& oauth_get_access_token_url() const; + const GURL& oauth_wrap_bridge_url() const; + const GURL& oauth_user_info_url() const; + const GURL& oauth_revoke_token_url() const; + const GURL& oauth1_login_url() const; const std::string& oauth1_login_scope() const; const std::string& oauth_wrap_bridge_user_info_scope() const; const std::string& oauth2_chrome_client_id() const; const std::string& oauth2_chrome_client_secret() const; - const std::string& client_login_to_oauth2_url() const; - const std::string& oauth2_auth_url() const; - const std::string& oauth2_token_url() const; - const std::string& oauth2_issue_token_url() const; - const std::string& oauth2_token_info_url() const; - const std::string& oauth2_revoke_url() const; + const GURL& client_login_to_oauth2_url() const; + const GURL& oauth2_auth_url() const; + const GURL& oauth2_token_url() const; + const GURL& oauth2_issue_token_url() const; + const GURL& oauth2_token_info_url() const; + const GURL& oauth2_revoke_url() const; - const std::string& gaia_login_form_realm() const; + const GURL& gaia_login_form_realm() const; private: GaiaUrls(); @@ -53,38 +53,41 @@ class GaiaUrls { friend struct DefaultSingletonTraits<GaiaUrls>; - std::string captcha_url_prefix_; - GURL gaia_url_; - std::string lso_origin_url_; - std::string google_apis_origin_url_; - std::string client_login_url_; - std::string service_login_url_; - std::string service_logout_url_; - std::string issue_auth_token_url_; - std::string get_user_info_url_; - std::string token_auth_url_; - std::string merge_session_url_; - std::string get_oauth_token_url_; - std::string oauth_get_access_token_url_; - std::string oauth_wrap_bridge_url_; - std::string oauth_user_info_url_; - std::string oauth_revoke_token_url_; - std::string oauth1_login_url_; + GURL captcha_base_url_; + + GURL lso_origin_url_; + GURL google_apis_origin_url_; + + GURL client_login_url_; + GURL service_login_url_; + GURL service_login_auth_url_; + GURL service_logout_url_; + GURL issue_auth_token_url_; + GURL get_user_info_url_; + GURL token_auth_url_; + GURL merge_session_url_; + GURL get_oauth_token_url_; + GURL oauth_get_access_token_url_; + GURL oauth_wrap_bridge_url_; + GURL oauth_user_info_url_; + GURL oauth_revoke_token_url_; + GURL oauth1_login_url_; std::string oauth1_login_scope_; std::string oauth_wrap_bridge_user_info_scope_; std::string oauth2_chrome_client_id_; std::string oauth2_chrome_client_secret_; - std::string client_login_to_oauth2_url_; - std::string oauth2_auth_url_; - std::string oauth2_token_url_; - std::string oauth2_issue_token_url_; - std::string oauth2_token_info_url_; - std::string oauth2_revoke_url_; - - std::string gaia_login_form_realm_; + + GURL client_login_to_oauth2_url_; + GURL oauth2_auth_url_; + GURL oauth2_token_url_; + GURL oauth2_issue_token_url_; + GURL oauth2_token_info_url_; + GURL oauth2_revoke_url_; + + GURL gaia_login_form_realm_; DISALLOW_COPY_AND_ASSIGN(GaiaUrls); }; diff --git a/chromium/google_apis/gaia/google_service_auth_error.h b/chromium/google_apis/gaia/google_service_auth_error.h index a99e515f31d..19fdb515405 100644 --- a/chromium/google_apis/gaia/google_service_auth_error.h +++ b/chromium/google_apis/gaia/google_service_auth_error.h @@ -33,8 +33,8 @@ class DictionaryValue; class GoogleServiceAuthError { public: // - // These enumerations are referenced by integer value in HTML login code. - // Do not change the numeric values. + // These enumerations are referenced by integer value in HTML login code and + // in UMA histograms. Do not change the numeric values. // enum State { // The user is authenticated. diff --git a/chromium/google_apis/gaia/oauth2_access_token_fetcher.cc b/chromium/google_apis/gaia/oauth2_access_token_fetcher.cc index 456251f0134..44f2d4a7795 100644 --- a/chromium/google_apis/gaia/oauth2_access_token_fetcher.cc +++ b/chromium/google_apis/gaia/oauth2_access_token_fetcher.cc @@ -182,7 +182,7 @@ void OAuth2AccessTokenFetcher::OnURLFetchComplete( // static GURL OAuth2AccessTokenFetcher::MakeGetAccessTokenUrl() { - return GURL(GaiaUrls::GetInstance()->oauth2_token_url()); + return GaiaUrls::GetInstance()->oauth2_token_url(); } // static diff --git a/chromium/google_apis/gaia/oauth2_mint_token_flow.cc b/chromium/google_apis/gaia/oauth2_mint_token_flow.cc index f66e0fb11d2..b705ab872b0 100644 --- a/chromium/google_apis/gaia/oauth2_mint_token_flow.cc +++ b/chromium/google_apis/gaia/oauth2_mint_token_flow.cc @@ -151,7 +151,7 @@ void OAuth2MintTokenFlow::ReportFailure( } GURL OAuth2MintTokenFlow::CreateApiCallUrl() { - return GURL(GaiaUrls::GetInstance()->oauth2_issue_token_url()); + return GaiaUrls::GetInstance()->oauth2_issue_token_url(); } std::string OAuth2MintTokenFlow::CreateApiCallBody() { diff --git a/chromium/google_apis/gaia/oauth2_token_service.cc b/chromium/google_apis/gaia/oauth2_token_service.cc new file mode 100644 index 00000000000..3259e283b64 --- /dev/null +++ b/chromium/google_apis/gaia/oauth2_token_service.cc @@ -0,0 +1,737 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "google_apis/gaia/oauth2_token_service.h" + +#include <vector> + +#include "base/bind.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/rand_util.h" +#include "base/stl_util.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "google_apis/gaia/gaia_urls.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "net/url_request/url_request_context_getter.h" + +int OAuth2TokenService::max_fetch_retry_num_ = 5; + +OAuth2TokenService::RequestParameters::RequestParameters( + const std::string& client_id, + const std::string& account_id, + const ScopeSet& scopes) + : client_id(client_id), + account_id(account_id), + scopes(scopes) { +} + +OAuth2TokenService::RequestParameters::~RequestParameters() { +} + +bool OAuth2TokenService::RequestParameters::operator<( + const RequestParameters& p) const { + if (client_id < p.client_id) + return true; + else if (p.client_id < client_id) + return false; + + if (account_id < p.account_id) + return true; + else if (p.account_id < account_id) + return false; + + return scopes < p.scopes; +} + +OAuth2TokenService::RequestImpl::RequestImpl( + OAuth2TokenService::Consumer* consumer) + : consumer_(consumer) { +} + +OAuth2TokenService::RequestImpl::~RequestImpl() { + DCHECK(CalledOnValidThread()); +} + +void OAuth2TokenService::RequestImpl::InformConsumer( + const GoogleServiceAuthError& error, + const std::string& access_token, + const base::Time& expiration_date) { + DCHECK(CalledOnValidThread()); + if (error.state() == GoogleServiceAuthError::NONE) + consumer_->OnGetTokenSuccess(this, access_token, expiration_date); + else + consumer_->OnGetTokenFailure(this, error); +} + +// Class that fetches an OAuth2 access token for a given set of scopes and +// OAuth2 refresh token. + +// Class that fetches OAuth2 access tokens for given scopes and refresh token. +// +// It aims to meet OAuth2TokenService's requirements on token fetching. Retry +// mechanism is used to handle failures. +// +// To use this class, call CreateAndStart() to create and start a Fetcher. +// +// The Fetcher will call back the service by calling +// OAuth2TokenService::OnFetchComplete() when it completes fetching, if it is +// not destructed before it completes fetching; if the Fetcher is destructed +// before it completes fetching, the service will never be called back. The +// Fetcher destructs itself after calling back the service when finishes +// fetching. +// +// Requests that are waiting for the fetching results of this Fetcher can be +// added to the Fetcher by calling +// OAuth2TokenService::Fetcher::AddWaitingRequest() before the Fetcher +// completes fetching. +// +// The waiting requests are taken as weak pointers and they can be deleted. +// The waiting requests will be called back with fetching results if they are +// not deleted +// - when the Fetcher completes fetching, if the Fetcher is not destructed +// before it completes fetching, or +// - when the Fetcher is destructed if the Fetcher is destructed before it +// completes fetching (in this case, the waiting requests will be called +// back with error). +class OAuth2TokenService::Fetcher : public OAuth2AccessTokenConsumer { + public: + // Creates a Fetcher and starts fetching an OAuth2 access token for + // |refresh_token| and |scopes| in the request context obtained by |getter|. + // The given |oauth2_token_service| will be informed when fetching is done. + static Fetcher* CreateAndStart(OAuth2TokenService* oauth2_token_service, + const std::string& account_id, + net::URLRequestContextGetter* getter, + const std::string& client_id, + const std::string& client_secret, + const std::string& refresh_token, + const ScopeSet& scopes, + base::WeakPtr<RequestImpl> waiting_request); + virtual ~Fetcher(); + + // Add a request that is waiting for the result of this Fetcher. + void AddWaitingRequest(base::WeakPtr<RequestImpl> waiting_request); + + // Returns count of waiting requests. + size_t GetWaitingRequestCount() const; + + void Cancel(); + + const ScopeSet& GetScopeSet() const; + const std::string& GetRefreshToken() const; + const std::string& GetClientId() const; + const std::string& GetAccountId() const; + + // The error result from this fetcher. + const GoogleServiceAuthError& error() const { return error_; } + + protected: + // OAuth2AccessTokenConsumer + virtual void OnGetTokenSuccess(const std::string& access_token, + const base::Time& expiration_date) OVERRIDE; + virtual void OnGetTokenFailure( + const GoogleServiceAuthError& error) OVERRIDE; + + private: + Fetcher(OAuth2TokenService* oauth2_token_service, + const std::string& account_id, + net::URLRequestContextGetter* getter, + const std::string& client_id, + const std::string& client_secret, + const std::string& refresh_token, + const OAuth2TokenService::ScopeSet& scopes, + base::WeakPtr<RequestImpl> waiting_request); + void Start(); + void InformWaitingRequests(); + void InformWaitingRequestsAndDelete(); + static bool ShouldRetry(const GoogleServiceAuthError& error); + int64 ComputeExponentialBackOffMilliseconds(int retry_num); + + // |oauth2_token_service_| remains valid for the life of this Fetcher, since + // this Fetcher is destructed in the dtor of the OAuth2TokenService or is + // scheduled for deletion at the end of OnGetTokenFailure/OnGetTokenSuccess + // (whichever comes first). + OAuth2TokenService* const oauth2_token_service_; + scoped_refptr<net::URLRequestContextGetter> getter_; + const std::string account_id_; + const std::string refresh_token_; + const ScopeSet scopes_; + std::vector<base::WeakPtr<RequestImpl> > waiting_requests_; + + int retry_number_; + base::OneShotTimer<Fetcher> retry_timer_; + scoped_ptr<OAuth2AccessTokenFetcher> fetcher_; + + // Variables that store fetch results. + // Initialized to be GoogleServiceAuthError::SERVICE_UNAVAILABLE to handle + // destruction. + GoogleServiceAuthError error_; + std::string access_token_; + base::Time expiration_date_; + + // OAuth2 client id and secret. + std::string client_id_; + std::string client_secret_; + + DISALLOW_COPY_AND_ASSIGN(Fetcher); +}; + +// static +OAuth2TokenService::Fetcher* OAuth2TokenService::Fetcher::CreateAndStart( + OAuth2TokenService* oauth2_token_service, + const std::string& account_id, + net::URLRequestContextGetter* getter, + const std::string& client_id, + const std::string& client_secret, + const std::string& refresh_token, + const OAuth2TokenService::ScopeSet& scopes, + base::WeakPtr<RequestImpl> waiting_request) { + OAuth2TokenService::Fetcher* fetcher = new Fetcher( + oauth2_token_service, + account_id, + getter, + client_id, + client_secret, + refresh_token, + scopes, + waiting_request); + fetcher->Start(); + return fetcher; +} + +OAuth2TokenService::Fetcher::Fetcher( + OAuth2TokenService* oauth2_token_service, + const std::string& account_id, + net::URLRequestContextGetter* getter, + const std::string& client_id, + const std::string& client_secret, + const std::string& refresh_token, + const OAuth2TokenService::ScopeSet& scopes, + base::WeakPtr<RequestImpl> waiting_request) + : oauth2_token_service_(oauth2_token_service), + getter_(getter), + account_id_(account_id), + refresh_token_(refresh_token), + scopes_(scopes), + retry_number_(0), + error_(GoogleServiceAuthError::SERVICE_UNAVAILABLE), + client_id_(client_id), + client_secret_(client_secret) { + DCHECK(oauth2_token_service_); + DCHECK(getter_.get()); + DCHECK(refresh_token_.length()); + waiting_requests_.push_back(waiting_request); +} + +OAuth2TokenService::Fetcher::~Fetcher() { + // Inform the waiting requests if it has not done so. + if (waiting_requests_.size()) + InformWaitingRequests(); +} + +void OAuth2TokenService::Fetcher::Start() { + fetcher_.reset(new OAuth2AccessTokenFetcher(this, getter_.get())); + fetcher_->Start(client_id_, + client_secret_, + refresh_token_, + std::vector<std::string>(scopes_.begin(), scopes_.end())); + retry_timer_.Stop(); +} + +void OAuth2TokenService::Fetcher::OnGetTokenSuccess( + const std::string& access_token, + const base::Time& expiration_date) { + fetcher_.reset(); + + // Fetch completes. + error_ = GoogleServiceAuthError::AuthErrorNone(); + access_token_ = access_token; + expiration_date_ = expiration_date; + + // Subclasses may override this method to skip caching in some cases, but + // we still inform all waiting Consumers of a successful token fetch below. + // This is intentional -- some consumers may need the token for cleanup + // tasks. https://chromiumcodereview.appspot.com/11312124/ + oauth2_token_service_->RegisterCacheEntry(client_id_, + account_id_, + scopes_, + access_token_, + expiration_date_); + InformWaitingRequestsAndDelete(); +} + +void OAuth2TokenService::Fetcher::OnGetTokenFailure( + const GoogleServiceAuthError& error) { + fetcher_.reset(); + + if (ShouldRetry(error) && retry_number_ < max_fetch_retry_num_) { + int64 backoff = ComputeExponentialBackOffMilliseconds(retry_number_); + ++retry_number_; + retry_timer_.Stop(); + retry_timer_.Start(FROM_HERE, + base::TimeDelta::FromMilliseconds(backoff), + this, + &OAuth2TokenService::Fetcher::Start); + return; + } + + error_ = error; + InformWaitingRequestsAndDelete(); +} + +// Returns an exponential backoff in milliseconds including randomness less than +// 1000 ms when retrying fetching an OAuth2 access token. +int64 OAuth2TokenService::Fetcher::ComputeExponentialBackOffMilliseconds( + int retry_num) { + DCHECK(retry_num < max_fetch_retry_num_); + int64 exponential_backoff_in_seconds = 1 << retry_num; + // Returns a backoff with randomness < 1000ms + return (exponential_backoff_in_seconds + base::RandDouble()) * 1000; +} + +// static +bool OAuth2TokenService::Fetcher::ShouldRetry( + const GoogleServiceAuthError& error) { + GoogleServiceAuthError::State error_state = error.state(); + return error_state == GoogleServiceAuthError::CONNECTION_FAILED || + error_state == GoogleServiceAuthError::REQUEST_CANCELED || + error_state == GoogleServiceAuthError::SERVICE_UNAVAILABLE; +} + +void OAuth2TokenService::Fetcher::InformWaitingRequests() { + std::vector<base::WeakPtr<RequestImpl> >::const_iterator iter = + waiting_requests_.begin(); + for (; iter != waiting_requests_.end(); ++iter) { + base::WeakPtr<RequestImpl> waiting_request = *iter; + if (waiting_request.get()) + waiting_request->InformConsumer(error_, access_token_, expiration_date_); + } + waiting_requests_.clear(); +} + +void OAuth2TokenService::Fetcher::InformWaitingRequestsAndDelete() { + // Deregisters itself from the service to prevent more waiting requests to + // be added when it calls back the waiting requests. + oauth2_token_service_->OnFetchComplete(this); + InformWaitingRequests(); + base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); +} + +void OAuth2TokenService::Fetcher::AddWaitingRequest( + base::WeakPtr<OAuth2TokenService::RequestImpl> waiting_request) { + waiting_requests_.push_back(waiting_request); +} + +size_t OAuth2TokenService::Fetcher::GetWaitingRequestCount() const { + return waiting_requests_.size(); +} + +void OAuth2TokenService::Fetcher::Cancel() { + fetcher_.reset(); + retry_timer_.Stop(); + error_ = GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); + InformWaitingRequestsAndDelete(); +} + +const OAuth2TokenService::ScopeSet& OAuth2TokenService::Fetcher::GetScopeSet() + const { + return scopes_; +} + +const std::string& OAuth2TokenService::Fetcher::GetRefreshToken() const { + return refresh_token_; +} + +const std::string& OAuth2TokenService::Fetcher::GetClientId() const { + return client_id_; +} + +const std::string& OAuth2TokenService::Fetcher::GetAccountId() const { + return account_id_; +} + +OAuth2TokenService::Request::Request() { +} + +OAuth2TokenService::Request::~Request() { +} + +OAuth2TokenService::Consumer::Consumer() { +} + +OAuth2TokenService::Consumer::~Consumer() { +} + +OAuth2TokenService::OAuth2TokenService() { +} + +OAuth2TokenService::~OAuth2TokenService() { + // Release all the pending fetchers. + STLDeleteContainerPairSecondPointers( + pending_fetchers_.begin(), pending_fetchers_.end()); +} + +void OAuth2TokenService::AddObserver(Observer* observer) { + observer_list_.AddObserver(observer); +} + +void OAuth2TokenService::RemoveObserver(Observer* observer) { + observer_list_.RemoveObserver(observer); +} + +bool OAuth2TokenService::RefreshTokenIsAvailable( + const std::string& account_id) { + DCHECK(CalledOnValidThread()); + return !GetRefreshToken(account_id).empty(); +} + +std::vector<std::string> OAuth2TokenService::GetAccounts() { + return std::vector<std::string>(); +} + +scoped_ptr<OAuth2TokenService::Request> OAuth2TokenService::StartRequest( + const std::string& account_id, + const OAuth2TokenService::ScopeSet& scopes, + OAuth2TokenService::Consumer* consumer) { + return StartRequestForClientWithContext( + account_id, + GetRequestContext(), + GaiaUrls::GetInstance()->oauth2_chrome_client_id(), + GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), + scopes, + consumer); +} + +scoped_ptr<OAuth2TokenService::Request> +OAuth2TokenService::StartRequestForClient( + const std::string& account_id, + const std::string& client_id, + const std::string& client_secret, + const OAuth2TokenService::ScopeSet& scopes, + OAuth2TokenService::Consumer* consumer) { + return StartRequestForClientWithContext( + account_id, + GetRequestContext(), + client_id, + client_secret, + scopes, + consumer); +} + +scoped_ptr<OAuth2TokenService::Request> +OAuth2TokenService::StartRequestWithContext( + const std::string& account_id, + net::URLRequestContextGetter* getter, + const ScopeSet& scopes, + Consumer* consumer) { + return StartRequestForClientWithContext( + account_id, + getter, + GaiaUrls::GetInstance()->oauth2_chrome_client_id(), + GaiaUrls::GetInstance()->oauth2_chrome_client_secret(), + scopes, + consumer); +} + +scoped_ptr<OAuth2TokenService::Request> +OAuth2TokenService::StartRequestForClientWithContext( + const std::string& account_id, + net::URLRequestContextGetter* getter, + const std::string& client_id, + const std::string& client_secret, + const ScopeSet& scopes, + Consumer* consumer) { + DCHECK(CalledOnValidThread()); + + scoped_ptr<RequestImpl> request(new RequestImpl(consumer)); + + if (!RefreshTokenIsAvailable(account_id)) { + base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &RequestImpl::InformConsumer, + request->AsWeakPtr(), + GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP), + std::string(), + base::Time())); + return request.PassAs<Request>(); + } + + RequestParameters request_parameters(client_id, + account_id, + scopes); + if (HasCacheEntry(request_parameters)) { + StartCacheLookupRequest(request.get(), request_parameters, consumer); + } else { + FetchOAuth2Token(request.get(), + account_id, + getter, + client_id, + client_secret, + scopes); + } + return request.PassAs<Request>(); +} + +void OAuth2TokenService::FetchOAuth2Token(RequestImpl* request, + const std::string& account_id, + net::URLRequestContextGetter* getter, + const std::string& client_id, + const std::string& client_secret, + const ScopeSet& scopes) { + std::string refresh_token = GetRefreshToken(account_id); + + // If there is already a pending fetcher for |scopes| and |account_id|, + // simply register this |request| for those results rather than starting + // a new fetcher. + RequestParameters request_parameters = RequestParameters(client_id, + account_id, + scopes); + std::map<RequestParameters, Fetcher*>::iterator iter = + pending_fetchers_.find(request_parameters); + if (iter != pending_fetchers_.end()) { + iter->second->AddWaitingRequest(request->AsWeakPtr()); + return; + } + + pending_fetchers_[request_parameters] = + Fetcher::CreateAndStart(this, + account_id, + getter, + client_id, + client_secret, + refresh_token, + scopes, + request->AsWeakPtr()); +} + +void OAuth2TokenService::StartCacheLookupRequest( + RequestImpl* request, + const OAuth2TokenService::RequestParameters& request_parameters, + OAuth2TokenService::Consumer* consumer) { + CHECK(HasCacheEntry(request_parameters)); + const CacheEntry* cache_entry = GetCacheEntry(request_parameters); + base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &RequestImpl::InformConsumer, + request->AsWeakPtr(), + GoogleServiceAuthError(GoogleServiceAuthError::NONE), + cache_entry->access_token, + cache_entry->expiration_date)); +} + +void OAuth2TokenService::InvalidateToken(const std::string& account_id, + const ScopeSet& scopes, + const std::string& access_token) { + InvalidateOAuth2Token(account_id, + GaiaUrls::GetInstance()->oauth2_chrome_client_id(), + scopes, + access_token); +} + +void OAuth2TokenService::InvalidateTokenForClient( + const std::string& account_id, + const std::string& client_id, + const ScopeSet& scopes, + const std::string& access_token) { + InvalidateOAuth2Token(account_id, client_id, scopes, access_token); +} + +void OAuth2TokenService::InvalidateOAuth2Token( + const std::string& account_id, + const std::string& client_id, + const ScopeSet& scopes, + const std::string& access_token) { + DCHECK(CalledOnValidThread()); + RemoveCacheEntry( + RequestParameters(client_id, + account_id, + scopes), + access_token); +} + +void OAuth2TokenService::OnFetchComplete(Fetcher* fetcher) { + DCHECK(CalledOnValidThread()); + + // Update the auth error state so auth errors are appropriately communicated + // to the user. + UpdateAuthError(fetcher->GetAccountId(), fetcher->error()); + + // Note |fetcher| is recorded in |pending_fetcher_| mapped to its refresh + // token and scope set. This is guaranteed as follows; here a Fetcher is said + // to be uncompleted if it has not finished calling back + // OAuth2TokenService::OnFetchComplete(). + // + // (1) All the live Fetchers are created by this service. + // This is because (1) all the live Fetchers are created by a live + // service, as all the fetchers created by a service are destructed in the + // service's dtor. + // + // (2) All the uncompleted Fetchers created by this service are recorded in + // |pending_fetchers_|. + // This is because (1) all the created Fetchers are added to + // |pending_fetchers_| (in method StartRequest()) and (2) method + // OnFetchComplete() is the only place where a Fetcher is erased from + // |pending_fetchers_|. Note no Fetcher is erased in method + // StartRequest(). + // + // (3) Each of the Fetchers recorded in |pending_fetchers_| is mapped to its + // refresh token and ScopeSet. This is guaranteed by Fetcher creation in + // method StartRequest(). + // + // When this method is called, |fetcher| is alive and uncompleted. + // By (1), |fetcher| is created by this service. + // Then by (2), |fetcher| is recorded in |pending_fetchers_|. + // Then by (3), |fetcher_| is mapped to its refresh token and ScopeSet. + std::map<RequestParameters, Fetcher*>::iterator iter = + pending_fetchers_.find(RequestParameters( + fetcher->GetClientId(), + fetcher->GetAccountId(), + fetcher->GetScopeSet())); + DCHECK(iter != pending_fetchers_.end()); + DCHECK_EQ(fetcher, iter->second); + pending_fetchers_.erase(iter); +} + +bool OAuth2TokenService::HasCacheEntry( + const RequestParameters& request_parameters) { + const CacheEntry* cache_entry = GetCacheEntry(request_parameters); + return cache_entry && cache_entry->access_token.length(); +} + +const OAuth2TokenService::CacheEntry* OAuth2TokenService::GetCacheEntry( + const RequestParameters& request_parameters) { + DCHECK(CalledOnValidThread()); + TokenCache::iterator token_iterator = token_cache_.find(request_parameters); + if (token_iterator == token_cache_.end()) + return NULL; + if (token_iterator->second.expiration_date <= base::Time::Now()) { + token_cache_.erase(token_iterator); + return NULL; + } + return &token_iterator->second; +} + +bool OAuth2TokenService::RemoveCacheEntry( + const RequestParameters& request_parameters, + const std::string& token_to_remove) { + DCHECK(CalledOnValidThread()); + TokenCache::iterator token_iterator = token_cache_.find(request_parameters); + if (token_iterator != token_cache_.end() && + token_iterator->second.access_token == token_to_remove) { + token_cache_.erase(token_iterator); + return true; + } + return false; +} + +void OAuth2TokenService::RegisterCacheEntry( + const std::string& client_id, + const std::string& account_id, + const OAuth2TokenService::ScopeSet& scopes, + const std::string& access_token, + const base::Time& expiration_date) { + DCHECK(CalledOnValidThread()); + + CacheEntry& token = token_cache_[RequestParameters(client_id, + account_id, + scopes)]; + token.access_token = access_token; + token.expiration_date = expiration_date; +} + +void OAuth2TokenService::UpdateAuthError( + const std::string& account_id, + const GoogleServiceAuthError& error) { + // Default implementation does nothing. +} + +void OAuth2TokenService::ClearCache() { + DCHECK(CalledOnValidThread()); + token_cache_.clear(); +} + +void OAuth2TokenService::ClearCacheForAccount(const std::string& account_id) { + DCHECK(CalledOnValidThread()); + for (TokenCache::iterator iter = token_cache_.begin(); + iter != token_cache_.end(); + /* iter incremented in body */) { + if (iter->first.account_id == account_id) { + token_cache_.erase(iter++); + } else { + ++iter; + } + } +} + +void OAuth2TokenService::CancelAllRequests() { + std::vector<Fetcher*> fetchers_to_cancel; + for (std::map<RequestParameters, Fetcher*>::iterator iter = + pending_fetchers_.begin(); + iter != pending_fetchers_.end(); + ++iter) { + fetchers_to_cancel.push_back(iter->second); + } + CancelFetchers(fetchers_to_cancel); +} + +void OAuth2TokenService::CancelRequestsForAccount( + const std::string& account_id) { + std::vector<Fetcher*> fetchers_to_cancel; + for (std::map<RequestParameters, Fetcher*>::iterator iter = + pending_fetchers_.begin(); + iter != pending_fetchers_.end(); + ++iter) { + if (iter->first.account_id == account_id) + fetchers_to_cancel.push_back(iter->second); + } + CancelFetchers(fetchers_to_cancel); +} + +void OAuth2TokenService::CancelFetchers( + std::vector<Fetcher*> fetchers_to_cancel) { + for (std::vector<OAuth2TokenService::Fetcher*>::iterator iter = + fetchers_to_cancel.begin(); + iter != fetchers_to_cancel.end(); + ++iter) { + (*iter)->Cancel(); + } +} + +void OAuth2TokenService::FireRefreshTokenAvailable( + const std::string& account_id) { + FOR_EACH_OBSERVER(Observer, observer_list_, + OnRefreshTokenAvailable(account_id)); +} + +void OAuth2TokenService::FireRefreshTokenRevoked( + const std::string& account_id) { + FOR_EACH_OBSERVER(Observer, observer_list_, + OnRefreshTokenRevoked(account_id)); +} + +void OAuth2TokenService::FireRefreshTokensLoaded() { + FOR_EACH_OBSERVER(Observer, observer_list_, OnRefreshTokensLoaded()); +} + +int OAuth2TokenService::cache_size_for_testing() const { + return token_cache_.size(); +} + +void OAuth2TokenService::set_max_authorization_token_fetch_retries_for_testing( + int max_retries) { + DCHECK(CalledOnValidThread()); + max_fetch_retry_num_ = max_retries; +} + +size_t OAuth2TokenService::GetNumPendingRequestsForTesting( + const std::string& client_id, + const std::string& account_id, + const ScopeSet& scopes) const { + PendingFetcherMap::const_iterator iter = pending_fetchers_.find( + OAuth2TokenService::RequestParameters( + client_id, + account_id, + scopes)); + return iter == pending_fetchers_.end() ? + 0 : iter->second->GetWaitingRequestCount(); +} diff --git a/chromium/google_apis/gaia/oauth2_token_service.h b/chromium/google_apis/gaia/oauth2_token_service.h new file mode 100644 index 00000000000..d092430f9a5 --- /dev/null +++ b/chromium/google_apis/gaia/oauth2_token_service.h @@ -0,0 +1,341 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GOOGLE_APIS_GAIA_OAUTH2_TOKEN_SERVICE_H_ +#define GOOGLE_APIS_GAIA_OAUTH2_TOKEN_SERVICE_H_ + +#include <map> +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "base/threading/non_thread_safe.h" +#include "base/time/time.h" +#include "base/timer/timer.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "google_apis/gaia/oauth2_access_token_consumer.h" +#include "google_apis/gaia/oauth2_access_token_fetcher.h" + +namespace net { +class URLRequestContextGetter; +} + +class GoogleServiceAuthError; + +// Abstract base class for a service that fetches and caches OAuth2 access +// tokens. Concrete subclasses should implement GetRefreshToken to return +// the appropriate refresh token. Derived services might maintain refresh tokens +// for multiple accounts. +// +// All calls are expected from the UI thread. +// +// To use this service, call StartRequest() with a given set of scopes and a +// consumer of the request results. The consumer is required to outlive the +// request. The request can be deleted. The consumer may be called back +// asynchronously with the fetch results. +// +// - If the consumer is not called back before the request is deleted, it will +// never be called back. +// Note in this case, the actual network requests are not canceled and the +// cache will be populated with the fetched results; it is just the consumer +// callback that is aborted. +// +// - Otherwise the consumer will be called back with the request and the fetch +// results. +// +// The caller of StartRequest() owns the returned request and is responsible to +// delete the request even once the callback has been invoked. +class OAuth2TokenService : public base::NonThreadSafe { + public: + // Class representing a request that fetches an OAuth2 access token. + class Request { + public: + virtual ~Request(); + protected: + Request(); + }; + + // Class representing the consumer of a Request passed to |StartRequest|, + // which will be called back when the request completes. + class Consumer { + public: + Consumer(); + virtual ~Consumer(); + // |request| is a Request that is started by this consumer and has + // completed. + virtual void OnGetTokenSuccess(const Request* request, + const std::string& access_token, + const base::Time& expiration_time) = 0; + virtual void OnGetTokenFailure(const Request* request, + const GoogleServiceAuthError& error) = 0; + }; + + // Classes that want to listen for token availability should implement this + // interface and register with the AddObserver() call. + class Observer { + public: + // Called whenever a new login-scoped refresh token is available for + // account |account_id|. Once available, access tokens can be retrieved for + // this account. This is called during initial startup for each token + // loaded. + virtual void OnRefreshTokenAvailable(const std::string& account_id) {} + // Called whenever the login-scoped refresh token becomes unavailable for + // account |account_id|. + virtual void OnRefreshTokenRevoked(const std::string& account_id) {} + // Called after all refresh tokens are loaded during OAuth2TokenService + // startup. + virtual void OnRefreshTokensLoaded() {} + protected: + virtual ~Observer() {} + }; + + // A set of scopes in OAuth2 authentication. + typedef std::set<std::string> ScopeSet; + + OAuth2TokenService(); + virtual ~OAuth2TokenService(); + + // Add or remove observers of this token service. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + // Checks in the cache for a valid access token for a specified |account_id| + // and |scopes|, and if not found starts a request for an OAuth2 access token + // using the OAuth2 refresh token maintained by this instance for that + // |account_id|. The caller owns the returned Request. + // |scopes| is the set of scopes to get an access token for, |consumer| is + // the object that will be called back with results if the returned request + // is not deleted. + // TODO(atwilson): Make this non-virtual when we change + // ProfileOAuth2TokenServiceRequestTest to use FakeProfileOAuth2TokenService. + virtual scoped_ptr<Request> StartRequest(const std::string& account_id, + const ScopeSet& scopes, + Consumer* consumer); + + // This method does the same as |StartRequest| except it uses |client_id| and + // |client_secret| to identify OAuth client app instead of using + // Chrome's default values. + scoped_ptr<Request> StartRequestForClient( + const std::string& account_id, + const std::string& client_id, + const std::string& client_secret, + const ScopeSet& scopes, + Consumer* consumer); + + // This method does the same as |StartRequest| except it uses the request + // context given by |getter| instead of using the one returned by + // |GetRequestContext| implemented by derived classes. + scoped_ptr<Request> StartRequestWithContext( + const std::string& account_id, + net::URLRequestContextGetter* getter, + const ScopeSet& scopes, + Consumer* consumer); + + // Lists account IDs of all accounts with a refresh token maintained by this + // instance. + virtual std::vector<std::string> GetAccounts(); + + // Returns true if a refresh token exists for |account_id|. If false, calls to + // |StartRequest| will result in a Consumer::OnGetTokenFailure callback. + virtual bool RefreshTokenIsAvailable(const std::string& account_id); + + // Mark an OAuth2 |access_token| issued for |account_id| and |scopes| as + // invalid. This should be done if the token was received from this class, + // but was not accepted by the server (e.g., the server returned + // 401 Unauthorized). The token will be removed from the cache for the given + // scopes. + void InvalidateToken(const std::string& account_id, + const ScopeSet& scopes, + const std::string& access_token); + + // Like |InvalidateToken| except is uses |client_id| to identity OAuth2 client + // app that issued the request instead of Chrome's default values. + void InvalidateTokenForClient(const std::string& account_id, + const std::string& client_id, + const ScopeSet& scopes, + const std::string& access_token); + + + // Return the current number of entries in the cache. + int cache_size_for_testing() const; + void set_max_authorization_token_fetch_retries_for_testing(int max_retries); + // Returns the current number of pending fetchers matching given params. + size_t GetNumPendingRequestsForTesting( + const std::string& client_id, + const std::string& account_id, + const ScopeSet& scopes) const; + + protected: + // Implements a cancelable |OAuth2TokenService::Request|, which should be + // operated on the UI thread. + // TODO(davidroche): move this out of header file. + class RequestImpl : public base::SupportsWeakPtr<RequestImpl>, + public base::NonThreadSafe, + public Request { + public: + // |consumer| is required to outlive this. + explicit RequestImpl(Consumer* consumer); + virtual ~RequestImpl(); + + // Informs |consumer_| that this request is completed. + void InformConsumer(const GoogleServiceAuthError& error, + const std::string& access_token, + const base::Time& expiration_date); + + private: + // |consumer_| to call back when this request completes. + Consumer* const consumer_; + }; + + // Subclasses should return the maintained refresh token for |account_id|. + // If no token is available, return an empty string. + virtual std::string GetRefreshToken(const std::string& account_id) = 0; + + // Subclasses can override if they want to report errors to the user. + virtual void UpdateAuthError( + const std::string& account_id, + const GoogleServiceAuthError& error); + + // Add a new entry to the cache. + // Subclasses can override if there are implementation-specific reasons + // that an access token should ever not be cached. + virtual void RegisterCacheEntry(const std::string& client_id, + const std::string& account_id, + const ScopeSet& scopes, + const std::string& access_token, + const base::Time& expiration_date); + + // Clears the internal token cache. + void ClearCache(); + + // Clears all of the tokens belonging to |account_id| from the internal token + // cache. It does not matter what other parameters, like |client_id| were + // used to request the tokens. + void ClearCacheForAccount(const std::string& account_id); + + // Cancels all requests that are currently in progress. + void CancelAllRequests(); + + // Cancels all requests related to a given |account_id|. + void CancelRequestsForAccount(const std::string& account_id); + + // Called by subclasses to notify observers. + void FireRefreshTokenAvailable(const std::string& account_id); + void FireRefreshTokenRevoked(const std::string& account_id); + void FireRefreshTokensLoaded(); + + // Fetches an OAuth token for the specified client/scopes. Virtual so it can + // be overridden for tests and for platform-specific behavior on Android. + virtual void FetchOAuth2Token(RequestImpl* request, + const std::string& account_id, + net::URLRequestContextGetter* getter, + const std::string& client_id, + const std::string& client_secret, + const ScopeSet& scopes); + + // Invalidates the |access_token| issued for |account_id|, |client_id| and + // |scopes|. Virtual so it can be overriden for tests and for platform- + // specifc behavior. + virtual void InvalidateOAuth2Token(const std::string& account_id, + const std::string& client_id, + const ScopeSet& scopes, + const std::string& access_token); + + private: + class Fetcher; + friend class Fetcher; + + // The parameters used to fetch an OAuth2 access token. + struct RequestParameters { + RequestParameters(const std::string& client_id, + const std::string& account_id, + const ScopeSet& scopes); + ~RequestParameters(); + bool operator<(const RequestParameters& params) const; + + // OAuth2 client id. + std::string client_id; + // Account id for which the request is made. + std::string account_id; + // URL scopes for the requested access token. + ScopeSet scopes; + }; + + typedef std::map<RequestParameters, Fetcher*> PendingFetcherMap; + + // Derived classes must provide a request context used for fetching access + // tokens with the |StartRequest| method. + virtual net::URLRequestContextGetter* GetRequestContext() = 0; + + // Struct that contains the information of an OAuth2 access token. + struct CacheEntry { + std::string access_token; + base::Time expiration_date; + }; + + // This method does the same as |StartRequestWithContext| except it + // uses |client_id| and |client_secret| to identify OAuth + // client app instead of using Chrome's default values. + scoped_ptr<Request> StartRequestForClientWithContext( + const std::string& account_id, + net::URLRequestContextGetter* getter, + const std::string& client_id, + const std::string& client_secret, + const ScopeSet& scopes, + Consumer* consumer); + + // Returns true if GetCacheEntry would return a valid cache entry for the + // given scopes. + bool HasCacheEntry(const RequestParameters& client_scopes); + + // Posts a task to fire the Consumer callback with the cached token. Must + // Must only be called if HasCacheEntry() returns true. + void StartCacheLookupRequest(RequestImpl* request, + const RequestParameters& client_scopes, + Consumer* consumer); + + // Returns a currently valid OAuth2 access token for the given set of scopes, + // or NULL if none have been cached. Note the user of this method should + // ensure no entry with the same |client_scopes| is added before the usage of + // the returned entry is done. + const CacheEntry* GetCacheEntry(const RequestParameters& client_scopes); + + // Removes an access token for the given set of scopes from the cache. + // Returns true if the entry was removed, otherwise false. + bool RemoveCacheEntry(const RequestParameters& client_scopes, + const std::string& token_to_remove); + + // Called when |fetcher| finishes fetching. + void OnFetchComplete(Fetcher* fetcher); + + // Called when a number of fetchers need to be canceled. + void CancelFetchers(std::vector<Fetcher*> fetchers_to_cancel); + + // The cache of currently valid tokens. + typedef std::map<RequestParameters, CacheEntry> TokenCache; + TokenCache token_cache_; + + // A map from fetch parameters to a fetcher that is fetching an OAuth2 access + // token using these parameters. + PendingFetcherMap pending_fetchers_; + + // List of observers to notify when token availability changes. + // Makes sure list is empty on destruction. + ObserverList<Observer, true> observer_list_; + + // Maximum number of retries in fetching an OAuth2 access token. + static int max_fetch_retry_num_; + + FRIEND_TEST_ALL_PREFIXES(OAuth2TokenServiceTest, RequestParametersOrderTest); + FRIEND_TEST_ALL_PREFIXES(OAuth2TokenServiceTest, + SameScopesRequestedForDifferentClients); + + DISALLOW_COPY_AND_ASSIGN(OAuth2TokenService); +}; + +#endif // GOOGLE_APIS_GAIA_OAUTH2_TOKEN_SERVICE_H_ diff --git a/chromium/google_apis/gaia/oauth2_token_service_test_util.cc b/chromium/google_apis/gaia/oauth2_token_service_test_util.cc new file mode 100644 index 00000000000..2aae59bb41a --- /dev/null +++ b/chromium/google_apis/gaia/oauth2_token_service_test_util.cc @@ -0,0 +1,44 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "google_apis/gaia/oauth2_token_service_test_util.h" + +#include "base/strings/stringprintf.h" + +namespace { +const char kValidTokenResponse[] = + "{" + " \"access_token\": \"%s\"," + " \"expires_in\": %d," + " \"token_type\": \"Bearer\"" + "}"; +} + +std::string GetValidTokenResponse(std::string token, int expiration) { + return base::StringPrintf(kValidTokenResponse, token.c_str(), expiration); +} + +TestingOAuth2TokenServiceConsumer::TestingOAuth2TokenServiceConsumer() + : number_of_successful_tokens_(0), + last_error_(GoogleServiceAuthError::AuthErrorNone()), + number_of_errors_(0) { +} + +TestingOAuth2TokenServiceConsumer::~TestingOAuth2TokenServiceConsumer() { +} + +void TestingOAuth2TokenServiceConsumer::OnGetTokenSuccess( + const OAuth2TokenService::Request* request, + const std::string& token, + const base::Time& expiration_date) { + last_token_ = token; + ++number_of_successful_tokens_; +} + +void TestingOAuth2TokenServiceConsumer::OnGetTokenFailure( + const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) { + last_error_ = error; + ++number_of_errors_; +} diff --git a/chromium/google_apis/gaia/oauth2_token_service_test_util.h b/chromium/google_apis/gaia/oauth2_token_service_test_util.h new file mode 100644 index 00000000000..53ccdf8228a --- /dev/null +++ b/chromium/google_apis/gaia/oauth2_token_service_test_util.h @@ -0,0 +1,34 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef GOOGLE_APIS_GAIA_OAUTH2_TOKEN_SERVICE_TEST_UTIL_H_ +#define GOOGLE_APIS_GAIA_OAUTH2_TOKEN_SERVICE_TEST_UTIL_H_ + +#include <string> + +#include "google_apis/gaia/google_service_auth_error.h" +#include "google_apis/gaia/oauth2_token_service.h" + +std::string GetValidTokenResponse(std::string token, int expiration); + +// A simple testing consumer. +class TestingOAuth2TokenServiceConsumer : public OAuth2TokenService::Consumer { + public: + TestingOAuth2TokenServiceConsumer(); + virtual ~TestingOAuth2TokenServiceConsumer(); + + // OAuth2TokenService::Consumer overrides. + virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request, + const std::string& token, + const base::Time& expiration_date) OVERRIDE; + virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) OVERRIDE; + + std::string last_token_; + int number_of_successful_tokens_; + GoogleServiceAuthError last_error_; + int number_of_errors_; +}; + +#endif // GOOGLE_APIS_GAIA_OAUTH2_TOKEN_SERVICE_TEST_UTIL_H_ diff --git a/chromium/google_apis/gaia/oauth2_token_service_unittest.cc b/chromium/google_apis/gaia/oauth2_token_service_unittest.cc new file mode 100644 index 00000000000..dcd21b12e93 --- /dev/null +++ b/chromium/google_apis/gaia/oauth2_token_service_unittest.cc @@ -0,0 +1,635 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "google_apis/gaia/gaia_constants.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "google_apis/gaia/oauth2_access_token_consumer.h" +#include "google_apis/gaia/oauth2_access_token_fetcher.h" +#include "google_apis/gaia/oauth2_token_service.h" +#include "google_apis/gaia/oauth2_token_service_test_util.h" +#include "net/http/http_status_code.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +// A testing consumer that retries on error. +class RetryingTestingOAuth2TokenServiceConsumer + : public TestingOAuth2TokenServiceConsumer { + public: + RetryingTestingOAuth2TokenServiceConsumer( + OAuth2TokenService* oauth2_service, + const std::string& account_id) + : oauth2_service_(oauth2_service), + account_id_(account_id) {} + virtual ~RetryingTestingOAuth2TokenServiceConsumer() {} + + virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) OVERRIDE { + TestingOAuth2TokenServiceConsumer::OnGetTokenFailure(request, error); + request_.reset(oauth2_service_->StartRequest( + account_id_, OAuth2TokenService::ScopeSet(), this).release()); + } + + OAuth2TokenService* oauth2_service_; + std::string account_id_; + scoped_ptr<OAuth2TokenService::Request> request_; +}; + +class TestOAuth2TokenService : public OAuth2TokenService { + public: + explicit TestOAuth2TokenService(net::TestURLRequestContextGetter* getter) + : request_context_getter_(getter) { + } + + void CancelAllRequestsForTest() { CancelAllRequests(); } + + void CancelRequestsForAccountForTest(const std::string& account_id) { + CancelRequestsForAccount(account_id); + } + + // For testing: set the refresh token to be used. + void set_refresh_token(const std::string& account_id, + const std::string& refresh_token) { + refresh_tokens_[account_id] = refresh_token; + } + + protected: + virtual std::string GetRefreshToken(const std::string& account_id) OVERRIDE { + // account_id explicitly ignored. + return refresh_tokens_[account_id]; + } + + private: + // OAuth2TokenService implementation. + virtual net::URLRequestContextGetter* GetRequestContext() OVERRIDE { + return request_context_getter_.get(); + } + + std::map<std::string, std::string> refresh_tokens_; + scoped_refptr<net::TestURLRequestContextGetter> request_context_getter_; +}; + +class OAuth2TokenServiceTest : public testing::Test { + public: + virtual void SetUp() OVERRIDE { + oauth2_service_.reset( + new TestOAuth2TokenService(new net::TestURLRequestContextGetter( + message_loop_.message_loop_proxy()))); + account_id_ = "test_user@gmail.com"; + } + + virtual void TearDown() OVERRIDE { + // Makes sure that all the clean up tasks are run. + base::RunLoop().RunUntilIdle(); + } + + protected: + base::MessageLoopForIO message_loop_; // net:: stuff needs IO message loop. + net::TestURLFetcherFactory factory_; + scoped_ptr<TestOAuth2TokenService> oauth2_service_; + std::string account_id_; + TestingOAuth2TokenServiceConsumer consumer_; +}; + +TEST_F(OAuth2TokenServiceTest, NoOAuth2RefreshToken) { + scoped_ptr<OAuth2TokenService::Request> request( + oauth2_service_->StartRequest(account_id_, OAuth2TokenService::ScopeSet(), + &consumer_)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(1, consumer_.number_of_errors_); +} + +TEST_F(OAuth2TokenServiceTest, FailureShouldNotRetry) { + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + scoped_ptr<OAuth2TokenService::Request> request( + oauth2_service_->StartRequest(account_id_, + OAuth2TokenService::ScopeSet(), + &consumer_)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_UNAUTHORIZED); + fetcher->SetResponseString(std::string()); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(1, consumer_.number_of_errors_); + EXPECT_EQ(fetcher, factory_.GetFetcherByID(0)); +} + +TEST_F(OAuth2TokenServiceTest, SuccessWithoutCaching) { + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + scoped_ptr<OAuth2TokenService::Request> request( + oauth2_service_->StartRequest(account_id_, + OAuth2TokenService::ScopeSet(), + &consumer_)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token", 3600)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token", consumer_.last_token_); +} + +TEST_F(OAuth2TokenServiceTest, SuccessWithCaching) { + OAuth2TokenService::ScopeSet scopes1; + scopes1.insert("s1"); + scopes1.insert("s2"); + OAuth2TokenService::ScopeSet scopes1_same; + scopes1_same.insert("s2"); + scopes1_same.insert("s1"); + OAuth2TokenService::ScopeSet scopes2; + scopes2.insert("s3"); + + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + + // First request. + scoped_ptr<OAuth2TokenService::Request> request( + oauth2_service_->StartRequest(account_id_, scopes1, &consumer_)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token", 3600)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token", consumer_.last_token_); + + // Second request to the same set of scopes, should return the same token + // without needing a network request. + scoped_ptr<OAuth2TokenService::Request> request2( + oauth2_service_->StartRequest(account_id_, scopes1_same, &consumer_)); + base::RunLoop().RunUntilIdle(); + + // No new network fetcher. + EXPECT_EQ(2, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token", consumer_.last_token_); + + // Third request to a new set of scopes, should return another token. + scoped_ptr<OAuth2TokenService::Request> request3( + oauth2_service_->StartRequest(account_id_, scopes2, &consumer_)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(2, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token2", 3600)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(3, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token2", consumer_.last_token_); +} + +TEST_F(OAuth2TokenServiceTest, SuccessAndExpirationAndFailure) { + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + + // First request. + scoped_ptr<OAuth2TokenService::Request> request( + oauth2_service_->StartRequest(account_id_, + OAuth2TokenService::ScopeSet(), + &consumer_)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token", 0)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token", consumer_.last_token_); + + // Second request must try to access the network as the token has expired. + scoped_ptr<OAuth2TokenService::Request> request2( + oauth2_service_->StartRequest(account_id_, + OAuth2TokenService::ScopeSet(), + &consumer_)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + + // Network failure. + fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_UNAUTHORIZED); + fetcher->SetResponseString(std::string()); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(1, consumer_.number_of_errors_); +} + +TEST_F(OAuth2TokenServiceTest, SuccessAndExpirationAndSuccess) { + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + + // First request. + scoped_ptr<OAuth2TokenService::Request> request( + oauth2_service_->StartRequest(account_id_, + OAuth2TokenService::ScopeSet(), + &consumer_)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token", 0)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token", consumer_.last_token_); + + // Second request must try to access the network as the token has expired. + scoped_ptr<OAuth2TokenService::Request> request2( + oauth2_service_->StartRequest(account_id_, + OAuth2TokenService::ScopeSet(), + &consumer_)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + + fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("another token", 0)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(2, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("another token", consumer_.last_token_); +} + +TEST_F(OAuth2TokenServiceTest, RequestDeletedBeforeCompletion) { + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + + scoped_ptr<OAuth2TokenService::Request> request( + oauth2_service_->StartRequest(account_id_, + OAuth2TokenService::ScopeSet(), + &consumer_)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + + request.reset(); + + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token", 3600)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); +} + +TEST_F(OAuth2TokenServiceTest, RequestDeletedAfterCompletion) { + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + + scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest( + account_id_, OAuth2TokenService::ScopeSet(), &consumer_)); + base::RunLoop().RunUntilIdle(); + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token", 3600)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token", consumer_.last_token_); + + request.reset(); + + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token", consumer_.last_token_); +} + +TEST_F(OAuth2TokenServiceTest, MultipleRequestsForTheSameScopesWithOneDeleted) { + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + + scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest( + account_id_, OAuth2TokenService::ScopeSet(), &consumer_)); + base::RunLoop().RunUntilIdle(); + scoped_ptr<OAuth2TokenService::Request> request2( + oauth2_service_->StartRequest(account_id_, OAuth2TokenService::ScopeSet(), + &consumer_)); + base::RunLoop().RunUntilIdle(); + + request.reset(); + + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token", 3600)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); +} + +TEST_F(OAuth2TokenServiceTest, ClearedRefreshTokenFailsSubsequentRequests) { + // We have a valid refresh token; the first request is successful. + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest( + account_id_, OAuth2TokenService::ScopeSet(), &consumer_)); + base::RunLoop().RunUntilIdle(); + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token", 3600)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token", consumer_.last_token_); + + // The refresh token is no longer available; subsequent requests fail. + oauth2_service_->set_refresh_token(account_id_, ""); + request = oauth2_service_->StartRequest(account_id_, + OAuth2TokenService::ScopeSet(), &consumer_); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(1, consumer_.number_of_errors_); +} + +TEST_F(OAuth2TokenServiceTest, + ChangedRefreshTokenDoesNotAffectInFlightRequests) { + oauth2_service_->set_refresh_token(account_id_, "first refreshToken"); + OAuth2TokenService::ScopeSet scopes; + scopes.insert("s1"); + scopes.insert("s2"); + OAuth2TokenService::ScopeSet scopes1; + scopes.insert("s3"); + scopes.insert("s4"); + + scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest( + account_id_, scopes, &consumer_)); + base::RunLoop().RunUntilIdle(); + net::TestURLFetcher* fetcher1 = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher1); + + // Note |request| is still pending when the refresh token changes. + oauth2_service_->set_refresh_token(account_id_, "second refreshToken"); + + // A 2nd request (using the new refresh token) that occurs and completes + // while the 1st request is in flight is successful. + TestingOAuth2TokenServiceConsumer consumer2; + scoped_ptr<OAuth2TokenService::Request> request2( + oauth2_service_->StartRequest(account_id_, scopes1, &consumer2)); + base::RunLoop().RunUntilIdle(); + + net::TestURLFetcher* fetcher2 = factory_.GetFetcherByID(0); + fetcher2->set_response_code(net::HTTP_OK); + fetcher2->SetResponseString(GetValidTokenResponse("second token", 3600)); + fetcher2->delegate()->OnURLFetchComplete(fetcher2); + EXPECT_EQ(1, consumer2.number_of_successful_tokens_); + EXPECT_EQ(0, consumer2.number_of_errors_); + EXPECT_EQ("second token", consumer2.last_token_); + + fetcher1->set_response_code(net::HTTP_OK); + fetcher1->SetResponseString(GetValidTokenResponse("first token", 3600)); + fetcher1->delegate()->OnURLFetchComplete(fetcher1); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("first token", consumer_.last_token_); +} + +TEST_F(OAuth2TokenServiceTest, ServiceShutDownBeforeFetchComplete) { + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest( + account_id_, OAuth2TokenService::ScopeSet(), &consumer_)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + + // The destructor should cancel all in-flight fetchers. + oauth2_service_.reset(NULL); + + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(1, consumer_.number_of_errors_); +} + +TEST_F(OAuth2TokenServiceTest, RetryingConsumer) { + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + RetryingTestingOAuth2TokenServiceConsumer consumer(oauth2_service_.get(), + account_id_); + scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest( + account_id_, OAuth2TokenService::ScopeSet(), &consumer)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, consumer.number_of_successful_tokens_); + EXPECT_EQ(0, consumer.number_of_errors_); + + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_UNAUTHORIZED); + fetcher->SetResponseString(std::string()); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(0, consumer.number_of_successful_tokens_); + EXPECT_EQ(1, consumer.number_of_errors_); + + fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_UNAUTHORIZED); + fetcher->SetResponseString(std::string()); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(0, consumer.number_of_successful_tokens_); + EXPECT_EQ(2, consumer.number_of_errors_); +} + +TEST_F(OAuth2TokenServiceTest, InvalidateToken) { + OAuth2TokenService::ScopeSet scopes; + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + + // First request. + scoped_ptr<OAuth2TokenService::Request> request( + oauth2_service_->StartRequest(account_id_, scopes, &consumer_)); + base::RunLoop().RunUntilIdle(); + + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token", 3600)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(1, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token", consumer_.last_token_); + + // Second request, should return the same token without needing a network + // request. + scoped_ptr<OAuth2TokenService::Request> request2( + oauth2_service_->StartRequest(account_id_, scopes, &consumer_)); + base::RunLoop().RunUntilIdle(); + + // No new network fetcher. + EXPECT_EQ(2, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token", consumer_.last_token_); + + // Invalidating the token should return a new token on the next request. + oauth2_service_->InvalidateToken(account_id_, scopes, consumer_.last_token_); + scoped_ptr<OAuth2TokenService::Request> request3( + oauth2_service_->StartRequest(account_id_, scopes, &consumer_)); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(2, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(GetValidTokenResponse("token2", 3600)); + fetcher->delegate()->OnURLFetchComplete(fetcher); + EXPECT_EQ(3, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + EXPECT_EQ("token2", consumer_.last_token_); +} + +TEST_F(OAuth2TokenServiceTest, CancelAllRequests) { + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + scoped_ptr<OAuth2TokenService::Request> request( + oauth2_service_->StartRequest(account_id_, OAuth2TokenService::ScopeSet(), + &consumer_)); + + oauth2_service_->set_refresh_token("account_id_2", "refreshToken2"); + scoped_ptr<OAuth2TokenService::Request> request2( + oauth2_service_->StartRequest(account_id_, OAuth2TokenService::ScopeSet(), + &consumer_)); + + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + + oauth2_service_->CancelAllRequestsForTest(); + + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(2, consumer_.number_of_errors_); +} + +TEST_F(OAuth2TokenServiceTest, CancelRequestsForAccount) { + OAuth2TokenService::ScopeSet scope_set_1; + scope_set_1.insert("scope1"); + scope_set_1.insert("scope2"); + OAuth2TokenService::ScopeSet scope_set_2(scope_set_1.begin(), + scope_set_1.end()); + scope_set_2.insert("scope3"); + + oauth2_service_->set_refresh_token(account_id_, "refreshToken"); + scoped_ptr<OAuth2TokenService::Request> request1( + oauth2_service_->StartRequest(account_id_, scope_set_1, &consumer_)); + scoped_ptr<OAuth2TokenService::Request> request2( + oauth2_service_->StartRequest(account_id_, scope_set_2, &consumer_)); + + std::string account_id_2("account_id_2"); + oauth2_service_->set_refresh_token(account_id_2, "refreshToken2"); + scoped_ptr<OAuth2TokenService::Request> request3( + oauth2_service_->StartRequest(account_id_2, scope_set_1, &consumer_)); + + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(0, consumer_.number_of_errors_); + + oauth2_service_->CancelRequestsForAccountForTest(account_id_); + + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(2, consumer_.number_of_errors_); + + oauth2_service_->CancelRequestsForAccountForTest(account_id_2); + + EXPECT_EQ(0, consumer_.number_of_successful_tokens_); + EXPECT_EQ(3, consumer_.number_of_errors_); +} + +TEST_F(OAuth2TokenServiceTest, SameScopesRequestedForDifferentClients) { + std::string client_id_1("client1"); + std::string client_secret_1("secret1"); + std::string client_id_2("client2"); + std::string client_secret_2("secret2"); + std::set<std::string> scope_set; + scope_set.insert("scope1"); + scope_set.insert("scope2"); + + std::string refresh_token("refreshToken"); + oauth2_service_->set_refresh_token(account_id_, refresh_token); + + scoped_ptr<OAuth2TokenService::Request> request1( + oauth2_service_->StartRequestForClient(account_id_, + client_id_1, + client_secret_1, + scope_set, + &consumer_)); + scoped_ptr<OAuth2TokenService::Request> request2( + oauth2_service_->StartRequestForClient(account_id_, + client_id_2, + client_secret_2, + scope_set, + &consumer_)); + // Start a request that should be duplicate of |request1|. + scoped_ptr<OAuth2TokenService::Request> request3( + oauth2_service_->StartRequestForClient(account_id_, + client_id_1, + client_secret_1, + scope_set, + &consumer_)); + base::RunLoop().RunUntilIdle(); + + ASSERT_EQ(2U, + oauth2_service_->GetNumPendingRequestsForTesting( + client_id_1, + account_id_, + scope_set)); + ASSERT_EQ(1U, + oauth2_service_->GetNumPendingRequestsForTesting( + client_id_2, + account_id_, + scope_set)); +} + +TEST_F(OAuth2TokenServiceTest, RequestParametersOrderTest) { + OAuth2TokenService::ScopeSet set_0; + OAuth2TokenService::ScopeSet set_1; + set_1.insert("1"); + + OAuth2TokenService::RequestParameters params[] = { + OAuth2TokenService::RequestParameters("0", "0", set_0), + OAuth2TokenService::RequestParameters("0", "0", set_1), + OAuth2TokenService::RequestParameters("0", "1", set_0), + OAuth2TokenService::RequestParameters("0", "1", set_1), + OAuth2TokenService::RequestParameters("1", "0", set_0), + OAuth2TokenService::RequestParameters("1", "0", set_1), + OAuth2TokenService::RequestParameters("1", "1", set_0), + OAuth2TokenService::RequestParameters("1", "1", set_1), + }; + + for (size_t i = 0; i < arraysize(params); i++) { + for (size_t j = 0; j < arraysize(params); j++) { + if (i == j) { + EXPECT_FALSE(params[i] < params[j]) << " i=" << i << ", j=" << j; + EXPECT_FALSE(params[j] < params[i]) << " i=" << i << ", j=" << j; + } else if (i < j) { + EXPECT_TRUE(params[i] < params[j]) << " i=" << i << ", j=" << j; + EXPECT_FALSE(params[j] < params[i]) << " i=" << i << ", j=" << j; + } else { + EXPECT_TRUE(params[j] < params[i]) << " i=" << i << ", j=" << j; + EXPECT_FALSE(params[i] < params[j]) << " i=" << i << ", j=" << j; + } + } + } +} |