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.
5 #include "components/signin/ios/browser/profile_oauth2_token_service_ios.h"
7 #include <Foundation/Foundation.h>
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"
23 const char* kForceInvalidGrantResponsesRefreshToken =
24 "force_invalid_grant_responses_refresh_token";
26 // Match the way Chromium handles authentication errors in
27 // google_apis/gaia/oauth2_access_token_fetcher.cc:
28 GoogleServiceAuthError GetGoogleServiceAuthErrorFromNSError(
29 ios::ProfileOAuth2TokenServiceIOSProvider* provider,
32 return GoogleServiceAuthError::AuthErrorNone();
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);
62 class SSOAccessTokenFetcher : public OAuth2AccessTokenFetcher {
64 SSOAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer,
65 ios::ProfileOAuth2TokenServiceIOSProvider* provider,
66 const std::string account_id);
67 virtual ~SSOAccessTokenFetcher();
69 virtual void Start(const std::string& client_id,
70 const std::string& client_secret,
71 const std::vector<std::string>& scopes) OVERRIDE;
73 virtual void CancelRequest() OVERRIDE;
75 // Handles an access token response.
76 void OnAccessTokenResponse(NSString* token,
81 base::WeakPtrFactory<SSOAccessTokenFetcher> weak_factory_;
82 ios::ProfileOAuth2TokenServiceIOSProvider* provider_; // weak
83 std::string account_id_;
84 bool request_was_cancelled_;
86 DISALLOW_COPY_AND_ASSIGN(SSOAccessTokenFetcher);
89 SSOAccessTokenFetcher::SSOAccessTokenFetcher(
90 OAuth2AccessTokenConsumer* consumer,
91 ios::ProfileOAuth2TokenServiceIOSProvider* provider,
92 const std::string account_id)
93 : OAuth2AccessTokenFetcher(consumer),
96 account_id_(account_id),
97 request_was_cancelled_(false) {
101 SSOAccessTokenFetcher::~SSOAccessTokenFetcher() {}
103 void 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()));
113 void SSOAccessTokenFetcher::CancelRequest() { request_was_cancelled_ = true; }
115 void SSOAccessTokenFetcher::OnAccessTokenResponse(NSString* token,
118 if (request_was_cancelled_) {
119 // Ignore the callback if the request was cancelled.
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);
129 FireOnGetTokenFailure(auth_error);
133 // Fetcher that returns INVALID_GAIA_CREDENTIALS responses for all requests.
134 class InvalidGrantAccessTokenFetcher : public OAuth2AccessTokenFetcher {
136 explicit InvalidGrantAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer);
137 virtual ~InvalidGrantAccessTokenFetcher();
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;
145 // Fires token failure notifications with INVALID_GAIA_CREDENTIALS error.
146 void FireInvalidGrant();
149 bool request_was_cancelled_;
150 DISALLOW_COPY_AND_ASSIGN(InvalidGrantAccessTokenFetcher);
153 InvalidGrantAccessTokenFetcher::InvalidGrantAccessTokenFetcher(
154 OAuth2AccessTokenConsumer* consumer)
155 : OAuth2AccessTokenFetcher(consumer),
156 request_was_cancelled_(false) {}
158 InvalidGrantAccessTokenFetcher::~InvalidGrantAccessTokenFetcher() {}
160 void 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(
166 base::Bind(&InvalidGrantAccessTokenFetcher::FireInvalidGrant,
167 base::Unretained(this)));
170 void InvalidGrantAccessTokenFetcher::FireInvalidGrant() {
171 if (request_was_cancelled_)
173 GoogleServiceAuthError auth_error(
174 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
175 FireOnGetTokenFailure(auth_error);
178 void InvalidGrantAccessTokenFetcher::CancelRequest() {
179 request_was_cancelled_ = true;
184 ProfileOAuth2TokenServiceIOS::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);
195 ProfileOAuth2TokenServiceIOS::AccountInfo::~AccountInfo() {
196 token_service_->signin_error_controller()->RemoveProvider(this);
199 void 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();
207 std::string ProfileOAuth2TokenServiceIOS::AccountInfo::GetAccountId() const {
211 std::string ProfileOAuth2TokenServiceIOS::AccountInfo::GetUsername() const {
212 // TODO(rogerta): when |account_id| becomes the obfuscated gaia id, this
213 // will need to be changed.
217 GoogleServiceAuthError
218 ProfileOAuth2TokenServiceIOS::AccountInfo::GetAuthStatus() const {
219 return last_auth_error_;
222 ProfileOAuth2TokenServiceIOS::ProfileOAuth2TokenServiceIOS()
223 : MutableProfileOAuth2TokenService(),
224 use_legacy_token_service_(false) {
225 DCHECK(thread_checker_.CalledOnValidThread());
228 ProfileOAuth2TokenServiceIOS::~ProfileOAuth2TokenServiceIOS() {
229 DCHECK(thread_checker_.CalledOnValidThread());
232 void ProfileOAuth2TokenServiceIOS::Initialize(SigninClient* client) {
233 DCHECK(thread_checker_.CalledOnValidThread());
234 MutableProfileOAuth2TokenService::Initialize(client);
237 void ProfileOAuth2TokenServiceIOS::Shutdown() {
238 DCHECK(thread_checker_.CalledOnValidThread());
241 MutableProfileOAuth2TokenService::Shutdown();
244 ios::ProfileOAuth2TokenServiceIOSProvider*
245 ProfileOAuth2TokenServiceIOS::GetProvider() {
246 ios::ProfileOAuth2TokenServiceIOSProvider* provider =
247 client()->GetIOSProvider();
252 void ProfileOAuth2TokenServiceIOS::LoadCredentials(
253 const std::string& primary_account_id) {
254 DCHECK(thread_checker_.CalledOnValidThread());
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());
260 use_legacy_token_service_ = !GetProvider()->IsUsingSharedAuthentication();
261 if (use_legacy_token_service_) {
262 MutableProfileOAuth2TokenService::LoadCredentials(primary_account_id);
266 GetProvider()->InitializeSharedAuthentication();
268 FireRefreshTokensLoaded();
271 void ProfileOAuth2TokenServiceIOS::ReloadCredentials() {
272 DCHECK(thread_checker_.CalledOnValidThread());
273 if (use_legacy_token_service_) {
278 ScopedBacthChange batch(this);
280 // Remove all old accounts that do not appear in |new_accounts| and then
281 // load |new_accounts|.
282 std::vector<std::string> new_accounts(GetProvider()->GetAllAccountIds());
283 std::vector<std::string> old_accounts(GetAccounts());
284 for (auto i = old_accounts.begin(); i != old_accounts.end(); ++i) {
285 if (std::find(new_accounts.begin(), new_accounts.end(), *i) ==
286 new_accounts.end()) {
291 // Load all new_accounts.
292 for (auto i = new_accounts.begin(); i != new_accounts.end(); ++i) {
293 AddOrUpdateAccount(*i);
297 void ProfileOAuth2TokenServiceIOS::UpdateCredentials(
298 const std::string& account_id,
299 const std::string& refresh_token) {
300 DCHECK(thread_checker_.CalledOnValidThread());
301 if (use_legacy_token_service_) {
302 MutableProfileOAuth2TokenService::UpdateCredentials(account_id,
306 NOTREACHED() << "Unexpected call to UpdateCredentials when using shared "
310 void ProfileOAuth2TokenServiceIOS::RevokeAllCredentials() {
311 DCHECK(thread_checker_.CalledOnValidThread());
312 if (use_legacy_token_service_) {
313 MutableProfileOAuth2TokenService::RevokeAllCredentials();
317 ScopedBacthChange batch(this);
320 AccountInfoMap toRemove = accounts_;
321 for (AccountInfoMap::iterator i = toRemove.begin(); i != toRemove.end(); ++i)
322 RemoveAccount(i->first);
324 DCHECK_EQ(0u, accounts_.size());
327 OAuth2AccessTokenFetcher*
328 ProfileOAuth2TokenServiceIOS::CreateAccessTokenFetcher(
329 const std::string& account_id,
330 net::URLRequestContextGetter* getter,
331 OAuth2AccessTokenConsumer* consumer) {
332 if (use_legacy_token_service_) {
333 std::string refresh_token = GetRefreshToken(account_id);
334 DCHECK(!refresh_token.empty());
335 if (refresh_token == kForceInvalidGrantResponsesRefreshToken) {
336 return new InvalidGrantAccessTokenFetcher(consumer);
338 return MutableProfileOAuth2TokenService::CreateAccessTokenFetcher(
339 account_id, getter, consumer);
343 return new SSOAccessTokenFetcher(consumer, GetProvider(), account_id);
346 void ProfileOAuth2TokenServiceIOS::ForceInvalidGrantResponses() {
347 if (!use_legacy_token_service_) {
351 std::vector<std::string> accounts =
352 MutableProfileOAuth2TokenService::GetAccounts();
353 if (accounts.empty()) {
358 std::string first_account_id = *accounts.begin();
359 if (RefreshTokenIsAvailable(first_account_id) &&
360 GetRefreshToken(first_account_id) !=
361 kForceInvalidGrantResponsesRefreshToken) {
362 MutableProfileOAuth2TokenService::RevokeAllCredentials();
365 ScopedBacthChange batch(this);
366 for (auto i = accounts.begin(); i != accounts.end(); ++i) {
367 std::string account_id = *i;
368 MutableProfileOAuth2TokenService::UpdateCredentials(
370 kForceInvalidGrantResponsesRefreshToken);
374 void ProfileOAuth2TokenServiceIOS::InvalidateOAuth2Token(
375 const std::string& account_id,
376 const std::string& client_id,
377 const ScopeSet& scopes,
378 const std::string& access_token) {
379 DCHECK(thread_checker_.CalledOnValidThread());
381 // Call |MutableProfileOAuth2TokenService::InvalidateOAuth2Token| to clear the
382 // cached access token.
383 MutableProfileOAuth2TokenService::InvalidateOAuth2Token(account_id,
388 // There is no need to inform the authentication library that the access
389 // token is invalid as it never caches the token.
392 std::vector<std::string> ProfileOAuth2TokenServiceIOS::GetAccounts() {
393 DCHECK(thread_checker_.CalledOnValidThread());
394 if (use_legacy_token_service_) {
395 return MutableProfileOAuth2TokenService::GetAccounts();
398 std::vector<std::string> account_ids;
399 for (auto i = accounts_.begin(); i != accounts_.end(); ++i)
400 account_ids.push_back(i->first);
404 bool ProfileOAuth2TokenServiceIOS::RefreshTokenIsAvailable(
405 const std::string& account_id) const {
406 DCHECK(thread_checker_.CalledOnValidThread());
408 if (use_legacy_token_service_) {
409 return MutableProfileOAuth2TokenService::RefreshTokenIsAvailable(
413 return accounts_.count(account_id) > 0;
416 std::string ProfileOAuth2TokenServiceIOS::GetRefreshToken(
417 const std::string& account_id) const {
418 DCHECK(thread_checker_.CalledOnValidThread());
419 if (use_legacy_token_service_)
420 return MutableProfileOAuth2TokenService::GetRefreshToken(account_id);
422 // On iOS, the refresh token does not exist as ProfileOAuth2TokenServiceIOS
423 // fetches the access token from the iOS authentication library.
425 return std::string();
429 ProfileOAuth2TokenServiceIOS::GetRefreshTokenWhenNotUsingSharedAuthentication(
430 const std::string& account_id) {
431 DCHECK(use_legacy_token_service_);
432 return GetRefreshToken(account_id);
435 void ProfileOAuth2TokenServiceIOS::UpdateAuthError(
436 const std::string& account_id,
437 const GoogleServiceAuthError& error) {
438 DCHECK(thread_checker_.CalledOnValidThread());
440 if (use_legacy_token_service_) {
441 MutableProfileOAuth2TokenService::UpdateAuthError(account_id, error);
445 // Do not report connection errors as these are not actually auth errors.
446 // We also want to avoid masking a "real" auth error just because we
447 // subsequently get a transient network error.
448 if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED ||
449 error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE) {
453 if (accounts_.count(account_id) == 0) {
457 accounts_[account_id]->SetLastAuthError(error);
460 // Clear the authentication error state and notify all observers that a new
461 // refresh token is available so that they request new access tokens.
462 void ProfileOAuth2TokenServiceIOS::AddOrUpdateAccount(
463 const std::string& account_id) {
464 DCHECK(thread_checker_.CalledOnValidThread());
465 DCHECK(!account_id.empty());
466 DCHECK(!use_legacy_token_service_);
468 bool account_present = accounts_.count(account_id) > 0;
469 if (account_present && accounts_[account_id]->GetAuthStatus().state() ==
470 GoogleServiceAuthError::NONE) {
471 // No need to update the account if it is already a known account and if
472 // there is no auth error.
476 if (account_present) {
477 CancelRequestsForAccount(account_id);
478 ClearCacheForAccount(account_id);
480 accounts_[account_id].reset(new AccountInfo(this, account_id));
482 UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
483 FireRefreshTokenAvailable(account_id);
486 void ProfileOAuth2TokenServiceIOS::RemoveAccount(
487 const std::string& account_id) {
488 DCHECK(thread_checker_.CalledOnValidThread());
489 DCHECK(!account_id.empty());
490 DCHECK(!use_legacy_token_service_);
492 if (accounts_.count(account_id) > 0) {
493 CancelRequestsForAccount(account_id);
494 ClearCacheForAccount(account_id);
495 accounts_.erase(account_id);
496 FireRefreshTokenRevoked(account_id);
500 void ProfileOAuth2TokenServiceIOS::StartUsingSharedAuthentication() {
501 if (!use_legacy_token_service_)
503 MutableProfileOAuth2TokenService::RevokeAllCredentials();
504 use_legacy_token_service_ = false;
507 void ProfileOAuth2TokenServiceIOS::SetUseLegacyTokenServiceForTesting(
508 bool use_legacy_token_service) {
509 use_legacy_token_service_ = use_legacy_token_service;