OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2013 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_ser vice_ios_provider.h" | |
19 #include "net/url_request/url_request_status.h" | |
20 | |
21 namespace { | |
22 | |
23 const 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: | |
28 GoogleServiceAuthError 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 | |
62 class 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 | |
89 SSOAccessTokenFetcher::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 | |
101 SSOAccessTokenFetcher::~SSOAccessTokenFetcher() {} | |
102 | |
103 void 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 | |
113 void SSOAccessTokenFetcher::CancelRequest() { request_was_cancelled_ = true; } | |
114 | |
115 void 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. | |
134 class 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 | |
153 InvalidGrantAccessTokenFetcher::InvalidGrantAccessTokenFetcher( | |
154 OAuth2AccessTokenConsumer* consumer) | |
155 : OAuth2AccessTokenFetcher(consumer), | |
156 request_was_cancelled_(false) {} | |
157 | |
158 InvalidGrantAccessTokenFetcher::~InvalidGrantAccessTokenFetcher() {} | |
159 | |
160 void 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 | |
170 void InvalidGrantAccessTokenFetcher::FireInvalidGrant() { | |
171 if (request_was_cancelled_) | |
172 return; | |
173 GoogleServiceAuthError auth_error( | |
174 GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS); | |
175 FireOnGetTokenFailure(auth_error); | |
176 } | |
177 | |
178 void InvalidGrantAccessTokenFetcher::CancelRequest() { | |
179 request_was_cancelled_ = true; | |
180 } | |
181 | |
182 } // namespace | |
183 | |
184 ProfileOAuth2TokenServiceIOS::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 | |
195 ProfileOAuth2TokenServiceIOS::AccountInfo::~AccountInfo() { | |
196 token_service_->signin_error_controller()->RemoveProvider(this); | |
197 } | |
198 | |
199 void 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 | |
207 std::string ProfileOAuth2TokenServiceIOS::AccountInfo::GetAccountId() const { | |
208 return account_id_; | |
209 } | |
210 | |
211 GoogleServiceAuthError | |
212 ProfileOAuth2TokenServiceIOS::AccountInfo::GetAuthStatus() const { | |
213 return last_auth_error_; | |
214 } | |
215 | |
216 ProfileOAuth2TokenServiceIOS::ProfileOAuth2TokenServiceIOS() | |
217 : MutableProfileOAuth2TokenService(), | |
218 use_legacy_token_service_(false) { | |
219 DCHECK(thread_checker_.CalledOnValidThread()); | |
220 } | |
221 | |
222 ProfileOAuth2TokenServiceIOS::~ProfileOAuth2TokenServiceIOS() { | |
223 DCHECK(thread_checker_.CalledOnValidThread()); | |
224 } | |
225 | |
226 void ProfileOAuth2TokenServiceIOS::Initialize(SigninClient* client) { | |
227 DCHECK(thread_checker_.CalledOnValidThread()); | |
228 MutableProfileOAuth2TokenService::Initialize(client); | |
229 } | |
230 | |
231 void ProfileOAuth2TokenServiceIOS::Shutdown() { | |
232 DCHECK(thread_checker_.CalledOnValidThread()); | |
233 CancelAllRequests(); | |
234 accounts_.clear(); | |
235 MutableProfileOAuth2TokenService::Shutdown(); | |
236 } | |
237 | |
238 ios::ProfileOAuth2TokenServiceIOSProvider* | |
239 ProfileOAuth2TokenServiceIOS::GetProvider() { | |
240 ios::ProfileOAuth2TokenServiceIOSProvider* provider = | |
241 client()->GetIOSProvider(); | |
242 DCHECK(provider); | |
243 return provider; | |
244 } | |
245 | |
246 void ProfileOAuth2TokenServiceIOS::LoadCredentials( | |
247 const std::string& primary_account_id) { | |
248 DCHECK(thread_checker_.CalledOnValidThread()); | |
249 | |
250 // LoadCredentials() is called iff the user is signed in to Chrome, so the | |
251 // primary account id must not be empty. | |
252 DCHECK(!primary_account_id.empty()); | |
253 | |
254 use_legacy_token_service_ = !GetProvider()->IsUsingSharedAuthentication(); | |
255 if (use_legacy_token_service_) { | |
256 MutableProfileOAuth2TokenService::LoadCredentials(primary_account_id); | |
257 return; | |
258 } | |
259 | |
260 GetProvider()->InitializeSharedAuthentication(); | |
261 ReloadCredentials(); | |
262 FireRefreshTokensLoaded(); | |
263 } | |
264 | |
265 void ProfileOAuth2TokenServiceIOS::ReloadCredentials() { | |
266 DCHECK(thread_checker_.CalledOnValidThread()); | |
267 if (use_legacy_token_service_) { | |
268 NOTREACHED(); | |
269 return; | |
270 } | |
271 | |
272 // Remove all old accounts that do not appear in |new_accounts| and then | |
273 // load |new_accounts|. | |
274 std::vector<std::string> new_accounts(GetProvider()->GetAllAccountIds()); | |
275 std::vector<std::string> old_accounts(GetAccounts()); | |
276 for (auto i = old_accounts.begin(); i != old_accounts.end(); ++i) { | |
277 if (std::find(new_accounts.begin(), new_accounts.end(), *i) == | |
278 new_accounts.end()) { | |
279 RemoveAccount(*i); | |
280 } | |
281 } | |
282 | |
283 // Load all new_accounts. | |
284 for (auto i = new_accounts.begin(); i != new_accounts.end(); ++i) { | |
285 AddOrUpdateAccount(*i); | |
286 } | |
287 } | |
288 | |
289 void ProfileOAuth2TokenServiceIOS::UpdateCredentials( | |
290 const std::string& account_id, | |
291 const std::string& refresh_token) { | |
292 DCHECK(thread_checker_.CalledOnValidThread()); | |
293 if (use_legacy_token_service_) { | |
294 MutableProfileOAuth2TokenService::UpdateCredentials(account_id, | |
295 refresh_token); | |
296 return; | |
297 } | |
298 NOTREACHED() << "Unexpected call to UpdateCredentials when using shared " | |
299 "authentication."; | |
300 } | |
301 | |
302 void ProfileOAuth2TokenServiceIOS::RevokeAllCredentials() { | |
303 DCHECK(thread_checker_.CalledOnValidThread()); | |
304 if (use_legacy_token_service_) { | |
305 MutableProfileOAuth2TokenService::RevokeAllCredentials(); | |
306 return; | |
307 } | |
308 | |
309 CancelAllRequests(); | |
310 ClearCache(); | |
311 AccountInfoMap toRemove = accounts_; | |
312 for (AccountInfoMap::iterator i = toRemove.begin(); i != toRemove.end(); ++i) | |
313 RemoveAccount(i->first); | |
314 | |
315 DCHECK_EQ(0u, accounts_.size()); | |
316 } | |
317 | |
318 OAuth2AccessTokenFetcher* | |
319 ProfileOAuth2TokenServiceIOS::CreateAccessTokenFetcher( | |
320 const std::string& account_id, | |
321 net::URLRequestContextGetter* getter, | |
322 OAuth2AccessTokenConsumer* consumer) { | |
323 if (use_legacy_token_service_) { | |
324 std::string refresh_token = GetRefreshToken(account_id); | |
325 DCHECK(!refresh_token.empty()); | |
326 if (refresh_token == kForceInvalidGrantResponsesRefreshToken) { | |
327 return new InvalidGrantAccessTokenFetcher(consumer); | |
328 } else { | |
329 return MutableProfileOAuth2TokenService::CreateAccessTokenFetcher( | |
330 account_id, getter, consumer); | |
331 } | |
332 } | |
333 | |
334 return new SSOAccessTokenFetcher(consumer, GetProvider(), account_id); | |
335 } | |
336 | |
337 void ProfileOAuth2TokenServiceIOS::ForceInvalidGrantResponses() { | |
338 if (!use_legacy_token_service_) { | |
339 NOTREACHED(); | |
340 return; | |
341 } | |
342 std::vector<std::string> accounts = | |
343 MutableProfileOAuth2TokenService::GetAccounts(); | |
344 if (accounts.empty()) { | |
345 NOTREACHED(); | |
346 return; | |
347 } | |
348 | |
349 std::string first_account_id = *accounts.begin(); | |
350 if (RefreshTokenIsAvailable(first_account_id) && | |
351 GetRefreshToken(first_account_id) != | |
352 kForceInvalidGrantResponsesRefreshToken) { | |
353 MutableProfileOAuth2TokenService::RevokeAllCredentials(); | |
354 } | |
355 | |
356 for (auto i = accounts.begin(); i != accounts.end(); ++i) { | |
357 std::string account_id = *i; | |
358 MutableProfileOAuth2TokenService::UpdateCredentials( | |
359 account_id, | |
360 kForceInvalidGrantResponsesRefreshToken); | |
361 } | |
362 } | |
363 | |
364 void ProfileOAuth2TokenServiceIOS::InvalidateOAuth2Token( | |
365 const std::string& account_id, | |
366 const std::string& client_id, | |
367 const ScopeSet& scopes, | |
368 const std::string& access_token) { | |
369 DCHECK(thread_checker_.CalledOnValidThread()); | |
370 | |
371 // Call |MutableProfileOAuth2TokenService::InvalidateOAuth2Token| to clear the | |
372 // cached access token. | |
373 MutableProfileOAuth2TokenService::InvalidateOAuth2Token(account_id, | |
374 client_id, | |
375 scopes, | |
376 access_token); | |
377 | |
378 // There is no need to inform the authentication library that the access | |
379 // token is invalid as it never caches the token. | |
380 } | |
381 | |
382 std::vector<std::string> ProfileOAuth2TokenServiceIOS::GetAccounts() { | |
383 DCHECK(thread_checker_.CalledOnValidThread()); | |
384 if (use_legacy_token_service_) { | |
385 return MutableProfileOAuth2TokenService::GetAccounts(); | |
386 } | |
387 | |
388 std::vector<std::string> account_ids; | |
389 for (auto i = accounts_.begin(); i != accounts_.end(); ++i) | |
390 account_ids.push_back(i->first); | |
391 return account_ids; | |
392 } | |
393 | |
394 bool ProfileOAuth2TokenServiceIOS::RefreshTokenIsAvailable( | |
395 const std::string& account_id) const { | |
396 DCHECK(thread_checker_.CalledOnValidThread()); | |
397 | |
398 if (use_legacy_token_service_) { | |
399 return MutableProfileOAuth2TokenService::RefreshTokenIsAvailable( | |
400 account_id); | |
401 } | |
402 | |
403 return accounts_.count(account_id) > 0; | |
404 } | |
405 | |
406 std::string ProfileOAuth2TokenServiceIOS::GetRefreshToken( | |
407 const std::string& account_id) const { | |
408 DCHECK(thread_checker_.CalledOnValidThread()); | |
409 if (use_legacy_token_service_) | |
410 return MutableProfileOAuth2TokenService::GetRefreshToken(account_id); | |
411 | |
412 // On iOS, the refresh token does not exist as ProfileOAuth2TokenServiceIOS | |
413 // fetches the access token from the iOS authentication library. | |
414 NOTREACHED(); | |
415 return std::string(); | |
416 } | |
417 | |
418 std::string | |
419 ProfileOAuth2TokenServiceIOS::GetRefreshTokenWhenNotUsingSharedAuthentication( | |
420 const std::string& account_id) { | |
421 DCHECK(use_legacy_token_service_); | |
422 return GetRefreshToken(account_id); | |
423 } | |
424 | |
425 void ProfileOAuth2TokenServiceIOS::UpdateAuthError( | |
426 const std::string& account_id, | |
427 const GoogleServiceAuthError& error) { | |
428 DCHECK(thread_checker_.CalledOnValidThread()); | |
429 | |
430 if (use_legacy_token_service_) { | |
431 MutableProfileOAuth2TokenService::UpdateAuthError(account_id, error); | |
432 return; | |
433 } | |
434 | |
435 // Do not report connection errors as these are not actually auth errors. | |
436 // We also want to avoid masking a "real" auth error just because we | |
437 // subsequently get a transient network error. | |
438 if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED || | |
439 error.state() == GoogleServiceAuthError::SERVICE_UNAVAILABLE) { | |
440 return; | |
441 } | |
442 | |
443 if (accounts_.count(account_id) == 0) { | |
444 NOTREACHED(); | |
445 return; | |
446 } | |
447 accounts_[account_id]->SetLastAuthError(error); | |
448 } | |
449 | |
450 // Clear the authentication error state and notify all observers that a new | |
451 // refresh token is available so that they request new access tokens. | |
452 void ProfileOAuth2TokenServiceIOS::AddOrUpdateAccount( | |
453 const std::string& account_id) { | |
454 DCHECK(thread_checker_.CalledOnValidThread()); | |
455 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.
| |
456 | |
457 bool account_present = accounts_.count(account_id) > 0; | |
458 if (account_present && accounts_[account_id]->GetAuthStatus().state() == | |
459 GoogleServiceAuthError::NONE) { | |
460 // No need to update the account if it is already a known account and if | |
461 // there is no auth error. | |
462 return; | |
463 } | |
464 | |
465 if (account_present) { | |
466 CancelRequestsForAccount(account_id); | |
467 ClearCacheForAccount(account_id); | |
468 } else { | |
469 accounts_[account_id].reset(new AccountInfo(this, account_id)); | |
470 } | |
471 UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone()); | |
472 FireRefreshTokenAvailable(account_id); | |
473 } | |
474 | |
475 void ProfileOAuth2TokenServiceIOS::RemoveAccount( | |
476 const std::string& account_id) { | |
477 DCHECK(thread_checker_.CalledOnValidThread()); | |
478 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.
| |
479 | |
480 if (accounts_.count(account_id) > 0) { | |
481 CancelRequestsForAccount(account_id); | |
482 ClearCacheForAccount(account_id); | |
483 accounts_.erase(account_id); | |
484 FireRefreshTokenRevoked(account_id); | |
485 } | |
486 } | |
487 | |
488 void ProfileOAuth2TokenServiceIOS::StartUsingSharedAuthentication() { | |
489 if (!use_legacy_token_service_) | |
490 return; | |
491 MutableProfileOAuth2TokenService::RevokeAllCredentials(); | |
492 use_legacy_token_service_ = false; | |
493 } | |
494 | |
495 void ProfileOAuth2TokenServiceIOS::SetUseLegacyTokenServiceForTesting( | |
496 bool use_legacy_token_service) { | |
497 use_legacy_token_service_ = use_legacy_token_service; | |
498 } | |
OLD | NEW |