Home | History | Annotate | Download | only in signin
      1 // Copyright 2013 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 "chrome/browser/signin/profile_oauth2_token_service.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/message_loop/message_loop.h"
      9 #include "base/stl_util.h"
     10 #include "base/time/time.h"
     11 #include "chrome/browser/chrome_notification_types.h"
     12 #include "chrome/browser/profiles/profile.h"
     13 #include "chrome/browser/signin/signin_global_error.h"
     14 #include "chrome/browser/signin/signin_manager.h"
     15 #include "chrome/browser/signin/signin_manager_factory.h"
     16 #include "chrome/browser/signin/token_service.h"
     17 #include "chrome/browser/signin/token_service_factory.h"
     18 #include "chrome/browser/ui/global_error/global_error_service.h"
     19 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
     20 #include "chrome/browser/webdata/token_web_data.h"
     21 #include "content/public/browser/browser_thread.h"
     22 #include "content/public/browser/notification_details.h"
     23 #include "content/public/browser/notification_source.h"
     24 #include "google_apis/gaia/gaia_constants.h"
     25 #include "google_apis/gaia/google_service_auth_error.h"
     26 #include "net/url_request/url_request_context_getter.h"
     27 
     28 namespace {
     29 
     30 const char kAccountIdPrefix[] = "AccountId-";
     31 const size_t kAccountIdPrefixLength = 10;
     32 
     33 bool IsLegacyServiceId(const std::string& account_id) {
     34   return account_id.compare(0u, kAccountIdPrefixLength, kAccountIdPrefix) != 0;
     35 }
     36 
     37 bool IsLegacyRefreshTokenId(const std::string& service_id) {
     38   return service_id == GaiaConstants::kGaiaOAuth2LoginRefreshToken;
     39 }
     40 
     41 std::string ApplyAccountIdPrefix(const std::string& account_id) {
     42   return kAccountIdPrefix + account_id;
     43 }
     44 
     45 std::string RemoveAccountIdPrefix(const std::string& prefixed_account_id) {
     46   return prefixed_account_id.substr(kAccountIdPrefixLength);
     47 }
     48 
     49 std::string GetAccountId(Profile* profile) {
     50   SigninManagerBase* signin_manager =
     51       SigninManagerFactory::GetForProfileIfExists(profile);
     52   return signin_manager ? signin_manager->GetAuthenticatedUsername() :
     53       std::string();
     54 }
     55 
     56 }  // namespace
     57 
     58 ProfileOAuth2TokenService::ProfileOAuth2TokenService()
     59     : profile_(NULL),
     60       web_data_service_request_(0),
     61       last_auth_error_(GoogleServiceAuthError::NONE) {
     62 }
     63 
     64 ProfileOAuth2TokenService::~ProfileOAuth2TokenService() {
     65   DCHECK(!signin_global_error_.get()) <<
     66       "ProfileOAuth2TokenService::Initialize called but not "
     67       "ProfileOAuth2TokenService::Shutdown";
     68 }
     69 
     70 void ProfileOAuth2TokenService::Initialize(Profile* profile) {
     71   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
     72 
     73   DCHECK(profile);
     74   DCHECK(!profile_);
     75   profile_ = profile;
     76 
     77   signin_global_error_.reset(new SigninGlobalError(profile));
     78   GlobalErrorServiceFactory::GetForProfile(profile_)->AddGlobalError(
     79       signin_global_error_.get());
     80   signin_global_error_->AddProvider(this);
     81 
     82   content::Source<TokenService> token_service_source(
     83       TokenServiceFactory::GetForProfile(profile));
     84   registrar_.Add(this,
     85                  chrome::NOTIFICATION_TOKENS_CLEARED,
     86                  token_service_source);
     87   registrar_.Add(this,
     88                  chrome::NOTIFICATION_TOKEN_AVAILABLE,
     89                  token_service_source);
     90   registrar_.Add(this,
     91                  chrome::NOTIFICATION_TOKEN_REQUEST_FAILED,
     92                  token_service_source);
     93   registrar_.Add(this,
     94                  chrome::NOTIFICATION_TOKEN_LOADING_FINISHED,
     95                  token_service_source);
     96 }
     97 
     98 void ProfileOAuth2TokenService::Shutdown() {
     99   CancelAllRequests();
    100   signin_global_error_->RemoveProvider(this);
    101   GlobalErrorServiceFactory::GetForProfile(profile_)->RemoveGlobalError(
    102       signin_global_error_.get());
    103   signin_global_error_.reset();
    104 }
    105 
    106 std::string ProfileOAuth2TokenService::GetRefreshToken() {
    107   TokenService* token_service = TokenServiceFactory::GetForProfile(profile_);
    108   if (!token_service || !token_service->HasOAuthLoginToken()) {
    109     return std::string();
    110   }
    111   return token_service->GetOAuth2LoginRefreshToken();
    112 }
    113 
    114 void ProfileOAuth2TokenService::UpdateAuthError(
    115     const GoogleServiceAuthError& error) {
    116   // Do not report connection errors as these are not actually auth errors.
    117   // We also want to avoid masking a "real" auth error just because we
    118   // subsequently get a transient network error.
    119   if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED)
    120     return;
    121 
    122   if (error.state() != last_auth_error_.state()) {
    123     last_auth_error_ = error;
    124     signin_global_error_->AuthStatusChanged();
    125   }
    126 }
    127 
    128 void ProfileOAuth2TokenService::Observe(
    129     int type,
    130     const content::NotificationSource& source,
    131     const content::NotificationDetails& details) {
    132   switch (type) {
    133     case chrome::NOTIFICATION_TOKEN_AVAILABLE: {
    134       TokenService::TokenAvailableDetails* tok_details =
    135           content::Details<TokenService::TokenAvailableDetails>(details).ptr();
    136       if (tok_details->service() ==
    137           GaiaConstants::kGaiaOAuth2LoginRefreshToken) {
    138         // TODO(fgorski): Canceling all requests will not be correct in a
    139         // multi-login environment. We should cancel only the requests related
    140         // to the token being replaced (old token for the same account_id).
    141         // Previous refresh token is not available at this point, but since
    142         // there are no other refresh tokens, we cancel all active requests.
    143         CancelAllRequests();
    144         ClearCache();
    145         UpdateAuthError(GoogleServiceAuthError::AuthErrorNone());
    146         FireRefreshTokenAvailable(GetAccountId(profile_));
    147       }
    148       break;
    149     }
    150     case chrome::NOTIFICATION_TOKEN_REQUEST_FAILED: {
    151       TokenService::TokenRequestFailedDetails* tok_details =
    152           content::Details<TokenService::TokenRequestFailedDetails>(details)
    153               .ptr();
    154       if (tok_details->service() == GaiaConstants::kLSOService ||
    155           tok_details->service() ==
    156               GaiaConstants::kGaiaOAuth2LoginRefreshToken) {
    157         // TODO(fgorski): Canceling all requests will not be correct in a
    158         // multi-login environment. We should cacnel only the requests related
    159         // to the failed refresh token.
    160         // Failed refresh token is not available at this point, but since
    161         // there are no other refresh tokens, we cancel all active requests.
    162         CancelAllRequests();
    163         ClearCache();
    164         UpdateAuthError(tok_details->error());
    165         FireRefreshTokenRevoked(GetAccountId(profile_), tok_details->error());
    166       }
    167       break;
    168     }
    169     case chrome::NOTIFICATION_TOKENS_CLEARED: {
    170       CancelAllRequests();
    171       ClearCache();
    172       UpdateAuthError(GoogleServiceAuthError::AuthErrorNone());
    173       FireRefreshTokensCleared();
    174       break;
    175     }
    176     case chrome::NOTIFICATION_TOKEN_LOADING_FINISHED:
    177       FireRefreshTokensLoaded();
    178       break;
    179     default:
    180       NOTREACHED() << "Invalid notification type=" << type;
    181       break;
    182   }
    183 }
    184 
    185 GoogleServiceAuthError ProfileOAuth2TokenService::GetAuthStatus() const {
    186   return last_auth_error_;
    187 }
    188 
    189 net::URLRequestContextGetter* ProfileOAuth2TokenService::GetRequestContext() {
    190   return profile_->GetRequestContext();
    191 }
    192 
    193 void ProfileOAuth2TokenService::RegisterCacheEntry(
    194     const std::string& refresh_token,
    195     const ScopeSet& scopes,
    196     const std::string& access_token,
    197     const base::Time& expiration_date) {
    198   if (ShouldCacheForRefreshToken(TokenServiceFactory::GetForProfile(profile_),
    199                                  refresh_token)) {
    200     OAuth2TokenService::RegisterCacheEntry(refresh_token,
    201                                            scopes,
    202                                            access_token,
    203                                            expiration_date);
    204   }
    205 }
    206 
    207 bool ProfileOAuth2TokenService::ShouldCacheForRefreshToken(
    208     TokenService *token_service,
    209     const std::string& refresh_token) {
    210   if (!token_service ||
    211       !token_service->HasOAuthLoginToken() ||
    212       token_service->GetOAuth2LoginRefreshToken().compare(refresh_token) != 0) {
    213     DLOG(INFO) <<
    214         "Received a token with a refresh token not maintained by TokenService.";
    215     return false;
    216   }
    217   return true;
    218 }
    219 
    220 void ProfileOAuth2TokenService::UpdateCredentials(
    221     const std::string& account_id,
    222     const std::string& refresh_token) {
    223   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    224   DCHECK(!refresh_token.empty());
    225 
    226   bool refresh_token_present = refresh_tokens_.count(account_id) > 0;
    227   if (!refresh_token_present ||
    228       refresh_tokens_[account_id] != refresh_token) {
    229     // If token present, and different from the new one, cancel its requests.
    230     if (refresh_token_present)
    231       CancelRequestsForToken(refresh_tokens_[account_id]);
    232 
    233     // Save the token in memory and in persistent store.
    234     refresh_tokens_[account_id] = refresh_token;
    235     scoped_refptr<TokenWebData> token_web_data =
    236         TokenWebData::FromBrowserContext(profile_);
    237     if (token_web_data.get())
    238       token_web_data->SetTokenForService(ApplyAccountIdPrefix(account_id),
    239                                          refresh_token);
    240 
    241     FireRefreshTokenAvailable(account_id);
    242     // TODO(fgorski): Notify diagnostic observers.
    243   }
    244 }
    245 
    246 void ProfileOAuth2TokenService::RevokeCredentials(
    247     const std::string& account_id) {
    248   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    249 
    250   if (refresh_tokens_.count(account_id) > 0) {
    251     CancelRequestsForToken(refresh_tokens_[account_id]);
    252     refresh_tokens_.erase(account_id);
    253     scoped_refptr<TokenWebData> token_web_data =
    254         TokenWebData::FromBrowserContext(profile_);
    255     if (token_web_data.get())
    256       token_web_data->RemoveTokenForService(ApplyAccountIdPrefix(account_id));
    257     FireRefreshTokenRevoked(account_id,
    258         GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED));
    259 
    260     // TODO(fgorski): Notify diagnostic observers.
    261   }
    262 }
    263 
    264 void ProfileOAuth2TokenService::RevokeAllCredentials() {
    265   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    266 
    267   CancelAllRequests();
    268   GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
    269   for (std::map<std::string, std::string>::const_iterator iter =
    270            refresh_tokens_.begin();
    271        iter != refresh_tokens_.end();
    272        ++iter) {
    273     FireRefreshTokenRevoked(iter->first, error);
    274   }
    275   refresh_tokens_.clear();
    276 
    277   scoped_refptr<TokenWebData> token_web_data =
    278       TokenWebData::FromBrowserContext(profile_);
    279   if (token_web_data.get())
    280     token_web_data->RemoveAllTokens();
    281   FireRefreshTokensCleared();
    282 
    283   // TODO(fgorski): Notify diagnostic observers.
    284 }
    285 
    286 void ProfileOAuth2TokenService::LoadCredentials() {
    287   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    288   DCHECK_EQ(0, web_data_service_request_);
    289 
    290   CancelAllRequests();
    291   refresh_tokens_.clear();
    292   scoped_refptr<TokenWebData> token_web_data =
    293       TokenWebData::FromBrowserContext(profile_);
    294   if (token_web_data.get())
    295     web_data_service_request_ = token_web_data->GetAllTokens(this);
    296 }
    297 
    298 void ProfileOAuth2TokenService::OnWebDataServiceRequestDone(
    299     WebDataServiceBase::Handle handle,
    300     const WDTypedResult* result) {
    301   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    302   DCHECK_EQ(web_data_service_request_, handle);
    303   web_data_service_request_ = 0;
    304 
    305   if (result) {
    306     DCHECK(result->GetType() == TOKEN_RESULT);
    307     const WDResult<std::map<std::string, std::string> > * token_result =
    308         static_cast<const WDResult<std::map<std::string, std::string> > * > (
    309             result);
    310     LoadAllCredentialsIntoMemory(token_result->GetValue());
    311   }
    312 }
    313 
    314 void ProfileOAuth2TokenService::LoadAllCredentialsIntoMemory(
    315     const std::map<std::string, std::string>& db_tokens) {
    316   std::string old_login_token;
    317 
    318   for (std::map<std::string, std::string>::const_iterator iter =
    319            db_tokens.begin();
    320        iter != db_tokens.end();
    321        ++iter) {
    322     std::string prefixed_account_id = iter->first;
    323     std::string refresh_token = iter->second;
    324 
    325     if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty())
    326       old_login_token = refresh_token;
    327 
    328     if (IsLegacyServiceId(prefixed_account_id)) {
    329       scoped_refptr<TokenWebData> token_web_data =
    330           TokenWebData::FromBrowserContext(profile_);
    331       if (token_web_data.get())
    332         token_web_data->RemoveTokenForService(prefixed_account_id);
    333     } else {
    334       DCHECK(!refresh_token.empty());
    335       std::string account_id = RemoveAccountIdPrefix(prefixed_account_id);
    336       refresh_tokens_[account_id] = refresh_token;
    337       FireRefreshTokenAvailable(account_id);
    338       // TODO(fgorski): Notify diagnostic observers.
    339     }
    340   }
    341 
    342   if (!old_login_token.empty() &&
    343       refresh_tokens_.count(GetAccountId(profile_)) == 0) {
    344     UpdateCredentials(GetAccountId(profile_), old_login_token);
    345   }
    346 
    347   FireRefreshTokensLoaded();
    348 }
    349