diff options
author | kinaba@chromium.org <kinaba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-26 09:24:19 +0000 |
---|---|---|
committer | kinaba@chromium.org <kinaba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-26 09:24:19 +0000 |
commit | 42f41216f1ef0dbe2f782d9411bc888bb68ef263 (patch) | |
tree | a28a0cd3880ff551a5780c1c1b636335c53cbaba /google_apis/gaia/oauth2_token_service.cc | |
parent | c852b7ad4d99658dddf8fe28265855bcd49fafa8 (diff) | |
download | chromium_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/gaia/oauth2_token_service.cc')
-rw-r--r-- | google_apis/gaia/oauth2_token_service.cc | 608 |
1 files changed, 608 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; +} |