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 // Match the way Chromium handles authentication errors in
24 // google_apis/gaia/oauth2_access_token_fetcher.cc:
25 GoogleServiceAuthError GetGoogleServiceAuthErrorFromNSError(
26 ios::ProfileOAuth2TokenServiceIOSProvider* provider,
29 return GoogleServiceAuthError::AuthErrorNone();
31 ios::AuthenticationErrorCategory errorCategory =
32 provider->GetAuthenticationErrorCategory(error);
33 switch (errorCategory) {
34 case ios::kAuthenticationErrorCategoryUnknownErrors:
35 // Treat all unknown error as unexpected service response errors.
36 // This may be too general and may require a finer grain filtering.
37 return GoogleServiceAuthError(
38 GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE);
39 case ios::kAuthenticationErrorCategoryAuthorizationErrors:
40 return GoogleServiceAuthError(
41 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
42 case ios::kAuthenticationErrorCategoryAuthorizationForbiddenErrors:
43 // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be
44 // '403 Rate Limit Exceeded.' (for more details, see
45 // google_apis/gaia/oauth2_access_token_fetcher.cc).
46 return GoogleServiceAuthError(
47 GoogleServiceAuthError::SERVICE_UNAVAILABLE);
48 case ios::kAuthenticationErrorCategoryNetworkServerErrors:
49 // Just set the connection error state to FAILED.
50 return GoogleServiceAuthError::FromConnectionError(
51 net::URLRequestStatus::FAILED);
52 case ios::kAuthenticationErrorCategoryUserCancellationErrors:
53 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
54 case ios::kAuthenticationErrorCategoryUnknownIdentityErrors:
55 return GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP);
59 class SSOAccessTokenFetcher : public OAuth2AccessTokenFetcher {
61 SSOAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer,
62 ios::ProfileOAuth2TokenServiceIOSProvider* provider,
63 const std::string account_id);
64 virtual ~SSOAccessTokenFetcher();
66 virtual void Start(const std::string& client_id,
67 const std::string& client_secret,
68 const std::vector<std::string>& scopes) override;
70 virtual void CancelRequest() override;
72 // Handles an access token response.
73 void OnAccessTokenResponse(NSString* token,
78 base::WeakPtrFactory<SSOAccessTokenFetcher> weak_factory_;
79 ios::ProfileOAuth2TokenServiceIOSProvider* provider_; // weak
80 std::string account_id_;
81 bool request_was_cancelled_;
83 DISALLOW_COPY_AND_ASSIGN(SSOAccessTokenFetcher);
86 SSOAccessTokenFetcher::SSOAccessTokenFetcher(
87 OAuth2AccessTokenConsumer* consumer,
88 ios::ProfileOAuth2TokenServiceIOSProvider* provider,
89 const std::string account_id)
90 : OAuth2AccessTokenFetcher(consumer),
93 account_id_(account_id),
94 request_was_cancelled_(false) {
98 SSOAccessTokenFetcher::~SSOAccessTokenFetcher() {}
100 void SSOAccessTokenFetcher::Start(const std::string& client_id,
101 const std::string& client_secret,
102 const std::vector<std::string>& scopes) {
103 std::set<std::string> scopes_set(scopes.begin(), scopes.end());
104 provider_->GetAccessToken(
105 account_id_, client_id, client_secret, scopes_set,
106 base::Bind(&SSOAccessTokenFetcher::OnAccessTokenResponse,
107 weak_factory_.GetWeakPtr()));
110 void SSOAccessTokenFetcher::CancelRequest() { request_was_cancelled_ = true; }
112 void SSOAccessTokenFetcher::OnAccessTokenResponse(NSString* token,
115 if (request_was_cancelled_) {
116 // Ignore the callback if the request was cancelled.
119 GoogleServiceAuthError auth_error =
120 GetGoogleServiceAuthErrorFromNSError(provider_, error);
121 if (auth_error.state() == GoogleServiceAuthError::NONE) {
122 base::Time expiration_date =
123 base::Time::FromDoubleT([expiration timeIntervalSince1970]);
124 FireOnGetTokenSuccess(base::SysNSStringToUTF8(token), expiration_date);
126 FireOnGetTokenFailure(auth_error);
132 ProfileOAuth2TokenServiceIOS::AccountInfo::AccountInfo(
133 ProfileOAuth2TokenService* token_service,
134 const std::string& account_id)
135 : token_service_(token_service),
136 account_id_(account_id),
137 last_auth_error_(GoogleServiceAuthError::NONE) {
138 DCHECK(token_service_);
139 DCHECK(!account_id_.empty());
140 token_service_->signin_error_controller()->AddProvider(this);
143 ProfileOAuth2TokenServiceIOS::AccountInfo::~AccountInfo() {
144 token_service_->signin_error_controller()->RemoveProvider(this);
147 void ProfileOAuth2TokenServiceIOS::AccountInfo::SetLastAuthError(
148 const GoogleServiceAuthError& error) {
149 if (error.state() != last_auth_error_.state()) {
150 last_auth_error_ = error;
151 token_service_->signin_error_controller()->AuthStatusChanged();
155 std::string ProfileOAuth2TokenServiceIOS::AccountInfo::GetAccountId() const {
159 std::string ProfileOAuth2TokenServiceIOS::AccountInfo::GetUsername() const {
160 // TODO(rogerta): when |account_id| becomes the obfuscated gaia id, this
161 // will need to be changed.
165 GoogleServiceAuthError
166 ProfileOAuth2TokenServiceIOS::AccountInfo::GetAuthStatus() const {
167 return last_auth_error_;
170 ProfileOAuth2TokenServiceIOS::ProfileOAuth2TokenServiceIOS()
171 : ProfileOAuth2TokenService() {
172 DCHECK(thread_checker_.CalledOnValidThread());
175 ProfileOAuth2TokenServiceIOS::~ProfileOAuth2TokenServiceIOS() {
176 DCHECK(thread_checker_.CalledOnValidThread());
179 void ProfileOAuth2TokenServiceIOS::Initialize(SigninClient* client) {
180 DCHECK(thread_checker_.CalledOnValidThread());
181 ProfileOAuth2TokenService::Initialize(client);
184 void ProfileOAuth2TokenServiceIOS::Shutdown() {
185 DCHECK(thread_checker_.CalledOnValidThread());
188 ProfileOAuth2TokenService::Shutdown();
191 ios::ProfileOAuth2TokenServiceIOSProvider*
192 ProfileOAuth2TokenServiceIOS::GetProvider() {
193 ios::ProfileOAuth2TokenServiceIOSProvider* provider =
194 client()->GetIOSProvider();
199 void ProfileOAuth2TokenServiceIOS::LoadCredentials(
200 const std::string& primary_account_id) {
201 DCHECK(thread_checker_.CalledOnValidThread());
203 // LoadCredentials() is called iff the user is signed in to Chrome, so the
204 // primary account id must not be empty.
205 DCHECK(!primary_account_id.empty());
207 GetProvider()->InitializeSharedAuthentication();
209 FireRefreshTokensLoaded();
212 void ProfileOAuth2TokenServiceIOS::ReloadCredentials() {
213 DCHECK(thread_checker_.CalledOnValidThread());
215 ScopedBatchChange batch(this);
217 // Remove all old accounts that do not appear in |new_accounts| and then
218 // load |new_accounts|.
219 std::vector<std::string> new_accounts(GetProvider()->GetAllAccountIds());
220 std::vector<std::string> old_accounts(GetAccounts());
221 for (auto i = old_accounts.begin(); i != old_accounts.end(); ++i) {
222 if (std::find(new_accounts.begin(), new_accounts.end(), *i) ==
223 new_accounts.end()) {
228 // Load all new_accounts.
229 for (auto i = new_accounts.begin(); i != new_accounts.end(); ++i) {
230 AddOrUpdateAccount(*i);
234 void ProfileOAuth2TokenServiceIOS::UpdateCredentials(
235 const std::string& account_id,
236 const std::string& refresh_token) {
237 DCHECK(thread_checker_.CalledOnValidThread());
238 NOTREACHED() << "Unexpected call to UpdateCredentials when using shared "
242 void ProfileOAuth2TokenServiceIOS::RevokeAllCredentials() {
243 DCHECK(thread_checker_.CalledOnValidThread());
245 ScopedBatchChange batch(this);
248 AccountInfoMap toRemove = accounts_;
249 for (AccountInfoMap::iterator i = toRemove.begin(); i != toRemove.end(); ++i)
250 RemoveAccount(i->first);
252 DCHECK_EQ(0u, accounts_.size());
255 OAuth2AccessTokenFetcher*
256 ProfileOAuth2TokenServiceIOS::CreateAccessTokenFetcher(
257 const std::string& account_id,
258 net::URLRequestContextGetter* getter,
259 OAuth2AccessTokenConsumer* consumer) {
260 return new SSOAccessTokenFetcher(consumer, GetProvider(), account_id);
263 void ProfileOAuth2TokenServiceIOS::InvalidateOAuth2Token(
264 const std::string& account_id,
265 const std::string& client_id,
266 const ScopeSet& scopes,
267 const std::string& access_token) {
268 DCHECK(thread_checker_.CalledOnValidThread());
270 // Call |ProfileOAuth2TokenService::InvalidateOAuth2Token| to clear the
271 // cached access token.
272 ProfileOAuth2TokenService::InvalidateOAuth2Token(account_id,
277 // There is no need to inform the authentication library that the access
278 // token is invalid as it never caches the token.
281 std::vector<std::string> ProfileOAuth2TokenServiceIOS::GetAccounts() {
282 DCHECK(thread_checker_.CalledOnValidThread());
283 std::vector<std::string> account_ids;
284 for (auto i = accounts_.begin(); i != accounts_.end(); ++i)
285 account_ids.push_back(i->first);
289 bool ProfileOAuth2TokenServiceIOS::RefreshTokenIsAvailable(
290 const std::string& account_id) const {
291 DCHECK(thread_checker_.CalledOnValidThread());
293 return accounts_.count(account_id) > 0;
296 void ProfileOAuth2TokenServiceIOS::UpdateAuthError(
297 const std::string& account_id,
298 const GoogleServiceAuthError& error) {
299 DCHECK(thread_checker_.CalledOnValidThread());
301 // Do not report connection errors as these are not actually auth errors.
302 // We also want to avoid masking a "real" auth error just because we
303 // subsequently get a transient network error.
304 if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED ||
305 error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE) {
309 if (accounts_.count(account_id) == 0) {
313 accounts_[account_id]->SetLastAuthError(error);
316 // Clear the authentication error state and notify all observers that a new
317 // refresh token is available so that they request new access tokens.
318 void ProfileOAuth2TokenServiceIOS::AddOrUpdateAccount(
319 const std::string& account_id) {
320 DCHECK(thread_checker_.CalledOnValidThread());
321 DCHECK(!account_id.empty());
323 bool account_present = accounts_.count(account_id) > 0;
324 if (account_present && accounts_[account_id]->GetAuthStatus().state() ==
325 GoogleServiceAuthError::NONE) {
326 // No need to update the account if it is already a known account and if
327 // there is no auth error.
331 if (account_present) {
332 CancelRequestsForAccount(account_id);
333 ClearCacheForAccount(account_id);
335 accounts_[account_id].reset(new AccountInfo(this, account_id));
337 UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
338 FireRefreshTokenAvailable(account_id);
341 void ProfileOAuth2TokenServiceIOS::RemoveAccount(
342 const std::string& account_id) {
343 DCHECK(thread_checker_.CalledOnValidThread());
344 DCHECK(!account_id.empty());
346 if (accounts_.count(account_id) > 0) {
347 CancelRequestsForAccount(account_id);
348 ClearCacheForAccount(account_id);
349 accounts_.erase(account_id);
350 FireRefreshTokenRevoked(account_id);