Roll src/third_party/WebKit 9f7fb92:f103b33 (svn 202621:202622)
[chromium-blink-merge.git] / components / signin / ios / browser / profile_oauth2_token_service_ios_delegate.mm
blob68ce674239fa52faea71a0e07438308f251ccd20
1 // Copyright 2015 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_delegate.h"
7 #include <Foundation/Foundation.h>
9 #include <set>
10 #include <string>
11 #include <vector>
13 #include "base/bind.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/prefs/pref_service.h"
17 #include "base/prefs/scoped_user_pref_update.h"
18 #include "base/stl_util.h"
19 #include "base/strings/sys_string_conversions.h"
20 #include "base/values.h"
21 #include "components/signin/core/browser/account_info.h"
22 #include "components/signin/core/browser/account_tracker_service.h"
23 #include "components/signin/core/browser/signin_client.h"
24 #include "components/signin/core/common/signin_pref_names.h"
25 #include "components/signin/ios/browser/profile_oauth2_token_service_ios_provider.h"
26 #include "google_apis/gaia/oauth2_access_token_fetcher.h"
27 #include "net/url_request/url_request_status.h"
29 namespace {
31 // Match the way Chromium handles authentication errors in
32 // google_apis/gaia/oauth2_access_token_fetcher.cc:
33 GoogleServiceAuthError GetGoogleServiceAuthErrorFromNSError(
34     ProfileOAuth2TokenServiceIOSProvider* provider,
35     NSError* error) {
36   if (!error)
37     return GoogleServiceAuthError::AuthErrorNone();
39   AuthenticationErrorCategory errorCategory =
40       provider->GetAuthenticationErrorCategory(error);
41   switch (errorCategory) {
42     case kAuthenticationErrorCategoryUnknownErrors:
43       // Treat all unknown error as unexpected service response errors.
44       // This may be too general and may require a finer grain filtering.
45       return GoogleServiceAuthError(
46           GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE);
47     case kAuthenticationErrorCategoryAuthorizationErrors:
48       return GoogleServiceAuthError(
49           GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
50     case kAuthenticationErrorCategoryAuthorizationForbiddenErrors:
51       // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be
52       // '403 Rate Limit Exceeded.' (for more details, see
53       // google_apis/gaia/oauth2_access_token_fetcher.cc).
54       return GoogleServiceAuthError(
55           GoogleServiceAuthError::SERVICE_UNAVAILABLE);
56     case kAuthenticationErrorCategoryNetworkServerErrors:
57       // Just set the connection error state to FAILED.
58       return GoogleServiceAuthError::FromConnectionError(
59           net::URLRequestStatus::FAILED);
60     case kAuthenticationErrorCategoryUserCancellationErrors:
61       return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
62     case kAuthenticationErrorCategoryUnknownIdentityErrors:
63       return GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP);
64   }
67 class SSOAccessTokenFetcher : public OAuth2AccessTokenFetcher {
68  public:
69   SSOAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer,
70                         ProfileOAuth2TokenServiceIOSProvider* provider,
71                         const AccountInfo& account);
72   ~SSOAccessTokenFetcher() override;
74   void Start(const std::string& client_id,
75              const std::string& client_secret,
76              const std::vector<std::string>& scopes) override;
78   void CancelRequest() override;
80   // Handles an access token response.
81   void OnAccessTokenResponse(NSString* token,
82                              NSDate* expiration,
83                              NSError* error);
85  private:
86   ProfileOAuth2TokenServiceIOSProvider* provider_;  // weak
87   AccountInfo account_;
88   bool request_was_cancelled_;
89   base::WeakPtrFactory<SSOAccessTokenFetcher> weak_factory_;
91   DISALLOW_COPY_AND_ASSIGN(SSOAccessTokenFetcher);
94 SSOAccessTokenFetcher::SSOAccessTokenFetcher(
95     OAuth2AccessTokenConsumer* consumer,
96     ProfileOAuth2TokenServiceIOSProvider* provider,
97     const AccountInfo& account)
98     : OAuth2AccessTokenFetcher(consumer),
99       provider_(provider),
100       account_(account),
101       request_was_cancelled_(false),
102       weak_factory_(this) {
103   DCHECK(provider_);
106 SSOAccessTokenFetcher::~SSOAccessTokenFetcher() {
109 void SSOAccessTokenFetcher::Start(const std::string& client_id,
110                                   const std::string& client_secret,
111                                   const std::vector<std::string>& scopes) {
112   std::set<std::string> scopes_set(scopes.begin(), scopes.end());
113   provider_->GetAccessToken(
114       account_.gaia, client_id, client_secret, scopes_set,
115       base::Bind(&SSOAccessTokenFetcher::OnAccessTokenResponse,
116                  weak_factory_.GetWeakPtr()));
119 void SSOAccessTokenFetcher::CancelRequest() {
120   request_was_cancelled_ = true;
123 void SSOAccessTokenFetcher::OnAccessTokenResponse(NSString* token,
124                                                   NSDate* expiration,
125                                                   NSError* error) {
126   if (request_was_cancelled_) {
127     // Ignore the callback if the request was cancelled.
128     return;
129   }
130   GoogleServiceAuthError auth_error =
131       GetGoogleServiceAuthErrorFromNSError(provider_, error);
132   if (auth_error.state() == GoogleServiceAuthError::NONE) {
133     base::Time expiration_date =
134         base::Time::FromDoubleT([expiration timeIntervalSince1970]);
135     FireOnGetTokenSuccess(base::SysNSStringToUTF8(token), expiration_date);
136   } else {
137     FireOnGetTokenFailure(auth_error);
138   }
141 }  // namespace
143 ProfileOAuth2TokenServiceIOSDelegate::AccountStatus::AccountStatus(
144     SigninErrorController* signin_error_controller,
145     const std::string& account_id)
146     : signin_error_controller_(signin_error_controller),
147       account_id_(account_id),
148       last_auth_error_(GoogleServiceAuthError::NONE) {
149   DCHECK(signin_error_controller_);
150   DCHECK(!account_id_.empty());
151   signin_error_controller_->AddProvider(this);
154 ProfileOAuth2TokenServiceIOSDelegate::AccountStatus::~AccountStatus() {
155   signin_error_controller_->RemoveProvider(this);
158 void ProfileOAuth2TokenServiceIOSDelegate::AccountStatus::SetLastAuthError(
159     const GoogleServiceAuthError& error) {
160   if (error.state() != last_auth_error_.state()) {
161     last_auth_error_ = error;
162     signin_error_controller_->AuthStatusChanged();
163   }
166 std::string ProfileOAuth2TokenServiceIOSDelegate::AccountStatus::GetAccountId()
167     const {
168   return account_id_;
171 GoogleServiceAuthError
172 ProfileOAuth2TokenServiceIOSDelegate::AccountStatus::GetAuthStatus() const {
173   return last_auth_error_;
176 ProfileOAuth2TokenServiceIOSDelegate::ProfileOAuth2TokenServiceIOSDelegate(
177     SigninClient* client,
178     ProfileOAuth2TokenServiceIOSProvider* provider,
179     AccountTrackerService* account_tracker_service,
180     SigninErrorController* signin_error_controller)
181     : client_(client),
182       provider_(provider),
183       account_tracker_service_(account_tracker_service),
184       signin_error_controller_(signin_error_controller) {
185   DCHECK(client_);
186   DCHECK(provider_);
187   DCHECK(account_tracker_service_);
188   DCHECK(signin_error_controller_);
191 ProfileOAuth2TokenServiceIOSDelegate::~ProfileOAuth2TokenServiceIOSDelegate() {
192   DCHECK(thread_checker_.CalledOnValidThread());
195 void ProfileOAuth2TokenServiceIOSDelegate::Shutdown() {
196   DCHECK(thread_checker_.CalledOnValidThread());
197   accounts_.clear();
200 void ProfileOAuth2TokenServiceIOSDelegate::LoadCredentials(
201     const std::string& primary_account_id) {
202   DCHECK(thread_checker_.CalledOnValidThread());
203   if (account_tracker_service_->GetMigrationState() ==
204       AccountTrackerService::MIGRATION_IN_PROGRESS) {
205     MigrateExcludedSecondaryAccountIds();
206   }
208   // LoadCredentials() is called iff the user is signed in to Chrome, so the
209   // primary account id must not be empty.
210   DCHECK(!primary_account_id.empty());
212   ReloadCredentials(primary_account_id);
213   FireRefreshTokensLoaded();
216 void ProfileOAuth2TokenServiceIOSDelegate::ReloadCredentials(
217     const std::string& primary_account_id) {
218   DCHECK(!primary_account_id.empty());
219   DCHECK(primary_account_id_.empty() ||
220          primary_account_id_ == primary_account_id);
221   primary_account_id_ = primary_account_id;
222   ReloadCredentials();
225 void ProfileOAuth2TokenServiceIOSDelegate::ReloadCredentials() {
226   DCHECK(thread_checker_.CalledOnValidThread());
227   if (primary_account_id_.empty()) {
228     // Avoid loading the credentials if there is no primary account id.
229     return;
230   }
232   // Get the list of new account ids.
233   std::set<std::string> excluded_account_ids = GetExcludedSecondaryAccounts();
234   std::set<std::string> new_account_ids;
235   for (const auto& new_account : provider_->GetAllAccounts()) {
236     DCHECK(!new_account.gaia.empty());
237     DCHECK(!new_account.email.empty());
238     if (!IsAccountExcluded(new_account.gaia, new_account.email,
239                            excluded_account_ids)) {
240       // Account must to be seeded before adding an account to ensure that
241       // the GAIA ID is available if any client of this token service starts
242       // a fetch access token operation when it receives a
243       // |OnRefreshTokenAvailable| notification.
244       std::string account_id = account_tracker_service_->SeedAccountInfo(
245           new_account.gaia, new_account.email);
246       new_account_ids.insert(account_id);
247     }
248   }
250   // Get the list of existing account ids.
251   std::vector<std::string> old_account_ids = GetAccounts();
252   std::sort(old_account_ids.begin(), old_account_ids.end());
254   std::set<std::string> accounts_to_add =
255       base::STLSetDifference<std::set<std::string>>(new_account_ids,
256                                                     old_account_ids);
257   std::set<std::string> accounts_to_remove =
258       base::STLSetDifference<std::set<std::string>>(old_account_ids,
259                                                     new_account_ids);
260   if (accounts_to_add.empty() && accounts_to_remove.empty())
261     return;
263   // Remove all old accounts that do not appear in |new_accounts| and then
264   // load |new_accounts|.
265   ScopedBatchChange batch(this);
266   for (const auto& account_to_remove : accounts_to_remove) {
267     RemoveAccount(account_to_remove);
268   }
270   // Load all new_accounts.
271   for (const auto& account_to_add : accounts_to_add) {
272     AddOrUpdateAccount(account_to_add);
273   }
276 void ProfileOAuth2TokenServiceIOSDelegate::UpdateCredentials(
277     const std::string& account_id,
278     const std::string& refresh_token) {
279   DCHECK(thread_checker_.CalledOnValidThread());
280   NOTREACHED() << "Unexpected call to UpdateCredentials when using shared "
281                   "authentication.";
284 void ProfileOAuth2TokenServiceIOSDelegate::RevokeAllCredentials() {
285   DCHECK(thread_checker_.CalledOnValidThread());
287   ScopedBatchChange batch(this);
288   AccountStatusMap toRemove = accounts_;
289   for (auto& accountStatus : toRemove)
290     RemoveAccount(accountStatus.first);
292   DCHECK_EQ(0u, accounts_.size());
293   primary_account_id_.clear();
294   ClearExcludedSecondaryAccounts();
297 OAuth2AccessTokenFetcher*
298 ProfileOAuth2TokenServiceIOSDelegate::CreateAccessTokenFetcher(
299     const std::string& account_id,
300     net::URLRequestContextGetter* getter,
301     OAuth2AccessTokenConsumer* consumer) {
302   AccountInfo account_info =
303       account_tracker_service_->GetAccountInfo(account_id);
304   return new SSOAccessTokenFetcher(consumer, provider_, account_info);
307 std::vector<std::string> ProfileOAuth2TokenServiceIOSDelegate::GetAccounts() {
308   DCHECK(thread_checker_.CalledOnValidThread());
309   std::vector<std::string> account_ids;
310   for (auto i = accounts_.begin(); i != accounts_.end(); ++i)
311     account_ids.push_back(i->first);
312   return account_ids;
315 bool ProfileOAuth2TokenServiceIOSDelegate::RefreshTokenIsAvailable(
316     const std::string& account_id) const {
317   DCHECK(thread_checker_.CalledOnValidThread());
319   return accounts_.count(account_id) > 0;
322 bool ProfileOAuth2TokenServiceIOSDelegate::RefreshTokenHasError(
323     const std::string& account_id) const {
324   DCHECK(thread_checker_.CalledOnValidThread());
325   auto it = accounts_.find(account_id);
326   // TODO(rogerta): should we distinguish between transient and persistent?
327   return it == accounts_.end() ? false : IsError(it->second->GetAuthStatus());
330 void ProfileOAuth2TokenServiceIOSDelegate::UpdateAuthError(
331     const std::string& account_id,
332     const GoogleServiceAuthError& error) {
333   DCHECK(thread_checker_.CalledOnValidThread());
335   // Do not report connection errors as these are not actually auth errors.
336   // We also want to avoid masking a "real" auth error just because we
337   // subsequently get a transient network error.
338   if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED ||
339       error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE) {
340     return;
341   }
343   if (accounts_.count(account_id) == 0) {
344     // Nothing to update as the account has already been removed.
345     return;
346   }
347   accounts_[account_id]->SetLastAuthError(error);
350 // Clear the authentication error state and notify all observers that a new
351 // refresh token is available so that they request new access tokens.
352 void ProfileOAuth2TokenServiceIOSDelegate::AddOrUpdateAccount(
353     const std::string& account_id) {
354   DCHECK(thread_checker_.CalledOnValidThread());
356   // Account must have been seeded before attempting to add it.
357   DCHECK(!account_tracker_service_->GetAccountInfo(account_id).gaia.empty());
358   DCHECK(!account_tracker_service_->GetAccountInfo(account_id).email.empty());
360   bool account_present = accounts_.count(account_id) > 0;
361   if (account_present &&
362       accounts_[account_id]->GetAuthStatus().state() ==
363           GoogleServiceAuthError::NONE) {
364     // No need to update the account if it is already a known account and if
365     // there is no auth error.
366     return;
367   }
369   if (!account_present) {
370     accounts_[account_id].reset(
371         new AccountStatus(signin_error_controller_, account_id));
372   }
374   UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
375   FireRefreshTokenAvailable(account_id);
378 void ProfileOAuth2TokenServiceIOSDelegate::RemoveAccount(
379     const std::string& account_id) {
380   DCHECK(thread_checker_.CalledOnValidThread());
381   DCHECK(!account_id.empty());
383   if (accounts_.count(account_id) > 0) {
384     accounts_.erase(account_id);
385     FireRefreshTokenRevoked(account_id);
386   }
389 std::set<std::string>
390 ProfileOAuth2TokenServiceIOSDelegate::GetExcludedSecondaryAccounts() {
391   const base::ListValue* excluded_secondary_accounts_pref =
392       client_->GetPrefs()->GetList(
393           prefs::kTokenServiceExcludedSecondaryAccounts);
394   std::set<std::string> excluded_secondary_accounts;
395   for (base::Value* pref_value : *excluded_secondary_accounts_pref) {
396     std::string value;
397     if (pref_value->GetAsString(&value))
398       excluded_secondary_accounts.insert(value);
399   }
400   return excluded_secondary_accounts;
403 void ProfileOAuth2TokenServiceIOSDelegate::ExcludeSecondaryAccounts(
404     const std::vector<std::string>& account_ids) {
405   for (const auto& account_id : account_ids)
406     ExcludeSecondaryAccount(account_id);
409 void ProfileOAuth2TokenServiceIOSDelegate::ExcludeSecondaryAccount(
410     const std::string& account_id) {
411   if (GetExcludeAllSecondaryAccounts()) {
412     // Avoid excluding individual secondary accounts when all secondary
413     // accounts are excluded.
414     return;
415   }
417   DCHECK(!account_id.empty());
418   ListPrefUpdate update(client_->GetPrefs(),
419                         prefs::kTokenServiceExcludedSecondaryAccounts);
420   base::ListValue* excluded_secondary_accounts = update.Get();
421   for (base::Value* pref_value : *excluded_secondary_accounts) {
422     std::string value_at_it;
423     if (pref_value->GetAsString(&value_at_it) && (value_at_it == account_id)) {
424       // |account_id| is already excluded.
425       return;
426     }
427   }
428   excluded_secondary_accounts->AppendString(account_id);
431 void ProfileOAuth2TokenServiceIOSDelegate::IncludeSecondaryAccount(
432     const std::string& account_id) {
433   if (GetExcludeAllSecondaryAccounts()) {
434     // Avoid including individual secondary accounts when all secondary
435     // accounts are excluded.
436     return;
437   }
439   DCHECK_NE(account_id, primary_account_id_);
440   DCHECK(!primary_account_id_.empty());
442   // Excluded secondary account ids is a logical set (not a list) of accounts.
443   // As the value stored in the excluded account ids preference is a list,
444   // the code below removes all occurences of |account_id| from this list. This
445   // ensures that |account_id| is actually included even in cases when the
446   // preference value was corrupted (see bug http://crbug.com/453470 as
447   // example).
448   ListPrefUpdate update(client_->GetPrefs(),
449                         prefs::kTokenServiceExcludedSecondaryAccounts);
450   base::ListValue* excluded_secondary_accounts = update.Get();
451   base::ListValue::iterator it = excluded_secondary_accounts->begin();
452   while (it != excluded_secondary_accounts->end()) {
453     base::Value* pref_value = *it;
454     std::string value_at_it;
455     if (pref_value->GetAsString(&value_at_it) && (value_at_it == account_id)) {
456       it = excluded_secondary_accounts->Erase(it, nullptr);
457       continue;
458     }
459     ++it;
460   }
463 bool ProfileOAuth2TokenServiceIOSDelegate::GetExcludeAllSecondaryAccounts() {
464   return client_->GetPrefs()->GetBoolean(
465       prefs::kTokenServiceExcludeAllSecondaryAccounts);
468 void ProfileOAuth2TokenServiceIOSDelegate::ExcludeAllSecondaryAccounts() {
469   client_->GetPrefs()->SetBoolean(
470       prefs::kTokenServiceExcludeAllSecondaryAccounts, true);
473 void ProfileOAuth2TokenServiceIOSDelegate::ClearExcludedSecondaryAccounts() {
474   client_->GetPrefs()->ClearPref(
475       prefs::kTokenServiceExcludeAllSecondaryAccounts);
476   client_->GetPrefs()->ClearPref(prefs::kTokenServiceExcludedSecondaryAccounts);
479 bool ProfileOAuth2TokenServiceIOSDelegate::IsAccountExcluded(
480     const std::string& gaia,
481     const std::string& email,
482     const std::set<std::string>& excluded_account_ids) {
483   std::string account_id =
484       account_tracker_service_->PickAccountIdForAccount(gaia, email);
485   if (account_id == primary_account_id_) {
486     // Only secondary account ids are excluded.
487     return false;
488   }
490   if (GetExcludeAllSecondaryAccounts())
491     return true;
492   return excluded_account_ids.count(account_id) > 0;
495 void ProfileOAuth2TokenServiceIOSDelegate::
496     MigrateExcludedSecondaryAccountIds() {
497   DCHECK_EQ(AccountTrackerService::MIGRATION_IN_PROGRESS,
498             account_tracker_service_->GetMigrationState());
500   // Before the account id migration, emails were used as account identifiers.
501   // Thus the pref |prefs::kTokenServiceExcludedSecondaryAccounts| holds the
502   // emails of the excluded secondary accounts.
503   std::set<std::string> excluded_emails = GetExcludedSecondaryAccounts();
504   if (excluded_emails.empty())
505     return;
507   std::vector<std::string> excluded_account_ids;
508   for (const std::string& excluded_email : excluded_emails) {
509     ProfileOAuth2TokenServiceIOSProvider::AccountInfo account_info =
510         provider_->GetAccountInfoForEmail(excluded_email);
511     if (account_info.gaia.empty()) {
512       // The provider no longer has an account with email |excluded_email|.
513       // This can occur for 2 reasons:
514       // 1. The account with email |excluded_email| was removed before being
515       //   migrated. It may simply be ignored in this case (no need to exclude
516       //   an account that is no longer available).
517       // 2. The migration of the excluded account ids was already done before,
518       //   but the entire migration of the accounts did not end for whatever
519       //   reason (e.g. the app crashed during the previous attempt to migrate
520       //   the accounts). The entire migration should be ignored in this case.
521       if (provider_->GetAccountInfoForGaia(excluded_email).gaia.empty()) {
522         // Case 1 above (account was removed).
523         DVLOG(1) << "Excluded secondary account with email " << excluded_email
524                  << " was removed before migration.";
525       } else {
526         // Case 2 above (migration already done).
527         DVLOG(1) << "Excluded secondary account ids were already migrated.";
528         return;
529       }
530     } else {
531       std::string excluded_account_id =
532           account_tracker_service_->PickAccountIdForAccount(account_info.gaia,
533                                                             account_info.email);
534       excluded_account_ids.push_back(excluded_account_id);
535     }
536   }
537   ClearExcludedSecondaryAccounts();
538   ExcludeSecondaryAccounts(excluded_account_ids);