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