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