mutable_profile_oauth2_token_service.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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 17namespace { 18 19const char kAccountIdPrefix[] = "AccountId-"; 20const size_t kAccountIdPrefixLength = 10; 21 22std::string ApplyAccountIdPrefix(const std::string& account_id) { 23 return kAccountIdPrefix + account_id; 24} 25 26bool IsLegacyRefreshTokenId(const std::string& service_id) { 27 return service_id == GaiaConstants::kGaiaOAuth2LoginRefreshToken; 28} 29 30bool IsLegacyServiceId(const std::string& account_id) { 31 return account_id.compare(0u, kAccountIdPrefixLength, kAccountIdPrefix) != 0; 32} 33 34std::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. 43class 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 60MutableProfileOAuth2TokenService:: 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 70MutableProfileOAuth2TokenService:: 71 RevokeServerRefreshToken::~RevokeServerRefreshToken() {} 72 73void 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 83MutableProfileOAuth2TokenService::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 96MutableProfileOAuth2TokenService::AccountInfo::~AccountInfo() { 97 token_service_->signin_error_controller()->RemoveProvider(this); 98} 99 100void 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 108std::string 109MutableProfileOAuth2TokenService::AccountInfo::GetAccountId() const { 110 return account_id_; 111} 112 113std::string 114MutableProfileOAuth2TokenService::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 120GoogleServiceAuthError 121MutableProfileOAuth2TokenService::AccountInfo::GetAuthStatus() const { 122 return last_auth_error_; 123} 124 125MutableProfileOAuth2TokenService::MutableProfileOAuth2TokenService() 126 : web_data_service_request_(0) { 127} 128 129MutableProfileOAuth2TokenService::~MutableProfileOAuth2TokenService() { 130 DCHECK(server_revokes_.empty()); 131} 132 133void MutableProfileOAuth2TokenService::Shutdown() { 134 server_revokes_.clear(); 135 CancelWebTokenFetch(); 136 CancelAllRequests(); 137 refresh_tokens_.clear(); 138 139 ProfileOAuth2TokenService::Shutdown(); 140} 141 142bool MutableProfileOAuth2TokenService::RefreshTokenIsAvailable( 143 const std::string& account_id) const { 144 return !GetRefreshToken(account_id).empty(); 145} 146 147std::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 155OAuth2AccessTokenFetcher* 156MutableProfileOAuth2TokenService::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 165net::URLRequestContextGetter* 166MutableProfileOAuth2TokenService::GetRequestContext() { 167 return client()->GetURLRequestContext(); 168} 169 170void 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 184void 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 222void MutableProfileOAuth2TokenService::LoadAllCredentialsIntoMemory( 223 const std::map<std::string, std::string>& db_tokens) { 224 std::string old_login_token; 225 226 for (std::map<std::string, std::string>::const_iterator iter = 227 db_tokens.begin(); 228 iter != db_tokens.end(); 229 ++iter) { 230 std::string prefixed_account_id = iter->first; 231 std::string refresh_token = iter->second; 232 233 if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty()) 234 old_login_token = refresh_token; 235 236 if (IsLegacyServiceId(prefixed_account_id)) { 237 scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase(); 238 if (token_web_data.get()) 239 token_web_data->RemoveTokenForService(prefixed_account_id); 240 } else { 241 DCHECK(!refresh_token.empty()); 242 std::string account_id = RemoveAccountIdPrefix(prefixed_account_id); 243 refresh_tokens()[account_id].reset( 244 new AccountInfo(this, account_id, refresh_token)); 245 FireRefreshTokenAvailable(account_id); 246 // TODO(fgorski): Notify diagnostic observers. 247 } 248 } 249 250 if (!old_login_token.empty()) { 251 DCHECK(!loading_primary_account_id_.empty()); 252 if (refresh_tokens().count(loading_primary_account_id_) == 0) 253 UpdateCredentials(loading_primary_account_id_, old_login_token); 254 } 255 256 FireRefreshTokensLoaded(); 257} 258 259void MutableProfileOAuth2TokenService::UpdateAuthError( 260 const std::string& account_id, 261 const GoogleServiceAuthError& error) { 262 // Do not report connection errors as these are not actually auth errors. 263 // We also want to avoid masking a "real" auth error just because we 264 // subsequently get a transient network error. 265 if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED || 266 error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE) 267 return; 268 269#if defined(OS_IOS) 270 // ProfileOauth2TokenService does not manage the refresh tokens on iOS - the 271 // account info on iOS is only used to manage the authentication error state. 272 // Simply add an account info entry with empty refresh token if none exists. 273 if (refresh_tokens_.count(account_id) == 0) { 274 refresh_tokens_[account_id].reset( 275 new AccountInfo(this, account_id, std::string())); 276 } 277#endif 278 279 if (refresh_tokens_.count(account_id) == 0) { 280 // This could happen if the preferences have been corrupted (see 281 // http://crbug.com/321370). In a Debug build that would be a bug, but in a 282 // Release build we want to deal with it gracefully. 283 NOTREACHED(); 284 return; 285 } 286 refresh_tokens_[account_id]->SetLastAuthError(error); 287} 288 289std::vector<std::string> MutableProfileOAuth2TokenService::GetAccounts() { 290 std::vector<std::string> account_ids; 291 for (AccountInfoMap::const_iterator iter = refresh_tokens_.begin(); 292 iter != refresh_tokens_.end(); ++iter) { 293 account_ids.push_back(iter->first); 294 } 295 return account_ids; 296} 297 298void MutableProfileOAuth2TokenService::UpdateCredentials( 299 const std::string& account_id, 300 const std::string& refresh_token) { 301 DCHECK(thread_checker_.CalledOnValidThread()); 302 DCHECK(!account_id.empty()); 303 DCHECK(!refresh_token.empty()); 304 305 signin_metrics::LogSigninAddAccount(); 306 307 bool refresh_token_present = refresh_tokens_.count(account_id) > 0; 308 if (!refresh_token_present || 309 refresh_tokens_[account_id]->refresh_token() != refresh_token) { 310 // If token present, and different from the new one, cancel its requests, 311 // and clear the entries in cache related to that account. 312 if (refresh_token_present) { 313 std::string revoke_reason = refresh_token_present ? "token differs" : 314 "token is missing"; 315 LOG(WARNING) << "Revoking refresh token on server. " 316 << "Reason: token update, " << revoke_reason; 317 RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token()); 318 CancelRequestsForAccount(account_id); 319 ClearCacheForAccount(account_id); 320 refresh_tokens_[account_id]->set_refresh_token(refresh_token); 321 } else { 322 refresh_tokens_[account_id].reset( 323 new AccountInfo(this, account_id, refresh_token)); 324 } 325 326 // Save the token in memory and in persistent store. 327 PersistCredentials(account_id, refresh_token); 328 329 UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone()); 330 FireRefreshTokenAvailable(account_id); 331 } 332} 333 334void MutableProfileOAuth2TokenService::RevokeCredentials( 335 const std::string& account_id) { 336 DCHECK(thread_checker_.CalledOnValidThread()); 337 338 if (refresh_tokens_.count(account_id) > 0) { 339 RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token()); 340 CancelRequestsForAccount(account_id); 341 ClearCacheForAccount(account_id); 342 refresh_tokens_.erase(account_id); 343 ClearPersistedCredentials(account_id); 344 FireRefreshTokenRevoked(account_id); 345 } 346} 347 348void MutableProfileOAuth2TokenService::PersistCredentials( 349 const std::string& account_id, 350 const std::string& refresh_token) { 351 scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase(); 352 if (token_web_data.get()) { 353 token_web_data->SetTokenForService(ApplyAccountIdPrefix(account_id), 354 refresh_token); 355 } 356} 357 358void MutableProfileOAuth2TokenService::ClearPersistedCredentials( 359 const std::string& account_id) { 360 scoped_refptr<TokenWebData> token_web_data = client()->GetDatabase(); 361 if (token_web_data.get()) 362 token_web_data->RemoveTokenForService(ApplyAccountIdPrefix(account_id)); 363} 364 365void MutableProfileOAuth2TokenService::RevokeAllCredentials() { 366 if (!client()->CanRevokeCredentials()) 367 return; 368 DCHECK(thread_checker_.CalledOnValidThread()); 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 379void 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 387void 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