Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(225)

Side by Side Diff: components/signin/ios/browser/profile_oauth2_token_service_ios.mm

Issue 1143323005: Refactor AO2TS to make it easier to componentize. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Address final comments Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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/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/strings/sys_string_conversions.h"
19 #include "base/values.h"
20 #include "components/signin/core/browser/signin_client.h"
21 #include "components/signin/core/common/signin_pref_names.h"
22 #include "google_apis/gaia/oauth2_access_token_fetcher.h"
23 #include "ios/public/provider/components/signin/browser/profile_oauth2_token_ser vice_ios_provider.h"
24 #include "net/url_request/url_request_status.h"
25
26 namespace {
27
28 // Match the way Chromium handles authentication errors in
29 // google_apis/gaia/oauth2_access_token_fetcher.cc:
30 GoogleServiceAuthError GetGoogleServiceAuthErrorFromNSError(
31 ios::ProfileOAuth2TokenServiceIOSProvider* provider,
32 NSError* error) {
33 if (!error)
34 return GoogleServiceAuthError::AuthErrorNone();
35
36 ios::AuthenticationErrorCategory errorCategory =
37 provider->GetAuthenticationErrorCategory(error);
38 switch (errorCategory) {
39 case ios::kAuthenticationErrorCategoryUnknownErrors:
40 // Treat all unknown error as unexpected service response errors.
41 // This may be too general and may require a finer grain filtering.
42 return GoogleServiceAuthError(
43 GoogleServiceAuthError::UNEXPECTED_SERVICE_RESPONSE);
44 case ios::kAuthenticationErrorCategoryAuthorizationErrors:
45 return GoogleServiceAuthError(
46 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS);
47 case ios::kAuthenticationErrorCategoryAuthorizationForbiddenErrors:
48 // HTTP_FORBIDDEN (403) is treated as temporary error, because it may be
49 // '403 Rate Limit Exceeded.' (for more details, see
50 // google_apis/gaia/oauth2_access_token_fetcher.cc).
51 return GoogleServiceAuthError(
52 GoogleServiceAuthError::SERVICE_UNAVAILABLE);
53 case ios::kAuthenticationErrorCategoryNetworkServerErrors:
54 // Just set the connection error state to FAILED.
55 return GoogleServiceAuthError::FromConnectionError(
56 net::URLRequestStatus::FAILED);
57 case ios::kAuthenticationErrorCategoryUserCancellationErrors:
58 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
59 case ios::kAuthenticationErrorCategoryUnknownIdentityErrors:
60 return GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP);
61 }
62 }
63
64 class SSOAccessTokenFetcher : public OAuth2AccessTokenFetcher {
65 public:
66 SSOAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer,
67 ios::ProfileOAuth2TokenServiceIOSProvider* provider,
68 const std::string account_id);
69 ~SSOAccessTokenFetcher() override;
70
71 void Start(const std::string& client_id,
72 const std::string& client_secret,
73 const std::vector<std::string>& scopes) override;
74
75 void CancelRequest() override;
76
77 // Handles an access token response.
78 void OnAccessTokenResponse(NSString* token,
79 NSDate* expiration,
80 NSError* error);
81
82 private:
83 ios::ProfileOAuth2TokenServiceIOSProvider* provider_; // weak
84 std::string account_id_;
85 bool request_was_cancelled_;
86 base::WeakPtrFactory<SSOAccessTokenFetcher> weak_factory_;
87
88 DISALLOW_COPY_AND_ASSIGN(SSOAccessTokenFetcher);
89 };
90
91 SSOAccessTokenFetcher::SSOAccessTokenFetcher(
92 OAuth2AccessTokenConsumer* consumer,
93 ios::ProfileOAuth2TokenServiceIOSProvider* provider,
94 const std::string account_id)
95 : OAuth2AccessTokenFetcher(consumer),
96 provider_(provider),
97 account_id_(account_id),
98 request_was_cancelled_(false),
99 weak_factory_(this) {
100 DCHECK(provider_);
101 }
102
103 SSOAccessTokenFetcher::~SSOAccessTokenFetcher() {}
104
105 void SSOAccessTokenFetcher::Start(const std::string& client_id,
106 const std::string& client_secret,
107 const std::vector<std::string>& scopes) {
108 std::set<std::string> scopes_set(scopes.begin(), scopes.end());
109 provider_->GetAccessToken(
110 account_id_, client_id, client_secret, scopes_set,
111 base::Bind(&SSOAccessTokenFetcher::OnAccessTokenResponse,
112 weak_factory_.GetWeakPtr()));
113 }
114
115 void SSOAccessTokenFetcher::CancelRequest() { request_was_cancelled_ = true; }
116
117 void SSOAccessTokenFetcher::OnAccessTokenResponse(NSString* token,
118 NSDate* expiration,
119 NSError* error) {
120 if (request_was_cancelled_) {
121 // Ignore the callback if the request was cancelled.
122 return;
123 }
124 GoogleServiceAuthError auth_error =
125 GetGoogleServiceAuthErrorFromNSError(provider_, error);
126 if (auth_error.state() == GoogleServiceAuthError::NONE) {
127 base::Time expiration_date =
128 base::Time::FromDoubleT([expiration timeIntervalSince1970]);
129 FireOnGetTokenSuccess(base::SysNSStringToUTF8(token), expiration_date);
130 } else {
131 FireOnGetTokenFailure(auth_error);
132 }
133 }
134
135 } // namespace
136
137 ProfileOAuth2TokenServiceIOS::AccountInfo::AccountInfo(
138 SigninErrorController* signin_error_controller,
139 const std::string& account_id)
140 : signin_error_controller_(signin_error_controller),
141 account_id_(account_id),
142 last_auth_error_(GoogleServiceAuthError::NONE),
143 marked_for_removal_(false) {
144 DCHECK(signin_error_controller_);
145 DCHECK(!account_id_.empty());
146 signin_error_controller_->AddProvider(this);
147 }
148
149 ProfileOAuth2TokenServiceIOS::AccountInfo::~AccountInfo() {
150 signin_error_controller_->RemoveProvider(this);
151 }
152
153 void ProfileOAuth2TokenServiceIOS::AccountInfo::SetLastAuthError(
154 const GoogleServiceAuthError& error) {
155 if (error.state() != last_auth_error_.state()) {
156 last_auth_error_ = error;
157 signin_error_controller_->AuthStatusChanged();
158 }
159 }
160
161 std::string ProfileOAuth2TokenServiceIOS::AccountInfo::GetAccountId() const {
162 return account_id_;
163 }
164
165 GoogleServiceAuthError
166 ProfileOAuth2TokenServiceIOS::AccountInfo::GetAuthStatus() const {
167 return last_auth_error_;
168 }
169
170 ProfileOAuth2TokenServiceIOS::ProfileOAuth2TokenServiceIOS()
171 : ProfileOAuth2TokenService() {
172 DCHECK(thread_checker_.CalledOnValidThread());
173 }
174
175 ProfileOAuth2TokenServiceIOS::~ProfileOAuth2TokenServiceIOS() {
176 DCHECK(thread_checker_.CalledOnValidThread());
177 }
178
179 void ProfileOAuth2TokenServiceIOS::Initialize(
180 SigninClient* client, SigninErrorController* signin_error_controller) {
181 DCHECK(thread_checker_.CalledOnValidThread());
182 ProfileOAuth2TokenService::Initialize(client, signin_error_controller);
183 }
184
185 void ProfileOAuth2TokenServiceIOS::Shutdown() {
186 DCHECK(thread_checker_.CalledOnValidThread());
187 CancelAllRequests();
188 accounts_.clear();
189 ProfileOAuth2TokenService::Shutdown();
190 }
191
192 ios::ProfileOAuth2TokenServiceIOSProvider*
193 ProfileOAuth2TokenServiceIOS::GetProvider() {
194 ios::ProfileOAuth2TokenServiceIOSProvider* provider =
195 client()->GetIOSProvider();
196 DCHECK(provider);
197 return provider;
198 }
199
200 void ProfileOAuth2TokenServiceIOS::LoadCredentials(
201 const std::string& primary_account_id) {
202 DCHECK(thread_checker_.CalledOnValidThread());
203
204 // LoadCredentials() is called iff the user is signed in to Chrome, so the
205 // primary account id must not be empty.
206 DCHECK(!primary_account_id.empty());
207
208 GetProvider()->InitializeSharedAuthentication();
209 ReloadCredentials(primary_account_id);
210 FireRefreshTokensLoaded();
211 }
212
213 void ProfileOAuth2TokenServiceIOS::ReloadCredentials(
214 const std::string& primary_account_id) {
215 DCHECK(!primary_account_id.empty());
216 DCHECK(primary_account_id_.empty() ||
217 primary_account_id_ == primary_account_id);
218 primary_account_id_ = primary_account_id;
219 ReloadCredentials();
220 }
221
222 void ProfileOAuth2TokenServiceIOS::ReloadCredentials() {
223 DCHECK(thread_checker_.CalledOnValidThread());
224 if (primary_account_id_.empty()) {
225 // Avoid loading the credentials if there is no primary account id.
226 return;
227 }
228
229 std::vector<std::string> new_accounts(GetProvider()->GetAllAccountIds());
230 if (GetExcludeAllSecondaryAccounts()) {
231 // Only keep the |primary_account_id| in the list of new accounts.
232 if (std::find(new_accounts.begin(),
233 new_accounts.end(),
234 primary_account_id_) != new_accounts.end()) {
235 new_accounts.clear();
236 new_accounts.push_back(primary_account_id_);
237 }
238 } else {
239 std::set<std::string> exclude_secondary_accounts =
240 GetExcludedSecondaryAccounts();
241 DCHECK(std::find(exclude_secondary_accounts.begin(),
242 exclude_secondary_accounts.end(),
243 primary_account_id_) == exclude_secondary_accounts.end());
244 for (const auto& excluded_account_id : exclude_secondary_accounts) {
245 DCHECK(!excluded_account_id.empty());
246 auto account_id_to_remove_position = std::remove(
247 new_accounts.begin(), new_accounts.end(), excluded_account_id);
248 new_accounts.erase(account_id_to_remove_position, new_accounts.end());
249 }
250 }
251
252 std::vector<std::string> old_accounts(GetAccounts());
253 std::sort(new_accounts.begin(), new_accounts.end());
254 std::sort(old_accounts.begin(), old_accounts.end());
255 if (new_accounts == old_accounts) {
256 // Avoid starting a batch change if there are no changes in the list of
257 // account.
258 return;
259 }
260
261 // Remove all old accounts that do not appear in |new_accounts| and then
262 // load |new_accounts|.
263 ScopedBatchChange batch(this);
264 for (auto i = old_accounts.begin(); i != old_accounts.end(); ++i) {
265 if (std::find(new_accounts.begin(), new_accounts.end(), *i) ==
266 new_accounts.end()) {
267 RemoveAccount(*i);
268 }
269 }
270
271 // Load all new_accounts.
272 for (auto i = new_accounts.begin(); i != new_accounts.end(); ++i) {
273 AddOrUpdateAccount(*i);
274 }
275 }
276
277 void ProfileOAuth2TokenServiceIOS::UpdateCredentials(
278 const std::string& account_id,
279 const std::string& refresh_token) {
280 DCHECK(thread_checker_.CalledOnValidThread());
281 NOTREACHED() << "Unexpected call to UpdateCredentials when using shared "
282 "authentication.";
283 }
284
285 void ProfileOAuth2TokenServiceIOS::RevokeAllCredentials() {
286 DCHECK(thread_checker_.CalledOnValidThread());
287
288 ScopedBatchChange batch(this);
289 AccountInfoMap toRemove = accounts_;
290 for (AccountInfoMap::iterator i = toRemove.begin(); i != toRemove.end(); ++i)
291 RemoveAccount(i->first);
292
293 DCHECK_EQ(0u, accounts_.size());
294 primary_account_id_.clear();
295 ClearExcludedSecondaryAccounts();
296 // |RemoveAccount| should have cancelled all the requests and cleared the
297 // cache, account-by-account. This extra-cleaning should do nothing unless
298 // something went wrong and some cache values and/or pending requests were not
299 // linked to any valid account.
300 CancelAllRequests();
301 ClearCache();
302 }
303
304 OAuth2AccessTokenFetcher*
305 ProfileOAuth2TokenServiceIOS::CreateAccessTokenFetcher(
306 const std::string& account_id,
307 net::URLRequestContextGetter* getter,
308 OAuth2AccessTokenConsumer* consumer) {
309 return new SSOAccessTokenFetcher(consumer, GetProvider(), account_id);
310 }
311
312 void ProfileOAuth2TokenServiceIOS::InvalidateOAuth2Token(
313 const std::string& account_id,
314 const std::string& client_id,
315 const ScopeSet& scopes,
316 const std::string& access_token) {
317 DCHECK(thread_checker_.CalledOnValidThread());
318
319 // Call |ProfileOAuth2TokenService::InvalidateOAuth2Token| to clear the
320 // cached access token.
321 ProfileOAuth2TokenService::InvalidateOAuth2Token(account_id,
322 client_id,
323 scopes,
324 access_token);
325
326 // There is no need to inform the authentication library that the access
327 // token is invalid as it never caches the token.
328 }
329
330 std::vector<std::string> ProfileOAuth2TokenServiceIOS::GetAccounts() {
331 DCHECK(thread_checker_.CalledOnValidThread());
332 std::vector<std::string> account_ids;
333 for (auto i = accounts_.begin(); i != accounts_.end(); ++i)
334 account_ids.push_back(i->first);
335 return account_ids;
336 }
337
338 bool ProfileOAuth2TokenServiceIOS::RefreshTokenIsAvailable(
339 const std::string& account_id) const {
340 DCHECK(thread_checker_.CalledOnValidThread());
341
342 AccountInfoMap::const_iterator iter = accounts_.find(account_id);
343 return iter != accounts_.end() && !iter->second->marked_for_removal();
344 }
345
346 void ProfileOAuth2TokenServiceIOS::UpdateAuthError(
347 const std::string& account_id,
348 const GoogleServiceAuthError& error) {
349 DCHECK(thread_checker_.CalledOnValidThread());
350
351 // Do not report connection errors as these are not actually auth errors.
352 // We also want to avoid masking a "real" auth error just because we
353 // subsequently get a transient network error.
354 if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED ||
355 error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE) {
356 return;
357 }
358
359 if (accounts_.count(account_id) == 0) {
360 NOTREACHED();
361 return;
362 }
363 accounts_[account_id]->SetLastAuthError(error);
364 }
365
366 // Clear the authentication error state and notify all observers that a new
367 // refresh token is available so that they request new access tokens.
368 void ProfileOAuth2TokenServiceIOS::AddOrUpdateAccount(
369 const std::string& account_id) {
370 DCHECK(thread_checker_.CalledOnValidThread());
371 DCHECK(!account_id.empty());
372
373 bool account_present = accounts_.count(account_id) > 0;
374 if (account_present && accounts_[account_id]->GetAuthStatus().state() ==
375 GoogleServiceAuthError::NONE) {
376 // No need to update the account if it is already a known account and if
377 // there is no auth error.
378 return;
379 }
380
381 if (account_present) {
382 CancelRequestsForAccount(account_id);
383 ClearCacheForAccount(account_id);
384 } else {
385 accounts_[account_id].reset(
386 new AccountInfo(signin_error_controller(), account_id));
387 }
388 UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
389 FireRefreshTokenAvailable(account_id);
390 }
391
392 void ProfileOAuth2TokenServiceIOS::RemoveAccount(
393 const std::string& account_id) {
394 DCHECK(thread_checker_.CalledOnValidThread());
395 DCHECK(!account_id.empty());
396
397 if (accounts_.count(account_id) > 0) {
398 // This is needed to ensure that refresh token for |acccount_id| is not
399 // available while the account is removed. Thus all access token requests
400 // for |account_id| triggered while an account is being removed will get a
401 // user not signed up error response.
402 accounts_[account_id]->set_marked_for_removal(true);
403 CancelRequestsForAccount(account_id);
404 ClearCacheForAccount(account_id);
405 accounts_.erase(account_id);
406 FireRefreshTokenRevoked(account_id);
407 }
408 }
409
410 std::set<std::string>
411 ProfileOAuth2TokenServiceIOS::GetExcludedSecondaryAccounts() {
412 const base::ListValue* excluded_secondary_accounts_pref =
413 client()->GetPrefs()->GetList(
414 prefs::kTokenServiceExcludedSecondaryAccounts);
415 std::set<std::string> excluded_secondary_accounts;
416 for (base::Value* pref_value : *excluded_secondary_accounts_pref) {
417 std::string value;
418 if (pref_value->GetAsString(&value))
419 excluded_secondary_accounts.insert(value);
420 }
421 return excluded_secondary_accounts;
422 }
423
424 void ProfileOAuth2TokenServiceIOS::ExcludeSecondaryAccounts(
425 const std::vector<std::string>& account_ids) {
426 for (const auto& account_id : account_ids)
427 ExcludeSecondaryAccount(account_id);
428 }
429
430 void ProfileOAuth2TokenServiceIOS::ExcludeSecondaryAccount(
431 const std::string& account_id) {
432 if (GetExcludeAllSecondaryAccounts()) {
433 // Avoid excluding individual secondary accounts when all secondary
434 // accounts are excluded.
435 return;
436 }
437
438 DCHECK(!account_id.empty());
439 ListPrefUpdate update(client()->GetPrefs(),
440 prefs::kTokenServiceExcludedSecondaryAccounts);
441 base::ListValue* excluded_secondary_accounts = update.Get();
442 for (base::Value* pref_value : *excluded_secondary_accounts) {
443 std::string value_at_it;
444 if (pref_value->GetAsString(&value_at_it) && (value_at_it == account_id)) {
445 // |account_id| is already excluded.
446 return;
447 }
448 }
449 excluded_secondary_accounts->AppendString(account_id);
450 }
451
452 void ProfileOAuth2TokenServiceIOS::IncludeSecondaryAccount(
453 const std::string& account_id) {
454 if (GetExcludeAllSecondaryAccounts()) {
455 // Avoid including individual secondary accounts when all secondary
456 // accounts are excluded.
457 return;
458 }
459
460 DCHECK_NE(account_id, primary_account_id_);
461 DCHECK(!primary_account_id_.empty());
462
463 // Excluded secondary account ids is a logical set (not a list) of accounts.
464 // As the value stored in the excluded account ids preference is a list,
465 // the code below removes all occurences of |account_id| from this list. This
466 // ensures that |account_id| is actually included even in cases when the
467 // preference value was corrupted (see bug http://crbug.com/453470 as
468 // example).
469 ListPrefUpdate update(client()->GetPrefs(),
470 prefs::kTokenServiceExcludedSecondaryAccounts);
471 base::ListValue* excluded_secondary_accounts = update.Get();
472 base::ListValue::iterator it = excluded_secondary_accounts->begin();
473 while (it != excluded_secondary_accounts->end()) {
474 base::Value* pref_value = *it;
475 std::string value_at_it;
476 if (pref_value->GetAsString(&value_at_it) && (value_at_it == account_id)) {
477 it = excluded_secondary_accounts->Erase(it, nullptr);
478 continue;
479 }
480 ++it;
481 }
482 }
483
484 bool ProfileOAuth2TokenServiceIOS::GetExcludeAllSecondaryAccounts() {
485 return client()->GetPrefs()->GetBoolean(
486 prefs::kTokenServiceExcludeAllSecondaryAccounts);
487 }
488
489 void ProfileOAuth2TokenServiceIOS::ExcludeAllSecondaryAccounts() {
490 client()->GetPrefs()->SetBoolean(
491 prefs::kTokenServiceExcludeAllSecondaryAccounts, true);
492 }
493
494 void ProfileOAuth2TokenServiceIOS::ClearExcludedSecondaryAccounts() {
495 client()->GetPrefs()->ClearPref(
496 prefs::kTokenServiceExcludeAllSecondaryAccounts);
497 client()->GetPrefs()->ClearPref(
498 prefs::kTokenServiceExcludedSecondaryAccounts);
499 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698