summaryrefslogtreecommitdiffstats
path: root/google_apis
diff options
context:
space:
mode:
authorkinaba@chromium.org <kinaba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-26 09:24:19 +0000
committerkinaba@chromium.org <kinaba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-08-26 09:24:19 +0000
commit42f41216f1ef0dbe2f782d9411bc888bb68ef263 (patch)
treea28a0cd3880ff551a5780c1c1b636335c53cbaba /google_apis
parentc852b7ad4d99658dddf8fe28265855bcd49fafa8 (diff)
downloadchromium_src-42f41216f1ef0dbe2f782d9411bc888bb68ef263.zip
chromium_src-42f41216f1ef0dbe2f782d9411bc888bb68ef263.tar.gz
chromium_src-42f41216f1ef0dbe2f782d9411bc888bb68ef263.tar.bz2
Move OAuth2TokenService to google_apis/.
This is for removing the dependency from c/b/google_apis/ to the chrome/ directory and move the whole directory content to more componentized, top-level google_api/ directory. As discussed in http://crbug.com/146989#c28 - #c30, OAuth2TokenSerive, which is the last dependency, is in fact written sorely using google_api/ items and hence itself can easily be moved to there. BUG=264570, 146989 Review URL: https://chromiumcodereview.appspot.com/23202015 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@219520 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'google_apis')
-rw-r--r--google_apis/gaia/oauth2_token_service.cc608
-rw-r--r--google_apis/gaia/oauth2_token_service.h285
-rw-r--r--google_apis/gaia/oauth2_token_service_test_util.cc44
-rw-r--r--google_apis/gaia/oauth2_token_service_test_util.h34
-rw-r--r--google_apis/gaia/oauth2_token_service_unittest.cc517
-rw-r--r--google_apis/google_apis.gyp2
6 files changed, 1490 insertions, 0 deletions
diff --git a/google_apis/gaia/oauth2_token_service.cc b/google_apis/gaia/oauth2_token_service.cc
new file mode 100644
index 0000000..0834c60
--- /dev/null
+++ b/google_apis/gaia/oauth2_token_service.cc
@@ -0,0 +1,608 @@
+// 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 "google_apis/gaia/oauth2_access_token_consumer.h"
+#include "google_apis/gaia/oauth2_access_token_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+
+int OAuth2TokenService::max_fetch_retry_num_ = 5;
+
+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 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,
+ net::URLRequestContextGetter* getter,
+ const std::string& chrome_client_id,
+ const std::string& chrome_client_secret,
+ const std::string& refresh_token,
+ const OAuth2TokenService::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);
+
+ void Cancel();
+
+ const OAuth2TokenService::ScopeSet& GetScopeSet() const;
+ const std::string& GetRefreshToken() 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,
+ net::URLRequestContextGetter* getter,
+ const std::string& chrome_client_id,
+ const std::string& chrome_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 refresh_token_;
+ const OAuth2TokenService::ScopeSet scopes_;
+ std::vector<base::WeakPtr<RequestImpl> > waiting_requests_;
+
+ int retry_number_;
+ base::OneShotTimer<OAuth2TokenService::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 chrome_client_id_;
+ std::string chrome_client_secret_;
+
+ DISALLOW_COPY_AND_ASSIGN(Fetcher);
+};
+
+// static
+OAuth2TokenService::Fetcher* OAuth2TokenService::Fetcher::CreateAndStart(
+ OAuth2TokenService* oauth2_token_service,
+ net::URLRequestContextGetter* getter,
+ const std::string& chrome_client_id,
+ const std::string& chrome_client_secret,
+ const std::string& refresh_token,
+ const OAuth2TokenService::ScopeSet& scopes,
+ base::WeakPtr<RequestImpl> waiting_request) {
+ OAuth2TokenService::Fetcher* fetcher = new Fetcher(
+ oauth2_token_service,
+ getter,
+ chrome_client_id,
+ chrome_client_secret,
+ refresh_token,
+ scopes,
+ waiting_request);
+ fetcher->Start();
+ return fetcher;
+}
+
+OAuth2TokenService::Fetcher::Fetcher(
+ OAuth2TokenService* oauth2_token_service,
+ net::URLRequestContextGetter* getter,
+ const std::string& chrome_client_id,
+ const std::string& chrome_client_secret,
+ const std::string& refresh_token,
+ const OAuth2TokenService::ScopeSet& scopes,
+ base::WeakPtr<RequestImpl> waiting_request)
+ : oauth2_token_service_(oauth2_token_service),
+ getter_(getter),
+ refresh_token_(refresh_token),
+ scopes_(scopes),
+ retry_number_(0),
+ error_(GoogleServiceAuthError::SERVICE_UNAVAILABLE),
+ chrome_client_id_(chrome_client_id),
+ chrome_client_secret_(chrome_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(chrome_client_id_,
+ chrome_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(refresh_token_,
+ 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);
+}
+
+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_;
+}
+
+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() {
+ return !GetRefreshToken().empty();
+}
+
+scoped_ptr<OAuth2TokenService::Request> OAuth2TokenService::StartRequest(
+ const OAuth2TokenService::ScopeSet& scopes,
+ OAuth2TokenService::Consumer* consumer) {
+ return StartRequestForClientWithContext(
+ GetRequestContext(),
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
+ GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
+ scopes,
+ consumer);
+}
+
+scoped_ptr<OAuth2TokenService::Request>
+OAuth2TokenService::StartRequestForClient(
+ const std::string& client_id,
+ const std::string& client_secret,
+ const OAuth2TokenService::ScopeSet& scopes,
+ OAuth2TokenService::Consumer* consumer) {
+ return StartRequestForClientWithContext(
+ GetRequestContext(),
+ client_id,
+ client_secret,
+ scopes,
+ consumer);
+}
+
+scoped_ptr<OAuth2TokenService::Request>
+OAuth2TokenService::StartRequestWithContext(
+ net::URLRequestContextGetter* getter,
+ const ScopeSet& scopes,
+ Consumer* consumer) {
+ return StartRequestForClientWithContext(
+ getter,
+ GaiaUrls::GetInstance()->oauth2_chrome_client_id(),
+ GaiaUrls::GetInstance()->oauth2_chrome_client_secret(),
+ scopes,
+ consumer);
+}
+
+scoped_ptr<OAuth2TokenService::Request>
+OAuth2TokenService::StartRequestForClientWithContext(
+ 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()) {
+ 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>();
+ }
+
+ if (HasCacheEntry(scopes)) {
+ StartCacheLookupRequest(request.get(), scopes, consumer);
+ } else {
+ FetchOAuth2Token(request.get(),
+ getter,
+ client_id,
+ client_secret,
+ scopes);
+ }
+ return request.PassAs<Request>();
+}
+
+void OAuth2TokenService::FetchOAuth2Token(RequestImpl* request,
+ net::URLRequestContextGetter* getter,
+ const std::string& client_id,
+ const std::string& client_secret,
+ const ScopeSet& scopes) {
+ std::string refresh_token = GetRefreshToken();
+
+ // If there is already a pending fetcher for |scopes| and |refresh_token|,
+ // simply register this |request| for those results rather than starting
+ // a new fetcher.
+ FetchParameters fetch_parameters = std::make_pair(refresh_token, scopes);
+ std::map<FetchParameters, Fetcher*>::iterator iter =
+ pending_fetchers_.find(fetch_parameters);
+ if (iter != pending_fetchers_.end()) {
+ iter->second->AddWaitingRequest(request->AsWeakPtr());
+ return;
+ }
+
+ pending_fetchers_[fetch_parameters] =
+ Fetcher::CreateAndStart(this,
+ getter,
+ client_id,
+ client_secret,
+ refresh_token,
+ scopes,
+ request->AsWeakPtr());
+}
+
+void OAuth2TokenService::StartCacheLookupRequest(
+ RequestImpl* request,
+ const OAuth2TokenService::ScopeSet& scopes,
+ OAuth2TokenService::Consumer* consumer) {
+ CHECK(HasCacheEntry(scopes));
+ const CacheEntry* cache_entry = GetCacheEntry(scopes);
+ 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 ScopeSet& scopes,
+ const std::string& invalid_token) {
+ DCHECK(CalledOnValidThread());
+ RemoveCacheEntry(scopes, invalid_token);
+}
+
+void OAuth2TokenService::OnFetchComplete(Fetcher* fetcher) {
+ DCHECK(CalledOnValidThread());
+
+ // Update the auth error state so auth errors are appropriately communicated
+ // to the user.
+ UpdateAuthError(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<FetchParameters, Fetcher*>::iterator iter =
+ pending_fetchers_.find(std::make_pair(
+ fetcher->GetRefreshToken(), fetcher->GetScopeSet()));
+ DCHECK(iter != pending_fetchers_.end());
+ DCHECK_EQ(fetcher, iter->second);
+ pending_fetchers_.erase(iter);
+}
+
+bool OAuth2TokenService::HasCacheEntry(
+ const OAuth2TokenService::ScopeSet& scopes) {
+ const CacheEntry* cache_entry = GetCacheEntry(scopes);
+ return cache_entry && cache_entry->access_token.length();
+}
+
+const OAuth2TokenService::CacheEntry* OAuth2TokenService::GetCacheEntry(
+ const OAuth2TokenService::ScopeSet& scopes) {
+ DCHECK(CalledOnValidThread());
+ TokenCache::iterator token_iterator = token_cache_.find(scopes);
+ 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 OAuth2TokenService::ScopeSet& scopes,
+ const std::string& token_to_remove) {
+ DCHECK(CalledOnValidThread());
+ TokenCache::iterator token_iterator = token_cache_.find(scopes);
+ 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& refresh_token,
+ const OAuth2TokenService::ScopeSet& scopes,
+ const std::string& access_token,
+ const base::Time& expiration_date) {
+ DCHECK(CalledOnValidThread());
+
+ CacheEntry& token = token_cache_[scopes];
+ token.access_token = access_token;
+ token.expiration_date = expiration_date;
+}
+
+void OAuth2TokenService::UpdateAuthError(const GoogleServiceAuthError& error) {
+ // Default implementation does nothing.
+}
+
+void OAuth2TokenService::ClearCache() {
+ DCHECK(CalledOnValidThread());
+ token_cache_.clear();
+}
+
+void OAuth2TokenService::CancelAllRequests() {
+ std::vector<Fetcher*> fetchers_to_cancel;
+ for (std::map<FetchParameters, Fetcher*>::iterator iter =
+ pending_fetchers_.begin();
+ iter != pending_fetchers_.end();
+ ++iter) {
+ fetchers_to_cancel.push_back(iter->second);
+ }
+ CancelFetchers(fetchers_to_cancel);
+}
+
+void OAuth2TokenService::CancelRequestsForToken(
+ const std::string& refresh_token) {
+ std::vector<Fetcher*> fetchers_to_cancel;
+ for (std::map<FetchParameters, Fetcher*>::iterator iter =
+ pending_fetchers_.begin();
+ iter != pending_fetchers_.end();
+ ++iter) {
+ if (iter->first.first == refresh_token)
+ 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());
+}
+
+void OAuth2TokenService::FireRefreshTokensCleared() {
+ FOR_EACH_OBSERVER(Observer, observer_list_, OnRefreshTokensCleared());
+}
+
+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;
+}
diff --git a/google_apis/gaia/oauth2_token_service.h b/google_apis/gaia/oauth2_token_service.h
new file mode 100644
index 0000000..967dc93
--- /dev/null
+++ b/google_apis/gaia/oauth2_token_service.h
@@ -0,0 +1,285 @@
+// 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/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 "google_apis/gaia/google_service_auth_error.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.
+//
+// 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.
+ // TODO(rogerta): may get rid of |error| argument for OnRefreshTokenRevoked()
+ // once we stop supporting ClientLogin. Need to evaluate if its still useful.
+ 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() {}
+ // Called after all refresh tokens are removed from OAuth2TokenService.
+ virtual void OnRefreshTokensCleared() {}
+ 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, and if not found starts
+ // a request for an OAuth2 access token using the OAuth2 refresh token
+ // maintained by this instance. 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 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& 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(
+ net::URLRequestContextGetter* getter,
+ const ScopeSet& scopes,
+ Consumer* consumer);
+
+ // Returns true if a refresh token exists. If false, calls to
+ // |StartRequest| will result in a Consumer::OnGetTokenFailure callback.
+ virtual bool RefreshTokenIsAvailable();
+
+ // Mark an OAuth2 access token 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.
+ virtual void InvalidateToken(const ScopeSet& scopes,
+ const std::string& invalid_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);
+
+ 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 refresh token maintained.
+ // If no token is available, return an empty string.
+ virtual std::string GetRefreshToken() = 0;
+
+ // Subclasses can override if they want to report errors to the user.
+ virtual void UpdateAuthError(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& refresh_token,
+ const ScopeSet& scopes,
+ const std::string& access_token,
+ const base::Time& expiration_date);
+
+ // Returns true if GetCacheEntry would return a valid cache entry for the
+ // given scopes.
+ bool HasCacheEntry(const ScopeSet& 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 ScopeSet& scopes,
+ Consumer* consumer);
+
+ // Clears the internal token cache.
+ void ClearCache();
+
+ // Cancels all requests that are currently in progress.
+ void CancelAllRequests();
+
+ // Cancels all requests related to a given refresh token.
+ void CancelRequestsForToken(const std::string& refresh_token);
+
+ // Called by subclasses to notify observers.
+ void FireRefreshTokenAvailable(const std::string& account_id);
+ void FireRefreshTokenRevoked(const std::string& account_id);
+ void FireRefreshTokensLoaded();
+ void FireRefreshTokensCleared();
+
+ // Derived classes must provide a request context used for fetching access
+ // tokens with the |StartRequest| method.
+ virtual net::URLRequestContextGetter* GetRequestContext() = 0;
+
+ // 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,
+ net::URLRequestContextGetter* getter,
+ const std::string& client_id,
+ const std::string& client_secret,
+ const ScopeSet& scopes);
+
+ private:
+ // Class that fetches an OAuth2 access token for a given set of scopes and
+ // OAuth2 refresh token.
+ class Fetcher;
+ friend class Fetcher;
+
+ // 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(
+ net::URLRequestContextGetter* getter,
+ const std::string& client_id,
+ const std::string& client_secret,
+ const ScopeSet& 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 |scopes| is added before the usage of the
+ // returned entry is done.
+ const CacheEntry* GetCacheEntry(const ScopeSet& 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 OAuth2TokenService::ScopeSet& 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<ScopeSet, CacheEntry> TokenCache;
+ TokenCache token_cache_;
+
+ // The parameters (refresh token and scope set) used to fetch an OAuth2 access
+ // token.
+ typedef std::pair<std::string, ScopeSet> FetchParameters;
+ // A map from fetch parameters to a fetcher that is fetching an OAuth2 access
+ // token using these parameters.
+ std::map<FetchParameters, Fetcher*> 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(OAuth2TokenService);
+};
+
+#endif // GOOGLE_APIS_GAIA_OAUTH2_TOKEN_SERVICE_H_
diff --git a/google_apis/gaia/oauth2_token_service_test_util.cc b/google_apis/gaia/oauth2_token_service_test_util.cc
new file mode 100644
index 0000000..2aae59b
--- /dev/null
+++ b/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/google_apis/gaia/oauth2_token_service_test_util.h b/google_apis/gaia/oauth2_token_service_test_util.h
new file mode 100644
index 0000000..53ccdf8
--- /dev/null
+++ b/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/google_apis/gaia/oauth2_token_service_unittest.cc b/google_apis/gaia/oauth2_token_service_unittest.cc
new file mode 100644
index 0000000..9550fc2
--- /dev/null
+++ b/google_apis/gaia/oauth2_token_service_unittest.cc
@@ -0,0 +1,517 @@
+// 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_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)
+ : oauth2_service_(oauth2_service) {}
+ virtual ~RetryingTestingOAuth2TokenServiceConsumer() {}
+
+ virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) OVERRIDE {
+ TestingOAuth2TokenServiceConsumer::OnGetTokenFailure(request, error);
+ request_.reset(oauth2_service_->StartRequest(
+ std::set<std::string>(), this).release());
+ }
+
+ OAuth2TokenService* oauth2_service_;
+ scoped_ptr<OAuth2TokenService::Request> request_;
+};
+
+class TestOAuth2TokenService : public OAuth2TokenService {
+ public:
+ explicit TestOAuth2TokenService(net::TestURLRequestContextGetter* getter)
+ : request_context_getter_(getter) {
+ }
+
+ void CancelAllRequestsForTest() { CancelAllRequests(); }
+
+ void CancelRequestsForTokenForTest(const std::string& refresh_token) {
+ CancelRequestsForToken(refresh_token);
+ }
+
+ // For testing: set the refresh token to be used.
+ void set_refresh_token(const std::string& refresh_token) {
+ refresh_token_ = refresh_token;
+ }
+
+ protected:
+ virtual std::string GetRefreshToken() OVERRIDE { return refresh_token_; }
+
+ private:
+ // OAuth2TokenService implementation.
+ virtual net::URLRequestContextGetter* GetRequestContext() OVERRIDE {
+ return request_context_getter_.get();
+ }
+
+ std::string refresh_token_;
+ 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())));
+ }
+
+ protected:
+ base::MessageLoopForIO message_loop_; // net:: stuff needs IO message loop.
+ net::TestURLFetcherFactory factory_;
+ scoped_ptr<TestOAuth2TokenService> oauth2_service_;
+ TestingOAuth2TokenServiceConsumer consumer_;
+};
+
+TEST_F(OAuth2TokenServiceTest, NoOAuth2RefreshToken) {
+ scoped_ptr<OAuth2TokenService::Request> request(
+ oauth2_service_->StartRequest(std::set<std::string>(), &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("refreshToken");
+ scoped_ptr<OAuth2TokenService::Request> request(
+ oauth2_service_->StartRequest(std::set<std::string>(), &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);
+ EXPECT_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("refreshToken");
+ scoped_ptr<OAuth2TokenService::Request> request(
+ oauth2_service_->StartRequest(std::set<std::string>(), &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);
+ EXPECT_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) {
+ std::set<std::string> scopes1;
+ scopes1.insert("s1");
+ scopes1.insert("s2");
+ std::set<std::string> scopes1_same;
+ scopes1_same.insert("s2");
+ scopes1_same.insert("s1");
+ std::set<std::string> scopes2;
+ scopes2.insert("s3");
+
+ oauth2_service_->set_refresh_token("refreshToken");
+
+ // First request.
+ scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest(
+ 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);
+ EXPECT_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(scopes1_same, &consumer_));
+ base::RunLoop().RunUntilIdle();
+
+ // No new network fetcher.
+ EXPECT_EQ(fetcher, factory_.GetFetcherByID(0));
+ 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(scopes2, &consumer_));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, consumer_.number_of_successful_tokens_);
+ EXPECT_EQ(0, consumer_.number_of_errors_);
+ fetcher = factory_.GetFetcherByID(0);
+ EXPECT_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("refreshToken");
+
+ // First request.
+ scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest(
+ std::set<std::string>(), &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);
+ EXPECT_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(std::set<std::string>(), &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);
+ EXPECT_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("refreshToken");
+
+ // First request.
+ scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest(
+ std::set<std::string>(), &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);
+ EXPECT_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(std::set<std::string>(), &consumer_));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, consumer_.number_of_successful_tokens_);
+ EXPECT_EQ(0, consumer_.number_of_errors_);
+
+ fetcher = factory_.GetFetcherByID(0);
+ EXPECT_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("refreshToken");
+
+ scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest(
+ std::set<std::string>(), &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);
+ EXPECT_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("refreshToken");
+
+ scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest(
+ std::set<std::string>(), &consumer_));
+ base::RunLoop().RunUntilIdle();
+ net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0);
+ 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("refreshToken");
+
+ scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest(
+ std::set<std::string>(), &consumer_));
+ base::RunLoop().RunUntilIdle();
+ scoped_ptr<OAuth2TokenService::Request> request2(
+ oauth2_service_->StartRequest(std::set<std::string>(), &consumer_));
+ base::RunLoop().RunUntilIdle();
+
+ request.reset();
+
+ net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0);
+ 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("refreshToken");
+ scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest(
+ std::set<std::string>(), &consumer_));
+ base::RunLoop().RunUntilIdle();
+ net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0);
+ 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("");
+ request = oauth2_service_->StartRequest(std::set<std::string>(), &consumer_);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(fetcher, factory_.GetFetcherByID(0));
+ EXPECT_EQ(1, consumer_.number_of_successful_tokens_);
+ EXPECT_EQ(1, consumer_.number_of_errors_);
+}
+
+TEST_F(OAuth2TokenServiceTest,
+ ChangedRefreshTokenDoesNotAffectInFlightRequests) {
+ oauth2_service_->set_refresh_token("first refreshToken");
+ std::set<std::string> scopes;
+ scopes.insert("s1");
+ scopes.insert("s2");
+
+ scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest(
+ scopes, &consumer_));
+ base::RunLoop().RunUntilIdle();
+ net::TestURLFetcher* fetcher1 = factory_.GetFetcherByID(0);
+
+ // Note |request| is still pending when the refresh token changes.
+ oauth2_service_->set_refresh_token("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(scopes, &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("refreshToken");
+ scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest(
+ std::set<std::string>(), &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("refreshToken");
+ RetryingTestingOAuth2TokenServiceConsumer consumer(oauth2_service_.get());
+ scoped_ptr<OAuth2TokenService::Request> request(oauth2_service_->StartRequest(
+ std::set<std::string>(), &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) {
+ std::set<std::string> scopes;
+ oauth2_service_->set_refresh_token("refreshToken");
+
+ // First request.
+ scoped_ptr<OAuth2TokenService::Request> request(
+ oauth2_service_->StartRequest(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);
+ EXPECT_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(scopes, &consumer_));
+ base::RunLoop().RunUntilIdle();
+
+ // No new network fetcher.
+ EXPECT_EQ(fetcher, factory_.GetFetcherByID(0));
+ 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(scopes, consumer_.last_token_);
+ scoped_ptr<OAuth2TokenService::Request> request3(
+ oauth2_service_->StartRequest(scopes, &consumer_));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, consumer_.number_of_successful_tokens_);
+ EXPECT_EQ(0, consumer_.number_of_errors_);
+ fetcher = factory_.GetFetcherByID(0);
+ EXPECT_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("refreshToken");
+ scoped_ptr<OAuth2TokenService::Request> request(
+ oauth2_service_->StartRequest(std::set<std::string>(), &consumer_));
+
+ oauth2_service_->set_refresh_token("refreshToken2");
+ scoped_ptr<OAuth2TokenService::Request> request2(
+ oauth2_service_->StartRequest(std::set<std::string>(), &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, CancelRequestsForToken) {
+ std::set<std::string> scope_set_1;
+ scope_set_1.insert("scope1");
+ scope_set_1.insert("scope2");
+ std::set<std::string> scope_set_2(scope_set_1.begin(), scope_set_1.end());
+ scope_set_2.insert("scope3");
+
+ oauth2_service_->set_refresh_token("refreshToken");
+ scoped_ptr<OAuth2TokenService::Request> request1(
+ oauth2_service_->StartRequest(scope_set_1, &consumer_));
+ scoped_ptr<OAuth2TokenService::Request> request2(
+ oauth2_service_->StartRequest(scope_set_2, &consumer_));
+
+ oauth2_service_->set_refresh_token("refreshToken2");
+ scoped_ptr<OAuth2TokenService::Request> request3(
+ oauth2_service_->StartRequest(scope_set_1, &consumer_));
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(0, consumer_.number_of_successful_tokens_);
+ EXPECT_EQ(0, consumer_.number_of_errors_);
+
+ oauth2_service_->CancelRequestsForTokenForTest("refreshToken");
+
+ EXPECT_EQ(0, consumer_.number_of_successful_tokens_);
+ EXPECT_EQ(2, consumer_.number_of_errors_);
+
+ oauth2_service_->CancelRequestsForTokenForTest("refreshToken2");
+
+ EXPECT_EQ(0, consumer_.number_of_successful_tokens_);
+ EXPECT_EQ(3, consumer_.number_of_errors_);
+}
diff --git a/google_apis/google_apis.gyp b/google_apis/google_apis.gyp
index c67232f..ef492ce 100644
--- a/google_apis/google_apis.gyp
+++ b/google_apis/google_apis.gyp
@@ -83,6 +83,8 @@
'gaia/oauth2_api_call_flow.h',
'gaia/oauth2_mint_token_flow.cc',
'gaia/oauth2_mint_token_flow.h',
+ 'gaia/oauth2_token_service.cc',
+ 'gaia/oauth2_token_service.h',
'google_api_keys.cc',
'google_api_keys.h',
],