root/components/signin/core/browser/mutable_profile_oauth2_token_service.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ApplyAccountIdPrefix
  2. IsLegacyRefreshTokenId
  3. IsLegacyServiceId
  4. RemoveAccountIdPrefix
  5. fetcher_
  6. OnOAuth2RevokeTokenCompleted
  7. last_auth_error_
  8. SetLastAuthError
  9. GetAccountId
  10. GetAuthStatus
  11. Shutdown
  12. RefreshTokenIsAvailable
  13. GetRefreshToken
  14. CreateAccessTokenFetcher
  15. GetRequestContext
  16. LoadCredentials
  17. OnWebDataServiceRequestDone
  18. LoadAllCredentialsIntoMemory
  19. UpdateAuthError
  20. GetAccounts
  21. UpdateCredentials
  22. RevokeCredentials
  23. PersistCredentials
  24. ClearPersistedCredentials
  25. RevokeAllCredentials
  26. RevokeCredentialsOnServer
  27. CancelWebTokenFetch

// Copyright 2014 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 "components/signin/core/browser/mutable_profile_oauth2_token_service.h"

#include "components/signin/core/browser/signin_client.h"
#include "components/signin/core/browser/webdata/token_web_data.h"
#include "components/webdata/common/web_data_service_base.h"
#include "google_apis/gaia/gaia_auth_fetcher.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/google_service_auth_error.h"
#include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
#include "net/url_request/url_request_context_getter.h"

namespace {

const char kAccountIdPrefix[] = "AccountId-";
const size_t kAccountIdPrefixLength = 10;

std::string ApplyAccountIdPrefix(const std::string& account_id) {
  return kAccountIdPrefix + account_id;
}

bool IsLegacyRefreshTokenId(const std::string& service_id) {
  return service_id == GaiaConstants::kGaiaOAuth2LoginRefreshToken;
}

bool IsLegacyServiceId(const std::string& account_id) {
  return account_id.compare(0u, kAccountIdPrefixLength, kAccountIdPrefix) != 0;
}

std::string RemoveAccountIdPrefix(const std::string& prefixed_account_id) {
  return prefixed_account_id.substr(kAccountIdPrefixLength);
}

}  // namespace

// This class sends a request to GAIA to revoke the given refresh token from
// the server.  This is a best effort attempt only.  This class deletes itself
// when done sucessfully or otherwise.
class MutableProfileOAuth2TokenService::RevokeServerRefreshToken
    : public GaiaAuthConsumer {
 public:
  RevokeServerRefreshToken(MutableProfileOAuth2TokenService* token_service,
                           const std::string& account_id);
  virtual ~RevokeServerRefreshToken();

 private:
  // GaiaAuthConsumer overrides:
  virtual void OnOAuth2RevokeTokenCompleted() OVERRIDE;

  MutableProfileOAuth2TokenService* token_service_;
  GaiaAuthFetcher fetcher_;

  DISALLOW_COPY_AND_ASSIGN(RevokeServerRefreshToken);
};

MutableProfileOAuth2TokenService::
    RevokeServerRefreshToken::RevokeServerRefreshToken(
    MutableProfileOAuth2TokenService* token_service,
    const std::string& refresh_token)
    : token_service_(token_service),
      fetcher_(this, GaiaConstants::kChromeSource,
               token_service_->GetRequestContext()) {
  fetcher_.StartRevokeOAuth2Token(refresh_token);
}

MutableProfileOAuth2TokenService::
    RevokeServerRefreshToken::~RevokeServerRefreshToken() {}

void MutableProfileOAuth2TokenService::
    RevokeServerRefreshToken::OnOAuth2RevokeTokenCompleted() {
  // |this| pointer will be deleted when removed from the vector, so don't
  // access any members after call to erase().
  token_service_->server_revokes_.erase(
      std::find(token_service_->server_revokes_.begin(),
                token_service_->server_revokes_.end(),
                this));
}

MutableProfileOAuth2TokenService::AccountInfo::AccountInfo(
    ProfileOAuth2TokenService* token_service,
    const std::string& account_id,
    const std::string& refresh_token)
  : token_service_(token_service),
    account_id_(account_id),
    refresh_token_(refresh_token),
    last_auth_error_(GoogleServiceAuthError::NONE) {
  DCHECK(token_service_);
  DCHECK(!account_id_.empty());
  token_service_->signin_error_controller()->AddProvider(this);
}

MutableProfileOAuth2TokenService::AccountInfo::~AccountInfo() {
  token_service_->signin_error_controller()->RemoveProvider(this);
}

void MutableProfileOAuth2TokenService::AccountInfo::SetLastAuthError(
    const GoogleServiceAuthError& error) {
  if (error.state() != last_auth_error_.state()) {
    last_auth_error_ = error;
    token_service_->signin_error_controller()->AuthStatusChanged();
  }
}

std::string
MutableProfileOAuth2TokenService::AccountInfo::GetAccountId() const {
  return account_id_;
}

GoogleServiceAuthError
MutableProfileOAuth2TokenService::AccountInfo::GetAuthStatus() const {
  return last_auth_error_;
}

MutableProfileOAuth2TokenService::MutableProfileOAuth2TokenService()
    : web_data_service_request_(0)  {
}

MutableProfileOAuth2TokenService::~MutableProfileOAuth2TokenService() {
  DCHECK(server_revokes_.empty());
}

void MutableProfileOAuth2TokenService::Shutdown() {
  server_revokes_.clear();
  CancelWebTokenFetch();
  CancelAllRequests();
  refresh_tokens_.clear();

  ProfileOAuth2TokenService::Shutdown();
}

bool MutableProfileOAuth2TokenService::RefreshTokenIsAvailable(
    const std::string& account_id) const {
  return !GetRefreshToken(account_id).empty();
}

std::string MutableProfileOAuth2TokenService::GetRefreshToken(
    const std::string& account_id) const {
  AccountInfoMap::const_iterator iter = refresh_tokens_.find(account_id);
  if (iter != refresh_tokens_.end())
    return iter->second->refresh_token();
  return std::string();
}

OAuth2AccessTokenFetcher*
MutableProfileOAuth2TokenService::CreateAccessTokenFetcher(
    const std::string& account_id,
    net::URLRequestContextGetter* getter,
    OAuth2AccessTokenConsumer* consumer) {
  std::string refresh_token = GetRefreshToken(account_id);
  DCHECK(!refresh_token.empty());
  return new OAuth2AccessTokenFetcherImpl(consumer, getter, refresh_token);
}

net::URLRequestContextGetter*
MutableProfileOAuth2TokenService::GetRequestContext() {
  return client()->GetURLRequestContext();
}

void MutableProfileOAuth2TokenService::LoadCredentials(
    const std::string& primary_account_id) {
  DCHECK(!primary_account_id.empty());
  DCHECK(loading_primary_account_id_.empty());
  DCHECK_EQ(0, web_data_service_request_);

  CancelAllRequests();
  refresh_tokens().clear();
  loading_primary_account_id_ = primary_account_id;
  scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
  if (token_web_data.get())
    web_data_service_request_ = token_web_data->GetAllTokens(this);
}

void MutableProfileOAuth2TokenService::OnWebDataServiceRequestDone(
    WebDataServiceBase::Handle handle,
    const WDTypedResult* result) {
  DCHECK_EQ(web_data_service_request_, handle);
  web_data_service_request_ = 0;

  if (result) {
    DCHECK(result->GetType() == TOKEN_RESULT);
    const WDResult<std::map<std::string, std::string> > * token_result =
        static_cast<const WDResult<std::map<std::string, std::string> > * > (
            result);
    LoadAllCredentialsIntoMemory(token_result->GetValue());
  }

  // Make sure that we have an entry for |loading_primary_account_id_| in the
  // map.  The entry could be missing if there is a corruption in the token DB
  // while this profile is connected to an account.
  DCHECK(!loading_primary_account_id_.empty());
  if (refresh_tokens().count(loading_primary_account_id_) == 0) {
    refresh_tokens()[loading_primary_account_id_].reset(
        new AccountInfo(this, loading_primary_account_id_, std::string()));
  }

  // If we don't have a refresh token for a known account, signal an error.
  for (AccountInfoMap::const_iterator i = refresh_tokens_.begin();
       i != refresh_tokens_.end(); ++i) {
    if (!RefreshTokenIsAvailable(i->first)) {
      UpdateAuthError(
          i->first,
          GoogleServiceAuthError(
              GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
      break;
    }
  }

  loading_primary_account_id_.clear();
}

void MutableProfileOAuth2TokenService::LoadAllCredentialsIntoMemory(
    const std::map<std::string, std::string>& db_tokens) {
  std::string old_login_token;

  for (std::map<std::string, std::string>::const_iterator iter =
           db_tokens.begin();
       iter != db_tokens.end();
       ++iter) {
    std::string prefixed_account_id = iter->first;
    std::string refresh_token = iter->second;

    if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty())
      old_login_token = refresh_token;

    if (IsLegacyServiceId(prefixed_account_id)) {
      scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
      if (token_web_data.get())
        token_web_data->RemoveTokenForService(prefixed_account_id);
    } else {
      DCHECK(!refresh_token.empty());
      std::string account_id = RemoveAccountIdPrefix(prefixed_account_id);
      refresh_tokens()[account_id].reset(
          new AccountInfo(this, account_id, refresh_token));
      FireRefreshTokenAvailable(account_id);
      // TODO(fgorski): Notify diagnostic observers.
    }
  }

  if (!old_login_token.empty()) {
    DCHECK(!loading_primary_account_id_.empty());
    if (refresh_tokens().count(loading_primary_account_id_) == 0)
      UpdateCredentials(loading_primary_account_id_, old_login_token);
  }

  FireRefreshTokensLoaded();
}

void MutableProfileOAuth2TokenService::UpdateAuthError(
    const std::string& account_id,
    const GoogleServiceAuthError& error) {
  // Do not report connection errors as these are not actually auth errors.
  // We also want to avoid masking a "real" auth error just because we
  // subsequently get a transient network error.
  if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED ||
      error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE)
    return;

#if defined(OS_IOS)
  // ProfileOauth2TokenService does not manage the refresh tokens on iOS - the
  // account info on iOS is only used to manage the authentication error state.
  // Simply add an account info entry with empty refresh token if none exists.
  if (refresh_tokens_.count(account_id) == 0) {
      refresh_tokens_[account_id].reset(
          new AccountInfo(this, account_id, std::string()));
  }
#endif

  if (refresh_tokens_.count(account_id) == 0) {
    // This could happen if the preferences have been corrupted (see
    // http://crbug.com/321370). In a Debug build that would be a bug, but in a
    // Release build we want to deal with it gracefully.
    NOTREACHED();
    return;
  }
  refresh_tokens_[account_id]->SetLastAuthError(error);
}

std::vector<std::string> MutableProfileOAuth2TokenService::GetAccounts() {
  std::vector<std::string> account_ids;
  for (AccountInfoMap::const_iterator iter = refresh_tokens_.begin();
           iter != refresh_tokens_.end(); ++iter) {
    account_ids.push_back(iter->first);
  }
  return account_ids;
}

void MutableProfileOAuth2TokenService::UpdateCredentials(
    const std::string& account_id,
    const std::string& refresh_token) {
  DCHECK(thread_checker_.CalledOnValidThread());
  DCHECK(!account_id.empty());
  DCHECK(!refresh_token.empty());

  bool refresh_token_present = refresh_tokens_.count(account_id) > 0;
  if (!refresh_token_present ||
      refresh_tokens_[account_id]->refresh_token() != refresh_token) {
    // If token present, and different from the new one, cancel its requests,
    // and clear the entries in cache related to that account.
    if (refresh_token_present) {
      std::string revoke_reason = refresh_token_present ? "token differs" :
                                                          "token is missing";
      LOG(WARNING) << "Revoking refresh token on server. "
                   << "Reason: token update, " << revoke_reason;
      RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token());
      CancelRequestsForAccount(account_id);
      ClearCacheForAccount(account_id);
      refresh_tokens_[account_id]->set_refresh_token(refresh_token);
    } else {
      refresh_tokens_[account_id].reset(
          new AccountInfo(this, account_id, refresh_token));
    }

    // Save the token in memory and in persistent store.
    PersistCredentials(account_id, refresh_token);

    UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
    FireRefreshTokenAvailable(account_id);
  }
}

void MutableProfileOAuth2TokenService::RevokeCredentials(
    const std::string& account_id) {
  DCHECK(thread_checker_.CalledOnValidThread());

  if (refresh_tokens_.count(account_id) > 0) {
    RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token());
    CancelRequestsForAccount(account_id);
    ClearCacheForAccount(account_id);
    refresh_tokens_.erase(account_id);
    ClearPersistedCredentials(account_id);
    FireRefreshTokenRevoked(account_id);
  }
}

void MutableProfileOAuth2TokenService::PersistCredentials(
    const std::string& account_id,
    const std::string& refresh_token) {
  scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
  if (token_web_data.get()) {
    token_web_data->SetTokenForService(ApplyAccountIdPrefix(account_id),
                                       refresh_token);
  }
}

void MutableProfileOAuth2TokenService::ClearPersistedCredentials(
    const std::string& account_id) {
  scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
  if (token_web_data.get())
    token_web_data->RemoveTokenForService(ApplyAccountIdPrefix(account_id));
}

void MutableProfileOAuth2TokenService::RevokeAllCredentials() {
  if (!client()->CanRevokeCredentials())
    return;
  DCHECK(thread_checker_.CalledOnValidThread());
  CancelWebTokenFetch();
  CancelAllRequests();
  ClearCache();
  AccountInfoMap tokens = refresh_tokens_;
  for (AccountInfoMap::iterator i = tokens.begin(); i != tokens.end(); ++i)
    RevokeCredentials(i->first);

  DCHECK_EQ(0u, refresh_tokens_.size());
}

void MutableProfileOAuth2TokenService::RevokeCredentialsOnServer(
    const std::string& refresh_token) {
  // Keep track or all server revoke requests.  This way they can be deleted
  // before the token service is shutdown and won't outlive the profile.
  server_revokes_.push_back(
      new RevokeServerRefreshToken(this, refresh_token));
}

void MutableProfileOAuth2TokenService::CancelWebTokenFetch() {
  if (web_data_service_request_ != 0) {
    scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
    DCHECK(token_web_data.get());
    token_web_data->CancelRequest(web_data_service_request_);
    web_data_service_request_  = 0;
  }
}

/* [<][>][^][v][top][bottom][index][help] */