Home | History | Annotate | Download | only in browser
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "components/signin/core/browser/mutable_profile_oauth2_token_service.h"
      6 
      7 #include "components/signin/core/browser/signin_client.h"
      8 #include "components/signin/core/browser/signin_metrics.h"
      9 #include "components/signin/core/browser/webdata/token_web_data.h"
     10 #include "components/webdata/common/web_data_service_base.h"
     11 #include "google_apis/gaia/gaia_auth_fetcher.h"
     12 #include "google_apis/gaia/gaia_constants.h"
     13 #include "google_apis/gaia/google_service_auth_error.h"
     14 #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
     15 #include "net/url_request/url_request_context_getter.h"
     16 
     17 namespace {
     18 
     19 const char kAccountIdPrefix[] = "AccountId-";
     20 const size_t kAccountIdPrefixLength = 10;
     21 
     22 std::string ApplyAccountIdPrefix(const std::string& account_id) {
     23   return kAccountIdPrefix + account_id;
     24 }
     25 
     26 bool IsLegacyRefreshTokenId(const std::string& service_id) {
     27   return service_id == GaiaConstants::kGaiaOAuth2LoginRefreshToken;
     28 }
     29 
     30 bool IsLegacyServiceId(const std::string& account_id) {
     31   return account_id.compare(0u, kAccountIdPrefixLength, kAccountIdPrefix) != 0;
     32 }
     33 
     34 std::string RemoveAccountIdPrefix(const std::string& prefixed_account_id) {
     35   return prefixed_account_id.substr(kAccountIdPrefixLength);
     36 }
     37 
     38 }  // namespace
     39 
     40 // This class sends a request to GAIA to revoke the given refresh token from
     41 // the server.  This is a best effort attempt only.  This class deletes itself
     42 // when done sucessfully or otherwise.
     43 class MutableProfileOAuth2TokenService::RevokeServerRefreshToken
     44     : public GaiaAuthConsumer {
     45  public:
     46   RevokeServerRefreshToken(MutableProfileOAuth2TokenService* token_service,
     47                            const std::string& account_id);
     48   virtual ~RevokeServerRefreshToken();
     49 
     50  private:
     51   // GaiaAuthConsumer overrides:
     52   virtual void OnOAuth2RevokeTokenCompleted() OVERRIDE;
     53 
     54   MutableProfileOAuth2TokenService* token_service_;
     55   GaiaAuthFetcher fetcher_;
     56 
     57   DISALLOW_COPY_AND_ASSIGN(RevokeServerRefreshToken);
     58 };
     59 
     60 MutableProfileOAuth2TokenService::
     61     RevokeServerRefreshToken::RevokeServerRefreshToken(
     62     MutableProfileOAuth2TokenService* token_service,
     63     const std::string& refresh_token)
     64     : token_service_(token_service),
     65       fetcher_(this, GaiaConstants::kChromeSource,
     66                token_service_->GetRequestContext()) {
     67   fetcher_.StartRevokeOAuth2Token(refresh_token);
     68 }
     69 
     70 MutableProfileOAuth2TokenService::
     71     RevokeServerRefreshToken::~RevokeServerRefreshToken() {}
     72 
     73 void MutableProfileOAuth2TokenService::
     74     RevokeServerRefreshToken::OnOAuth2RevokeTokenCompleted() {
     75   // |this| pointer will be deleted when removed from the vector, so don't
     76   // access any members after call to erase().
     77   token_service_->server_revokes_.erase(
     78       std::find(token_service_->server_revokes_.begin(),
     79                 token_service_->server_revokes_.end(),
     80                 this));
     81 }
     82 
     83 MutableProfileOAuth2TokenService::AccountInfo::AccountInfo(
     84     ProfileOAuth2TokenService* token_service,
     85     const std::string& account_id,
     86     const std::string& refresh_token)
     87   : token_service_(token_service),
     88     account_id_(account_id),
     89     refresh_token_(refresh_token),
     90     last_auth_error_(GoogleServiceAuthError::NONE) {
     91   DCHECK(token_service_);
     92   DCHECK(!account_id_.empty());
     93   token_service_->signin_error_controller()->AddProvider(this);
     94 }
     95 
     96 MutableProfileOAuth2TokenService::AccountInfo::~AccountInfo() {
     97   token_service_->signin_error_controller()->RemoveProvider(this);
     98 }
     99 
    100 void MutableProfileOAuth2TokenService::AccountInfo::SetLastAuthError(
    101     const GoogleServiceAuthError& error) {
    102   if (error.state() != last_auth_error_.state()) {
    103     last_auth_error_ = error;
    104     token_service_->signin_error_controller()->AuthStatusChanged();
    105   }
    106 }
    107 
    108 std::string
    109 MutableProfileOAuth2TokenService::AccountInfo::GetAccountId() const {
    110   return account_id_;
    111 }
    112 
    113 std::string
    114 MutableProfileOAuth2TokenService::AccountInfo::GetUsername() const {
    115   // TODO(rogerta): when |account_id| becomes the obfuscated gaia id, this
    116   // will need to be changed.
    117   return account_id_;
    118 }
    119 
    120 GoogleServiceAuthError
    121 MutableProfileOAuth2TokenService::AccountInfo::GetAuthStatus() const {
    122   return last_auth_error_;
    123 }
    124 
    125 MutableProfileOAuth2TokenService::MutableProfileOAuth2TokenService()
    126     : web_data_service_request_(0)  {
    127 }
    128 
    129 MutableProfileOAuth2TokenService::~MutableProfileOAuth2TokenService() {
    130   DCHECK(server_revokes_.empty());
    131 }
    132 
    133 void MutableProfileOAuth2TokenService::Shutdown() {
    134   server_revokes_.clear();
    135   CancelWebTokenFetch();
    136   CancelAllRequests();
    137   refresh_tokens_.clear();
    138 
    139   ProfileOAuth2TokenService::Shutdown();
    140 }
    141 
    142 bool MutableProfileOAuth2TokenService::RefreshTokenIsAvailable(
    143     const std::string& account_id) const {
    144   return !GetRefreshToken(account_id).empty();
    145 }
    146 
    147 std::string MutableProfileOAuth2TokenService::GetRefreshToken(
    148     const std::string& account_id) const {
    149   AccountInfoMap::const_iterator iter = refresh_tokens_.find(account_id);
    150   if (iter != refresh_tokens_.end())
    151     return iter->second->refresh_token();
    152   return std::string();
    153 }
    154 
    155 OAuth2AccessTokenFetcher*
    156 MutableProfileOAuth2TokenService::CreateAccessTokenFetcher(
    157     const std::string& account_id,
    158     net::URLRequestContextGetter* getter,
    159     OAuth2AccessTokenConsumer* consumer) {
    160   std::string refresh_token = GetRefreshToken(account_id);
    161   DCHECK(!refresh_token.empty());
    162   return new OAuth2AccessTokenFetcherImpl(consumer, getter, refresh_token);
    163 }
    164 
    165 net::URLRequestContextGetter*
    166 MutableProfileOAuth2TokenService::GetRequestContext() {
    167   return client()->GetURLRequestContext();
    168 }
    169 
    170 void MutableProfileOAuth2TokenService::LoadCredentials(
    171     const std::string& primary_account_id) {
    172   DCHECK(!primary_account_id.empty());
    173   DCHECK(loading_primary_account_id_.empty());
    174   DCHECK_EQ(0, web_data_service_request_);
    175 
    176   CancelAllRequests();
    177   refresh_tokens().clear();
    178   loading_primary_account_id_ = primary_account_id;
    179   scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
    180   if (token_web_data.get())
    181     web_data_service_request_ = token_web_data->GetAllTokens(this);
    182 }
    183 
    184 void MutableProfileOAuth2TokenService::OnWebDataServiceRequestDone(
    185     WebDataServiceBase::Handle handle,
    186     const WDTypedResult* result) {
    187   DCHECK_EQ(web_data_service_request_, handle);
    188   web_data_service_request_ = 0;
    189 
    190   if (result) {
    191     DCHECK(result->GetType() == TOKEN_RESULT);
    192     const WDResult<std::map<std::string, std::string> > * token_result =
    193         static_cast<const WDResult<std::map<std::string, std::string> > * > (
    194             result);
    195     LoadAllCredentialsIntoMemory(token_result->GetValue());
    196   }
    197 
    198   // Make sure that we have an entry for |loading_primary_account_id_| in the
    199   // map.  The entry could be missing if there is a corruption in the token DB
    200   // while this profile is connected to an account.
    201   DCHECK(!loading_primary_account_id_.empty());
    202   if (refresh_tokens().count(loading_primary_account_id_) == 0) {
    203     refresh_tokens()[loading_primary_account_id_].reset(
    204         new AccountInfo(this, loading_primary_account_id_, std::string()));
    205   }
    206 
    207   // If we don't have a refresh token for a known account, signal an error.
    208   for (AccountInfoMap::const_iterator i = refresh_tokens_.begin();
    209        i != refresh_tokens_.end(); ++i) {
    210     if (!RefreshTokenIsAvailable(i->first)) {
    211       UpdateAuthError(
    212           i->first,
    213           GoogleServiceAuthError(
    214               GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
    215       break;
    216     }
    217   }
    218 
    219   loading_primary_account_id_.clear();
    220 }
    221 
    222 void MutableProfileOAuth2TokenService::LoadAllCredentialsIntoMemory(
    223     const std::map<std::string, std::string>& db_tokens) {
    224   std::string old_login_token;
    225 
    226   {
    227     ScopedBacthChange batch(this);
    228 
    229     for (std::map<std::string, std::string>::const_iterator iter =
    230              db_tokens.begin();
    231          iter != db_tokens.end();
    232          ++iter) {
    233       std::string prefixed_account_id = iter->first;
    234       std::string refresh_token = iter->second;
    235 
    236       if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty())
    237         old_login_token = refresh_token;
    238 
    239       if (IsLegacyServiceId(prefixed_account_id)) {
    240         scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
    241         if (token_web_data.get())
    242           token_web_data->RemoveTokenForService(prefixed_account_id);
    243       } else {
    244         DCHECK(!refresh_token.empty());
    245         std::string account_id = RemoveAccountIdPrefix(prefixed_account_id);
    246         refresh_tokens()[account_id].reset(
    247             new AccountInfo(this, account_id, refresh_token));
    248         FireRefreshTokenAvailable(account_id);
    249         // TODO(fgorski): Notify diagnostic observers.
    250       }
    251     }
    252 
    253     if (!old_login_token.empty()) {
    254       DCHECK(!loading_primary_account_id_.empty());
    255       if (refresh_tokens().count(loading_primary_account_id_) == 0)
    256         UpdateCredentials(loading_primary_account_id_, old_login_token);
    257     }
    258   }
    259 
    260   FireRefreshTokensLoaded();
    261 }
    262 
    263 void MutableProfileOAuth2TokenService::UpdateAuthError(
    264     const std::string& account_id,
    265     const GoogleServiceAuthError& error) {
    266   // Do not report connection errors as these are not actually auth errors.
    267   // We also want to avoid masking a "real" auth error just because we
    268   // subsequently get a transient network error.
    269   if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED ||
    270       error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE)
    271     return;
    272 
    273   if (refresh_tokens_.count(account_id) == 0) {
    274     // This could happen if the preferences have been corrupted (see
    275     // http://crbug.com/321370). In a Debug build that would be a bug, but in a
    276     // Release build we want to deal with it gracefully.
    277     NOTREACHED();
    278     return;
    279   }
    280   refresh_tokens_[account_id]->SetLastAuthError(error);
    281 }
    282 
    283 std::vector<std::string> MutableProfileOAuth2TokenService::GetAccounts() {
    284   std::vector<std::string> account_ids;
    285   for (AccountInfoMap::const_iterator iter = refresh_tokens_.begin();
    286            iter != refresh_tokens_.end(); ++iter) {
    287     account_ids.push_back(iter->first);
    288   }
    289   return account_ids;
    290 }
    291 
    292 void MutableProfileOAuth2TokenService::UpdateCredentials(
    293     const std::string& account_id,
    294     const std::string& refresh_token) {
    295   DCHECK(thread_checker_.CalledOnValidThread());
    296   DCHECK(!account_id.empty());
    297   DCHECK(!refresh_token.empty());
    298 
    299   signin_metrics::LogSigninAddAccount();
    300 
    301   bool refresh_token_present = refresh_tokens_.count(account_id) > 0;
    302   if (!refresh_token_present ||
    303       refresh_tokens_[account_id]->refresh_token() != refresh_token) {
    304     ScopedBacthChange batch(this);
    305 
    306     // If token present, and different from the new one, cancel its requests,
    307     // and clear the entries in cache related to that account.
    308     if (refresh_token_present) {
    309       std::string revoke_reason = refresh_token_present ? "token differs" :
    310                                                           "token is missing";
    311       LOG(WARNING) << "Revoking refresh token on server. "
    312                    << "Reason: token update, " << revoke_reason;
    313       RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token());
    314       CancelRequestsForAccount(account_id);
    315       ClearCacheForAccount(account_id);
    316       refresh_tokens_[account_id]->set_refresh_token(refresh_token);
    317     } else {
    318       refresh_tokens_[account_id].reset(
    319           new AccountInfo(this, account_id, refresh_token));
    320     }
    321 
    322     // Save the token in memory and in persistent store.
    323     PersistCredentials(account_id, refresh_token);
    324 
    325     UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
    326     FireRefreshTokenAvailable(account_id);
    327   }
    328 }
    329 
    330 void MutableProfileOAuth2TokenService::RevokeCredentials(
    331     const std::string& account_id) {
    332   DCHECK(thread_checker_.CalledOnValidThread());
    333 
    334   if (refresh_tokens_.count(account_id) > 0) {
    335     ScopedBacthChange batch(this);
    336     RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token());
    337     CancelRequestsForAccount(account_id);
    338     ClearCacheForAccount(account_id);
    339     refresh_tokens_.erase(account_id);
    340     ClearPersistedCredentials(account_id);
    341     FireRefreshTokenRevoked(account_id);
    342   }
    343 }
    344 
    345 void MutableProfileOAuth2TokenService::PersistCredentials(
    346     const std::string& account_id,
    347     const std::string& refresh_token) {
    348   scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
    349   if (token_web_data.get()) {
    350     token_web_data->SetTokenForService(ApplyAccountIdPrefix(account_id),
    351                                        refresh_token);
    352   }
    353 }
    354 
    355 void MutableProfileOAuth2TokenService::ClearPersistedCredentials(
    356     const std::string& account_id) {
    357   scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
    358   if (token_web_data.get())
    359     token_web_data->RemoveTokenForService(ApplyAccountIdPrefix(account_id));
    360 }
    361 
    362 void MutableProfileOAuth2TokenService::RevokeAllCredentials() {
    363   if (!client()->CanRevokeCredentials())
    364     return;
    365   DCHECK(thread_checker_.CalledOnValidThread());
    366 
    367   ScopedBacthChange batch(this);
    368 
    369   CancelWebTokenFetch();
    370   CancelAllRequests();
    371   ClearCache();
    372   AccountInfoMap tokens = refresh_tokens_;
    373   for (AccountInfoMap::iterator i = tokens.begin(); i != tokens.end(); ++i)
    374     RevokeCredentials(i->first);
    375 
    376   DCHECK_EQ(0u, refresh_tokens_.size());
    377 }
    378 
    379 void MutableProfileOAuth2TokenService::RevokeCredentialsOnServer(
    380     const std::string& refresh_token) {
    381   // Keep track or all server revoke requests.  This way they can be deleted
    382   // before the token service is shutdown and won't outlive the profile.
    383   server_revokes_.push_back(
    384       new RevokeServerRefreshToken(this, refresh_token));
    385 }
    386 
    387 void MutableProfileOAuth2TokenService::CancelWebTokenFetch() {
    388   if (web_data_service_request_ != 0) {
    389     scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase();
    390     DCHECK(token_web_data.get());
    391     token_web_data->CancelRequest(web_data_service_request_);
    392     web_data_service_request_  = 0;
    393   }
    394 }
    395