Index: components/signin/ios/browser/profile_oauth2_token_service_ios.mm |
diff --git a/components/signin/ios/browser/profile_oauth2_token_service_ios.mm b/components/signin/ios/browser/profile_oauth2_token_service_ios.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..50a93feed0c252273bfb67fcd223b733c4eefd4c |
--- /dev/null |
+++ b/components/signin/ios/browser/profile_oauth2_token_service_ios.mm |
@@ -0,0 +1,498 @@ |
+// Copyright 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "components/signin/ios/browser/profile_oauth2_token_service_ios.h" |
+ |
+#include <Foundation/Foundation.h> |
+ |
+#include <set> |
+#include <string> |
+#include <vector> |
+ |
+#include "base/bind.h" |
+#include "base/message_loop/message_loop.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "components/signin/core/browser/signin_client.h" |
+#include "google_apis/gaia/oauth2_access_token_fetcher.h" |
+#include "ios/public/provider/components/signin/browser/profile_oauth2_token_service_ios_provider.h" |
+#include "net/url_request/url_request_status.h" |
+ |
+namespace { |
+ |
+const char* kForceInvalidGrantResponsesRefreshToken = |
+ "force_invalid_grant_responses_refresh_token"; |
+ |
+// Match the way Chromium handles authentication errors in |
+// google_apis/gaia/oauth2_access_token_fetcher.cc: |
+GoogleServiceAuthError GetGoogleServiceAuthErrorFromNSError( |
+ ios::ProfileOAuth2TokenServiceIOSProvider* provider, |
+ NSError* error) { |
+ if (!error) |
+ return GoogleServiceAuthError::AuthErrorNone(); |
+ |
+ ios::AuthenticationErrorCategory errorCategory = |
+ provider->GetAuthenticationErrorCategory(error); |
+ switch (errorCategory) { |
+ case ios::kAuthenticationErrorCategoryUnknownErrors: |
+ // Treat all unknown error as unexpected service response errors. |
+ // This may be too general and may require a finer grain filtering. |
+ return GoogleServiceAuthError( |
+ GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE); |
+ case ios::kAuthenticationErrorCategoryAuthorizationErrors: |
+ return GoogleServiceAuthError( |
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); |
+ case ios::kAuthenticationErrorCategoryAuthorizationForbiddenErrors: |
+ // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be |
+ // '403 Rate Limit Exceeded.' (for more details, see |
+ // google_apis/gaia/oauth2_access_token_fetcher.cc). |
+ return GoogleServiceAuthError( |
+ GoogleServiceAuthError::SERVICE_UNAVAILABLE); |
+ case ios::kAuthenticationErrorCategoryNetworkServerErrors: |
+ // Just set the connection error state to FAILED. |
+ return GoogleServiceAuthError::FromConnectionError( |
+ net::URLRequestStatus::FAILED); |
+ case ios::kAuthenticationErrorCategoryUserCancellationErrors: |
+ return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); |
+ case ios::kAuthenticationErrorCategoryUnknownIdentityErrors: |
+ return GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP); |
+ } |
+} |
+ |
+class SSOAccessTokenFetcher : public OAuth2AccessTokenFetcher { |
+ public: |
+ SSOAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer, |
+ ios::ProfileOAuth2TokenServiceIOSProvider* provider, |
+ const std::string account_id); |
+ virtual ~SSOAccessTokenFetcher(); |
+ |
+ virtual void Start(const std::string& client_id, |
+ const std::string& client_secret, |
+ const std::vector<std::string>& scopes) OVERRIDE; |
+ |
+ virtual void CancelRequest() OVERRIDE; |
+ |
+ // Handles an access token response. |
+ void OnAccessTokenResponse(NSString* token, |
+ NSDate* expiration, |
+ NSError* error); |
+ |
+ private: |
+ base::WeakPtrFactory<SSOAccessTokenFetcher> weak_factory_; |
+ ios::ProfileOAuth2TokenServiceIOSProvider* provider_; // weak |
+ std::string account_id_; |
+ bool request_was_cancelled_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(SSOAccessTokenFetcher); |
+}; |
+ |
+SSOAccessTokenFetcher::SSOAccessTokenFetcher( |
+ OAuth2AccessTokenConsumer* consumer, |
+ ios::ProfileOAuth2TokenServiceIOSProvider* provider, |
+ const std::string account_id) |
+ : OAuth2AccessTokenFetcher(consumer), |
+ weak_factory_(this), |
+ provider_(provider), |
+ account_id_(account_id), |
+ request_was_cancelled_(false) { |
+ DCHECK(provider_); |
+} |
+ |
+SSOAccessTokenFetcher::~SSOAccessTokenFetcher() {} |
+ |
+void SSOAccessTokenFetcher::Start(const std::string& client_id, |
+ const std::string& client_secret, |
+ const std::vector<std::string>& scopes) { |
+ std::set<std::string> scopes_set(scopes.begin(), scopes.end()); |
+ provider_->GetAccessToken( |
+ account_id_, client_id, client_secret, scopes_set, |
+ base::Bind(&SSOAccessTokenFetcher::OnAccessTokenResponse, |
+ weak_factory_.GetWeakPtr())); |
+} |
+ |
+void SSOAccessTokenFetcher::CancelRequest() { request_was_cancelled_ = true; } |
+ |
+void SSOAccessTokenFetcher::OnAccessTokenResponse(NSString* token, |
+ NSDate* expiration, |
+ NSError* error) { |
+ if (request_was_cancelled_) { |
+ // Ignore the callback if the request was cancelled. |
+ return; |
+ } |
+ GoogleServiceAuthError auth_error = |
+ GetGoogleServiceAuthErrorFromNSError(provider_, error); |
+ if (auth_error.state() == GoogleServiceAuthError::NONE) { |
+ base::Time expiration_date = |
+ base::Time::FromDoubleT([expiration timeIntervalSince1970]); |
+ FireOnGetTokenSuccess(base::SysNSStringToUTF8(token), expiration_date); |
+ } else { |
+ FireOnGetTokenFailure(auth_error); |
+ } |
+} |
+ |
+// Fetcher that returns INVALID_GAIA_CREDENTIALS responses for all requests. |
+class InvalidGrantAccessTokenFetcher : public OAuth2AccessTokenFetcher { |
+ public: |
+ explicit InvalidGrantAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer); |
+ virtual ~InvalidGrantAccessTokenFetcher(); |
+ |
+ // OAuth2AccessTokenFetcher |
+ virtual void Start(const std::string& client_id, |
+ const std::string& client_secret, |
+ const std::vector<std::string>& scopes) OVERRIDE; |
+ virtual void CancelRequest() OVERRIDE; |
+ |
+ // Fires token failure notifications with INVALID_GAIA_CREDENTIALS error. |
+ void FireInvalidGrant(); |
+ |
+ private: |
+ bool request_was_cancelled_; |
+ DISALLOW_COPY_AND_ASSIGN(InvalidGrantAccessTokenFetcher); |
+}; |
+ |
+InvalidGrantAccessTokenFetcher::InvalidGrantAccessTokenFetcher( |
+ OAuth2AccessTokenConsumer* consumer) |
+ : OAuth2AccessTokenFetcher(consumer), |
+ request_was_cancelled_(false) {} |
+ |
+InvalidGrantAccessTokenFetcher::~InvalidGrantAccessTokenFetcher() {} |
+ |
+void InvalidGrantAccessTokenFetcher::Start( |
+ const std::string& client_id, |
+ const std::string& client_secret, |
+ const std::vector<std::string>& scopes) { |
+ base::MessageLoop::current()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&InvalidGrantAccessTokenFetcher::FireInvalidGrant, |
+ base::Unretained(this))); |
+}; |
+ |
+void InvalidGrantAccessTokenFetcher::FireInvalidGrant() { |
+ if (request_was_cancelled_) |
+ return; |
+ GoogleServiceAuthError auth_error( |
+ GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); |
+ FireOnGetTokenFailure(auth_error); |
+} |
+ |
+void InvalidGrantAccessTokenFetcher::CancelRequest() { |
+ request_was_cancelled_ = true; |
+} |
+ |
+} // namespace |
+ |
+ProfileOAuth2TokenServiceIOS::AccountInfo::AccountInfo( |
+ ProfileOAuth2TokenService* token_service, |
+ const std::string& account_id) |
+ : token_service_(token_service), |
+ account_id_(account_id), |
+ last_auth_error_(GoogleServiceAuthError::NONE) { |
+ DCHECK(token_service_); |
+ DCHECK(!account_id_.empty()); |
+ token_service_->signin_error_controller()->AddProvider(this); |
+} |
+ |
+ProfileOAuth2TokenServiceIOS::AccountInfo::~AccountInfo() { |
+ token_service_->signin_error_controller()->RemoveProvider(this); |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::AccountInfo::SetLastAuthError( |
+ const GoogleServiceAuthError& error) { |
+ if (error.state() != last_auth_error_.state()) { |
+ last_auth_error_ = error; |
+ token_service_->signin_error_controller()->AuthStatusChanged(); |
+ } |
+} |
+ |
+std::string ProfileOAuth2TokenServiceIOS::AccountInfo::GetAccountId() const { |
+ return account_id_; |
+} |
+ |
+GoogleServiceAuthError |
+ProfileOAuth2TokenServiceIOS::AccountInfo::GetAuthStatus() const { |
+ return last_auth_error_; |
+} |
+ |
+ProfileOAuth2TokenServiceIOS::ProfileOAuth2TokenServiceIOS() |
+ : MutableProfileOAuth2TokenService(), |
+ use_legacy_token_service_(false) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+} |
+ |
+ProfileOAuth2TokenServiceIOS::~ProfileOAuth2TokenServiceIOS() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::Initialize(SigninClient* client) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ MutableProfileOAuth2TokenService::Initialize(client); |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::Shutdown() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ CancelAllRequests(); |
+ accounts_.clear(); |
+ MutableProfileOAuth2TokenService::Shutdown(); |
+} |
+ |
+ios::ProfileOAuth2TokenServiceIOSProvider* |
+ProfileOAuth2TokenServiceIOS::GetProvider() { |
+ ios::ProfileOAuth2TokenServiceIOSProvider* provider = |
+ client()->GetIOSProvider(); |
+ DCHECK(provider); |
+ return provider; |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::LoadCredentials( |
+ const std::string& primary_account_id) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ // LoadCredentials() is called iff the user is signed in to Chrome, so the |
+ // primary account id must not be empty. |
+ DCHECK(!primary_account_id.empty()); |
+ |
+ use_legacy_token_service_ = !GetProvider()->IsUsingSharedAuthentication(); |
+ if (use_legacy_token_service_) { |
+ MutableProfileOAuth2TokenService::LoadCredentials(primary_account_id); |
+ return; |
+ } |
+ |
+ GetProvider()->InitializeSharedAuthentication(); |
+ ReloadCredentials(); |
+ FireRefreshTokensLoaded(); |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::ReloadCredentials() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ if (use_legacy_token_service_) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ |
+ // Remove all old accounts that do not appear in |new_accounts| and then |
+ // load |new_accounts|. |
+ std::vector<std::string> new_accounts(GetProvider()->GetAllAccountIds()); |
+ std::vector<std::string> old_accounts(GetAccounts()); |
+ for (auto i = old_accounts.begin(); i != old_accounts.end(); ++i) { |
+ if (std::find(new_accounts.begin(), new_accounts.end(), *i) == |
+ new_accounts.end()) { |
+ RemoveAccount(*i); |
+ } |
+ } |
+ |
+ // Load all new_accounts. |
+ for (auto i = new_accounts.begin(); i != new_accounts.end(); ++i) { |
+ AddOrUpdateAccount(*i); |
+ } |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::UpdateCredentials( |
+ const std::string& account_id, |
+ const std::string& refresh_token) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ if (use_legacy_token_service_) { |
+ MutableProfileOAuth2TokenService::UpdateCredentials(account_id, |
+ refresh_token); |
+ return; |
+ } |
+ NOTREACHED() << "Unexpected call to UpdateCredentials when using shared " |
+ "authentication."; |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::RevokeAllCredentials() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ if (use_legacy_token_service_) { |
+ MutableProfileOAuth2TokenService::RevokeAllCredentials(); |
+ return; |
+ } |
+ |
+ CancelAllRequests(); |
+ ClearCache(); |
+ AccountInfoMap toRemove = accounts_; |
+ for (AccountInfoMap::iterator i = toRemove.begin(); i != toRemove.end(); ++i) |
+ RemoveAccount(i->first); |
+ |
+ DCHECK_EQ(0u, accounts_.size()); |
+} |
+ |
+OAuth2AccessTokenFetcher* |
+ProfileOAuth2TokenServiceIOS::CreateAccessTokenFetcher( |
+ const std::string& account_id, |
+ net::URLRequestContextGetter* getter, |
+ OAuth2AccessTokenConsumer* consumer) { |
+ if (use_legacy_token_service_) { |
+ std::string refresh_token = GetRefreshToken(account_id); |
+ DCHECK(!refresh_token.empty()); |
+ if (refresh_token == kForceInvalidGrantResponsesRefreshToken) { |
+ return new InvalidGrantAccessTokenFetcher(consumer); |
+ } else { |
+ return MutableProfileOAuth2TokenService::CreateAccessTokenFetcher( |
+ account_id, getter, consumer); |
+ } |
+ } |
+ |
+ return new SSOAccessTokenFetcher(consumer, GetProvider(), account_id); |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::ForceInvalidGrantResponses() { |
+ if (!use_legacy_token_service_) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ std::vector<std::string> accounts = |
+ MutableProfileOAuth2TokenService::GetAccounts(); |
+ if (accounts.empty()) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ |
+ std::string first_account_id = *accounts.begin(); |
+ if (RefreshTokenIsAvailable(first_account_id) && |
+ GetRefreshToken(first_account_id) != |
+ kForceInvalidGrantResponsesRefreshToken) { |
+ MutableProfileOAuth2TokenService::RevokeAllCredentials(); |
+ } |
+ |
+ for (auto i = accounts.begin(); i != accounts.end(); ++i) { |
+ std::string account_id = *i; |
+ MutableProfileOAuth2TokenService::UpdateCredentials( |
+ account_id, |
+ kForceInvalidGrantResponsesRefreshToken); |
+ } |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::InvalidateOAuth2Token( |
+ const std::string& account_id, |
+ const std::string& client_id, |
+ const ScopeSet& scopes, |
+ const std::string& access_token) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ // Call |MutableProfileOAuth2TokenService::InvalidateOAuth2Token| to clear the |
+ // cached access token. |
+ MutableProfileOAuth2TokenService::InvalidateOAuth2Token(account_id, |
+ client_id, |
+ scopes, |
+ access_token); |
+ |
+ // There is no need to inform the authentication library that the access |
+ // token is invalid as it never caches the token. |
+} |
+ |
+std::vector<std::string> ProfileOAuth2TokenServiceIOS::GetAccounts() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ if (use_legacy_token_service_) { |
+ return MutableProfileOAuth2TokenService::GetAccounts(); |
+ } |
+ |
+ std::vector<std::string> account_ids; |
+ for (auto i = accounts_.begin(); i != accounts_.end(); ++i) |
+ account_ids.push_back(i->first); |
+ return account_ids; |
+} |
+ |
+bool ProfileOAuth2TokenServiceIOS::RefreshTokenIsAvailable( |
+ const std::string& account_id) const { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ if (use_legacy_token_service_) { |
+ return MutableProfileOAuth2TokenService::RefreshTokenIsAvailable( |
+ account_id); |
+ } |
+ |
+ return accounts_.count(account_id) > 0; |
+} |
+ |
+std::string ProfileOAuth2TokenServiceIOS::GetRefreshToken( |
+ const std::string& account_id) const { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ if (use_legacy_token_service_) |
+ return MutableProfileOAuth2TokenService::GetRefreshToken(account_id); |
+ |
+ // On iOS, the refresh token does not exist as ProfileOAuth2TokenServiceIOS |
+ // fetches the access token from the iOS authentication library. |
+ NOTREACHED(); |
+ return std::string(); |
+} |
+ |
+std::string |
+ProfileOAuth2TokenServiceIOS::GetRefreshTokenWhenNotUsingSharedAuthentication( |
+ const std::string& account_id) { |
+ DCHECK(use_legacy_token_service_); |
+ return GetRefreshToken(account_id); |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::UpdateAuthError( |
+ const std::string& account_id, |
+ const GoogleServiceAuthError& error) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ |
+ if (use_legacy_token_service_) { |
+ MutableProfileOAuth2TokenService::UpdateAuthError(account_id, error); |
+ return; |
+ } |
+ |
+ // Do not report connection errors as these are not actually auth errors. |
+ // We also want to avoid masking a "real" auth error just because we |
+ // subsequently get a transient network error. |
+ if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED || |
+ error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE) { |
+ return; |
+ } |
+ |
+ if (accounts_.count(account_id) == 0) { |
+ NOTREACHED(); |
+ return; |
+ } |
+ accounts_[account_id]->SetLastAuthError(error); |
+} |
+ |
+// Clear the authentication error state and notify all observers that a new |
+// refresh token is available so that they request new access tokens. |
+void ProfileOAuth2TokenServiceIOS::AddOrUpdateAccount( |
+ const std::string& account_id) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ DCHECK(!account_id.empty()); |
Roger Tawa OOO till Jul 10th
2014/04/10 15:04:08
Add dcheck?
DCHECK(!use_legacy_token_service_
msarda
2014/04/10 15:13:36
Done. I'll fix it in a future CL.
|
+ |
+ bool account_present = accounts_.count(account_id) > 0; |
+ if (account_present && accounts_[account_id]->GetAuthStatus().state() == |
+ GoogleServiceAuthError::NONE) { |
+ // No need to update the account if it is already a known account and if |
+ // there is no auth error. |
+ return; |
+ } |
+ |
+ if (account_present) { |
+ CancelRequestsForAccount(account_id); |
+ ClearCacheForAccount(account_id); |
+ } else { |
+ accounts_[account_id].reset(new AccountInfo(this, account_id)); |
+ } |
+ UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone()); |
+ FireRefreshTokenAvailable(account_id); |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::RemoveAccount( |
+ const std::string& account_id) { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ DCHECK(!account_id.empty()); |
Roger Tawa OOO till Jul 10th
2014/04/10 15:04:08
Add dcheck?
DCHECK(!use_legacy_token_service_
msarda
2014/04/10 15:13:36
Done. I'll fix it in a future CL.
|
+ |
+ if (accounts_.count(account_id) > 0) { |
+ CancelRequestsForAccount(account_id); |
+ ClearCacheForAccount(account_id); |
+ accounts_.erase(account_id); |
+ FireRefreshTokenRevoked(account_id); |
+ } |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::StartUsingSharedAuthentication() { |
+ if (!use_legacy_token_service_) |
+ return; |
+ MutableProfileOAuth2TokenService::RevokeAllCredentials(); |
+ use_legacy_token_service_ = false; |
+} |
+ |
+void ProfileOAuth2TokenServiceIOS::SetUseLegacyTokenServiceForTesting( |
+ bool use_legacy_token_service) { |
+ use_legacy_token_service_ = use_legacy_token_service; |
+} |