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