// 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;
class OAuth2AccessTokenFetcher;
// 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:
// A set of scopes in OAuth2 authentication.
typedef std::set<std::string> ScopeSet;
// Class representing a request that fetches an OAuth2 access token.
class Request {
public:
virtual ~Request();
virtual std::string GetAccountId() const = 0;
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(const std::string& id);
virtual ~Consumer();
std::string id() const { return id_; }
// |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;
private:
std::string id_;
};
// Classes that want to listen for refresh 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() {}
};
// Classes that want to monitor status of access token and access token
// request should implement this interface and register with the
// AddDiagnosticsObserver() call.
class DiagnosticsObserver {
public:
// Called when receiving request for access token.
virtual void OnAccessTokenRequested(const std::string& account_id,
const std::string& consumer_id,
const ScopeSet& scopes) = 0;
// Called when access token fetching finished successfully or
// unsuccessfully. |expiration_time| are only valid with
// successful completion.
virtual void OnFetchAccessTokenComplete(const std::string& account_id,
const std::string& consumer_id,
const ScopeSet& scopes,
GoogleServiceAuthError error,
base::Time expiration_time) = 0;
virtual void OnTokenRemoved(const std::string& account_id,
const ScopeSet& scopes) = 0;
};
OAuth2TokenService();
virtual ~OAuth2TokenService();
// Add or remove observers of this token service.
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
// Add or remove observers of this token service.
void AddDiagnosticsObserver(DiagnosticsObserver* observer);
void RemoveDiagnosticsObserver(DiagnosticsObserver* 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.
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) const = 0;
// 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(const std::string& account_id, Consumer* consumer);
virtual ~RequestImpl();
// Overridden from Request:
virtual std::string GetAccountId() const OVERRIDE;
std::string GetConsumerId() const;
// 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.
const std::string account_id_;
Consumer* const consumer_;
};
// 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.
virtual void FireRefreshTokenAvailable(const std::string& account_id);
virtual void FireRefreshTokenRevoked(const std::string& account_id);
virtual 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);
// Creates an access token fetcher for the given account id.
//
// Subclasses should override to create an access token fetcher for the given
// |account_id|. This method is only called if subclasses use the default
// implementation of |FetchOAuth2Token|.
virtual OAuth2AccessTokenFetcher* CreateAccessTokenFetcher(
const std::string& account_id,
net::URLRequestContextGetter* getter,
OAuth2AccessTokenConsumer* consumer) = 0;
// 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 refresh token availability changes.
// Makes sure list is empty on destruction.
ObserverList<Observer, true> observer_list_;
// List of observers to notify when access token status changes.
ObserverList<DiagnosticsObserver, true> diagnostics_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_