profile_oauth2_token_service_ios.mm 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/ios/browser/profile_oauth2_token_service_ios.h" 6 7#include <Foundation/Foundation.h> 8 9#include <set> 10#include <string> 11#include <vector> 12 13#include "base/bind.h" 14#include "base/message_loop/message_loop.h" 15#include "base/strings/sys_string_conversions.h" 16#include "components/signin/core/browser/signin_client.h" 17#include "google_apis/gaia/oauth2_access_token_fetcher.h" 18#include "ios/public/provider/components/signin/browser/profile_oauth2_token_service_ios_provider.h" 19#include "net/url_request/url_request_status.h" 20 21namespace { 22 23const char* kForceInvalidGrantResponsesRefreshToken = 24 "force_invalid_grant_responses_refresh_token"; 25 26// Match the way Chromium handles authentication errors in 27// google_apis/gaia/oauth2_access_token_fetcher.cc: 28GoogleServiceAuthError GetGoogleServiceAuthErrorFromNSError( 29 ios::ProfileOAuth2TokenServiceIOSProvider* provider, 30 NSError* error) { 31 if (!error) 32 return GoogleServiceAuthError::AuthErrorNone(); 33 34 ios::AuthenticationErrorCategory errorCategory = 35 provider->GetAuthenticationErrorCategory(error); 36 switch (errorCategory) { 37 case ios::kAuthenticationErrorCategoryUnknownErrors: 38 // Treat all unknown error as unexpected service response errors. 39 // This may be too general and may require a finer grain filtering. 40 return GoogleServiceAuthError( 41 GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE); 42 case ios::kAuthenticationErrorCategoryAuthorizationErrors: 43 return GoogleServiceAuthError( 44 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 45 case ios::kAuthenticationErrorCategoryAuthorizationForbiddenErrors: 46 // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be 47 // '403 Rate Limit Exceeded.' (for more details, see 48 // google_apis/gaia/oauth2_access_token_fetcher.cc). 49 return GoogleServiceAuthError( 50 GoogleServiceAuthError::SERVICE_UNAVAILABLE); 51 case ios::kAuthenticationErrorCategoryNetworkServerErrors: 52 // Just set the connection error state to FAILED. 53 return GoogleServiceAuthError::FromConnectionError( 54 net::URLRequestStatus::FAILED); 55 case ios::kAuthenticationErrorCategoryUserCancellationErrors: 56 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); 57 case ios::kAuthenticationErrorCategoryUnknownIdentityErrors: 58 return GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP); 59 } 60} 61 62class SSOAccessTokenFetcher : public OAuth2AccessTokenFetcher { 63 public: 64 SSOAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer, 65 ios::ProfileOAuth2TokenServiceIOSProvider* provider, 66 const std::string account_id); 67 virtual ~SSOAccessTokenFetcher(); 68 69 virtual void Start(const std::string& client_id, 70 const std::string& client_secret, 71 const std::vector<std::string>& scopes) OVERRIDE; 72 73 virtual void CancelRequest() OVERRIDE; 74 75 // Handles an access token response. 76 void OnAccessTokenResponse(NSString* token, 77 NSDate* expiration, 78 NSError* error); 79 80 private: 81 base::WeakPtrFactory<SSOAccessTokenFetcher> weak_factory_; 82 ios::ProfileOAuth2TokenServiceIOSProvider* provider_; // weak 83 std::string account_id_; 84 bool request_was_cancelled_; 85 86 DISALLOW_COPY_AND_ASSIGN(SSOAccessTokenFetcher); 87}; 88 89SSOAccessTokenFetcher::SSOAccessTokenFetcher( 90 OAuth2AccessTokenConsumer* consumer, 91 ios::ProfileOAuth2TokenServiceIOSProvider* provider, 92 const std::string account_id) 93 : OAuth2AccessTokenFetcher(consumer), 94 weak_factory_(this), 95 provider_(provider), 96 account_id_(account_id), 97 request_was_cancelled_(false) { 98 DCHECK(provider_); 99} 100 101SSOAccessTokenFetcher::~SSOAccessTokenFetcher() {} 102 103void SSOAccessTokenFetcher::Start(const std::string& client_id, 104 const std::string& client_secret, 105 const std::vector<std::string>& scopes) { 106 std::set<std::string> scopes_set(scopes.begin(), scopes.end()); 107 provider_->GetAccessToken( 108 account_id_, client_id, client_secret, scopes_set, 109 base::Bind(&SSOAccessTokenFetcher::OnAccessTokenResponse, 110 weak_factory_.GetWeakPtr())); 111} 112 113void SSOAccessTokenFetcher::CancelRequest() { request_was_cancelled_ = true; } 114 115void SSOAccessTokenFetcher::OnAccessTokenResponse(NSString* token, 116 NSDate* expiration, 117 NSError* error) { 118 if (request_was_cancelled_) { 119 // Ignore the callback if the request was cancelled. 120 return; 121 } 122 GoogleServiceAuthError auth_error = 123 GetGoogleServiceAuthErrorFromNSError(provider_, error); 124 if (auth_error.state() == GoogleServiceAuthError::NONE) { 125 base::Time expiration_date = 126 base::Time::FromDoubleT([expiration timeIntervalSince1970]); 127 FireOnGetTokenSuccess(base::SysNSStringToUTF8(token), expiration_date); 128 } else { 129 FireOnGetTokenFailure(auth_error); 130 } 131} 132 133// Fetcher that returns INVALID_GAIA_CREDENTIALS responses for all requests. 134class InvalidGrantAccessTokenFetcher : public OAuth2AccessTokenFetcher { 135 public: 136 explicit InvalidGrantAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer); 137 virtual ~InvalidGrantAccessTokenFetcher(); 138 139 // OAuth2AccessTokenFetcher 140 virtual void Start(const std::string& client_id, 141 const std::string& client_secret, 142 const std::vector<std::string>& scopes) OVERRIDE; 143 virtual void CancelRequest() OVERRIDE; 144 145 // Fires token failure notifications with INVALID_GAIA_CREDENTIALS error. 146 void FireInvalidGrant(); 147 148 private: 149 bool request_was_cancelled_; 150 DISALLOW_COPY_AND_ASSIGN(InvalidGrantAccessTokenFetcher); 151}; 152 153InvalidGrantAccessTokenFetcher::InvalidGrantAccessTokenFetcher( 154 OAuth2AccessTokenConsumer* consumer) 155 : OAuth2AccessTokenFetcher(consumer), 156 request_was_cancelled_(false) {} 157 158InvalidGrantAccessTokenFetcher::~InvalidGrantAccessTokenFetcher() {} 159 160void InvalidGrantAccessTokenFetcher::Start( 161 const std::string& client_id, 162 const std::string& client_secret, 163 const std::vector<std::string>& scopes) { 164 base::MessageLoop::current()->PostTask( 165 FROM_HERE, 166 base::Bind(&InvalidGrantAccessTokenFetcher::FireInvalidGrant, 167 base::Unretained(this))); 168}; 169 170void InvalidGrantAccessTokenFetcher::FireInvalidGrant() { 171 if (request_was_cancelled_) 172 return; 173 GoogleServiceAuthError auth_error( 174 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); 175 FireOnGetTokenFailure(auth_error); 176} 177 178void InvalidGrantAccessTokenFetcher::CancelRequest() { 179 request_was_cancelled_ = true; 180} 181 182} // namespace 183 184ProfileOAuth2TokenServiceIOS::AccountInfo::AccountInfo( 185 ProfileOAuth2TokenService* token_service, 186 const std::string& account_id) 187 : token_service_(token_service), 188 account_id_(account_id), 189 last_auth_error_(GoogleServiceAuthError::NONE) { 190 DCHECK(token_service_); 191 DCHECK(!account_id_.empty()); 192 token_service_->signin_error_controller()->AddProvider(this); 193} 194 195ProfileOAuth2TokenServiceIOS::AccountInfo::~AccountInfo() { 196 token_service_->signin_error_controller()->RemoveProvider(this); 197} 198 199void ProfileOAuth2TokenServiceIOS::AccountInfo::SetLastAuthError( 200 const GoogleServiceAuthError& error) { 201 if (error.state() != last_auth_error_.state()) { 202 last_auth_error_ = error; 203 token_service_->signin_error_controller()->AuthStatusChanged(); 204 } 205} 206 207std::string ProfileOAuth2TokenServiceIOS::AccountInfo::GetAccountId() const { 208 return account_id_; 209} 210 211std::string ProfileOAuth2TokenServiceIOS::AccountInfo::GetUsername() const { 212 // TODO(rogerta): when |account_id| becomes the obfuscated gaia id, this 213 // will need to be changed. 214 return account_id_; 215} 216 217GoogleServiceAuthError 218ProfileOAuth2TokenServiceIOS::AccountInfo::GetAuthStatus() const { 219 return last_auth_error_; 220} 221 222ProfileOAuth2TokenServiceIOS::ProfileOAuth2TokenServiceIOS() 223 : MutableProfileOAuth2TokenService(), 224 use_legacy_token_service_(false) { 225 DCHECK(thread_checker_.CalledOnValidThread()); 226} 227 228ProfileOAuth2TokenServiceIOS::~ProfileOAuth2TokenServiceIOS() { 229 DCHECK(thread_checker_.CalledOnValidThread()); 230} 231 232void ProfileOAuth2TokenServiceIOS::Initialize(SigninClient* client) { 233 DCHECK(thread_checker_.CalledOnValidThread()); 234 MutableProfileOAuth2TokenService::Initialize(client); 235} 236 237void ProfileOAuth2TokenServiceIOS::Shutdown() { 238 DCHECK(thread_checker_.CalledOnValidThread()); 239 CancelAllRequests(); 240 accounts_.clear(); 241 MutableProfileOAuth2TokenService::Shutdown(); 242} 243 244ios::ProfileOAuth2TokenServiceIOSProvider* 245ProfileOAuth2TokenServiceIOS::GetProvider() { 246 ios::ProfileOAuth2TokenServiceIOSProvider* provider = 247 client()->GetIOSProvider(); 248 DCHECK(provider); 249 return provider; 250} 251 252void ProfileOAuth2TokenServiceIOS::LoadCredentials( 253 const std::string& primary_account_id) { 254 DCHECK(thread_checker_.CalledOnValidThread()); 255 256 // LoadCredentials() is called iff the user is signed in to Chrome, so the 257 // primary account id must not be empty. 258 DCHECK(!primary_account_id.empty()); 259 260 use_legacy_token_service_ = !GetProvider()->IsUsingSharedAuthentication(); 261 if (use_legacy_token_service_) { 262 MutableProfileOAuth2TokenService::LoadCredentials(primary_account_id); 263 return; 264 } 265 266 GetProvider()->InitializeSharedAuthentication(); 267 ReloadCredentials(); 268 FireRefreshTokensLoaded(); 269} 270 271void ProfileOAuth2TokenServiceIOS::ReloadCredentials() { 272 DCHECK(thread_checker_.CalledOnValidThread()); 273 if (use_legacy_token_service_) { 274 NOTREACHED(); 275 return; 276 } 277 278 // Remove all old accounts that do not appear in |new_accounts| and then 279 // load |new_accounts|. 280 std::vector<std::string> new_accounts(GetProvider()->GetAllAccountIds()); 281 std::vector<std::string> old_accounts(GetAccounts()); 282 for (auto i = old_accounts.begin(); i != old_accounts.end(); ++i) { 283 if (std::find(new_accounts.begin(), new_accounts.end(), *i) == 284 new_accounts.end()) { 285 RemoveAccount(*i); 286 } 287 } 288 289 // Load all new_accounts. 290 for (auto i = new_accounts.begin(); i != new_accounts.end(); ++i) { 291 AddOrUpdateAccount(*i); 292 } 293} 294 295void ProfileOAuth2TokenServiceIOS::UpdateCredentials( 296 const std::string& account_id, 297 const std::string& refresh_token) { 298 DCHECK(thread_checker_.CalledOnValidThread()); 299 if (use_legacy_token_service_) { 300 MutableProfileOAuth2TokenService::UpdateCredentials(account_id, 301 refresh_token); 302 return; 303 } 304 NOTREACHED() << "Unexpected call to UpdateCredentials when using shared " 305 "authentication."; 306} 307 308void ProfileOAuth2TokenServiceIOS::RevokeAllCredentials() { 309 DCHECK(thread_checker_.CalledOnValidThread()); 310 if (use_legacy_token_service_) { 311 MutableProfileOAuth2TokenService::RevokeAllCredentials(); 312 return; 313 } 314 315 CancelAllRequests(); 316 ClearCache(); 317 AccountInfoMap toRemove = accounts_; 318 for (AccountInfoMap::iterator i = toRemove.begin(); i != toRemove.end(); ++i) 319 RemoveAccount(i->first); 320 321 DCHECK_EQ(0u, accounts_.size()); 322} 323 324OAuth2AccessTokenFetcher* 325ProfileOAuth2TokenServiceIOS::CreateAccessTokenFetcher( 326 const std::string& account_id, 327 net::URLRequestContextGetter* getter, 328 OAuth2AccessTokenConsumer* consumer) { 329 if (use_legacy_token_service_) { 330 std::string refresh_token = GetRefreshToken(account_id); 331 DCHECK(!refresh_token.empty()); 332 if (refresh_token == kForceInvalidGrantResponsesRefreshToken) { 333 return new InvalidGrantAccessTokenFetcher(consumer); 334 } else { 335 return MutableProfileOAuth2TokenService::CreateAccessTokenFetcher( 336 account_id, getter, consumer); 337 } 338 } 339 340 return new SSOAccessTokenFetcher(consumer, GetProvider(), account_id); 341} 342 343void ProfileOAuth2TokenServiceIOS::ForceInvalidGrantResponses() { 344 if (!use_legacy_token_service_) { 345 NOTREACHED(); 346 return; 347 } 348 std::vector<std::string> accounts = 349 MutableProfileOAuth2TokenService::GetAccounts(); 350 if (accounts.empty()) { 351 NOTREACHED(); 352 return; 353 } 354 355 std::string first_account_id = *accounts.begin(); 356 if (RefreshTokenIsAvailable(first_account_id) && 357 GetRefreshToken(first_account_id) != 358 kForceInvalidGrantResponsesRefreshToken) { 359 MutableProfileOAuth2TokenService::RevokeAllCredentials(); 360 } 361 362 for (auto i = accounts.begin(); i != accounts.end(); ++i) { 363 std::string account_id = *i; 364 MutableProfileOAuth2TokenService::UpdateCredentials( 365 account_id, 366 kForceInvalidGrantResponsesRefreshToken); 367 } 368} 369 370void ProfileOAuth2TokenServiceIOS::InvalidateOAuth2Token( 371 const std::string& account_id, 372 const std::string& client_id, 373 const ScopeSet& scopes, 374 const std::string& access_token) { 375 DCHECK(thread_checker_.CalledOnValidThread()); 376 377 // Call |MutableProfileOAuth2TokenService::InvalidateOAuth2Token| to clear the 378 // cached access token. 379 MutableProfileOAuth2TokenService::InvalidateOAuth2Token(account_id, 380 client_id, 381 scopes, 382 access_token); 383 384 // There is no need to inform the authentication library that the access 385 // token is invalid as it never caches the token. 386} 387 388std::vector<std::string> ProfileOAuth2TokenServiceIOS::GetAccounts() { 389 DCHECK(thread_checker_.CalledOnValidThread()); 390 if (use_legacy_token_service_) { 391 return MutableProfileOAuth2TokenService::GetAccounts(); 392 } 393 394 std::vector<std::string> account_ids; 395 for (auto i = accounts_.begin(); i != accounts_.end(); ++i) 396 account_ids.push_back(i->first); 397 return account_ids; 398} 399 400bool ProfileOAuth2TokenServiceIOS::RefreshTokenIsAvailable( 401 const std::string& account_id) const { 402 DCHECK(thread_checker_.CalledOnValidThread()); 403 404 if (use_legacy_token_service_) { 405 return MutableProfileOAuth2TokenService::RefreshTokenIsAvailable( 406 account_id); 407 } 408 409 return accounts_.count(account_id) > 0; 410} 411 412std::string ProfileOAuth2TokenServiceIOS::GetRefreshToken( 413 const std::string& account_id) const { 414 DCHECK(thread_checker_.CalledOnValidThread()); 415 if (use_legacy_token_service_) 416 return MutableProfileOAuth2TokenService::GetRefreshToken(account_id); 417 418 // On iOS, the refresh token does not exist as ProfileOAuth2TokenServiceIOS 419 // fetches the access token from the iOS authentication library. 420 NOTREACHED(); 421 return std::string(); 422} 423 424std::string 425ProfileOAuth2TokenServiceIOS::GetRefreshTokenWhenNotUsingSharedAuthentication( 426 const std::string& account_id) { 427 DCHECK(use_legacy_token_service_); 428 return GetRefreshToken(account_id); 429} 430 431void ProfileOAuth2TokenServiceIOS::UpdateAuthError( 432 const std::string& account_id, 433 const GoogleServiceAuthError& error) { 434 DCHECK(thread_checker_.CalledOnValidThread()); 435 436 if (use_legacy_token_service_) { 437 MutableProfileOAuth2TokenService::UpdateAuthError(account_id, error); 438 return; 439 } 440 441 // Do not report connection errors as these are not actually auth errors. 442 // We also want to avoid masking a "real" auth error just because we 443 // subsequently get a transient network error. 444 if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED || 445 error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE) { 446 return; 447 } 448 449 if (accounts_.count(account_id) == 0) { 450 NOTREACHED(); 451 return; 452 } 453 accounts_[account_id]->SetLastAuthError(error); 454} 455 456// Clear the authentication error state and notify all observers that a new 457// refresh token is available so that they request new access tokens. 458void ProfileOAuth2TokenServiceIOS::AddOrUpdateAccount( 459 const std::string& account_id) { 460 DCHECK(thread_checker_.CalledOnValidThread()); 461 DCHECK(!account_id.empty()); 462 DCHECK(!use_legacy_token_service_); 463 464 bool account_present = accounts_.count(account_id) > 0; 465 if (account_present && accounts_[account_id]->GetAuthStatus().state() == 466 GoogleServiceAuthError::NONE) { 467 // No need to update the account if it is already a known account and if 468 // there is no auth error. 469 return; 470 } 471 472 if (account_present) { 473 CancelRequestsForAccount(account_id); 474 ClearCacheForAccount(account_id); 475 } else { 476 accounts_[account_id].reset(new AccountInfo(this, account_id)); 477 } 478 UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone()); 479 FireRefreshTokenAvailable(account_id); 480} 481 482void ProfileOAuth2TokenServiceIOS::RemoveAccount( 483 const std::string& account_id) { 484 DCHECK(thread_checker_.CalledOnValidThread()); 485 DCHECK(!account_id.empty()); 486 DCHECK(!use_legacy_token_service_); 487 488 if (accounts_.count(account_id) > 0) { 489 CancelRequestsForAccount(account_id); 490 ClearCacheForAccount(account_id); 491 accounts_.erase(account_id); 492 FireRefreshTokenRevoked(account_id); 493 } 494} 495 496void ProfileOAuth2TokenServiceIOS::StartUsingSharedAuthentication() { 497 if (!use_legacy_token_service_) 498 return; 499 MutableProfileOAuth2TokenService::RevokeAllCredentials(); 500 use_legacy_token_service_ = false; 501} 502 503void ProfileOAuth2TokenServiceIOS::SetUseLegacyTokenServiceForTesting( 504 bool use_legacy_token_service) { 505 use_legacy_token_service_ = use_legacy_token_service; 506} 507