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.
5 #include "chrome/browser/signin/oauth2_token_service_delegate_android.h"
7 #include "base/android/jni_android.h"
8 #include "base/android/jni_array.h"
9 #include "base/android/jni_string.h"
10 #include "base/bind.h"
11 #include "base/logging.h"
12 #include "chrome/browser/profiles/profile_android.h"
13 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
14 #include "chrome/browser/sync/profile_sync_service_android.h"
15 #include "components/signin/core/browser/account_info.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "google_apis/gaia/gaia_auth_util.h"
18 #include "google_apis/gaia/oauth2_access_token_fetcher.h"
19 #include "jni/OAuth2TokenService_jni.h"
21 using base::android::AttachCurrentThread
;
22 using base::android::ConvertJavaStringToUTF8
;
23 using base::android::ConvertUTF8ToJavaString
;
24 using base::android::ScopedJavaLocalRef
;
25 using content::BrowserThread
;
29 // Callback from FetchOAuth2TokenWithUsername().
31 // - the error, or NONE if the token fetch was successful.
32 // - the OAuth2 access token.
33 // - the expiry time of the token (may be null, indicating that the expiry
35 typedef base::Callback
<void(const GoogleServiceAuthError
&,
37 const base::Time
&)> FetchOAuth2TokenCallback
;
39 class AndroidAccessTokenFetcher
: public OAuth2AccessTokenFetcher
{
41 AndroidAccessTokenFetcher(OAuth2AccessTokenConsumer
* consumer
,
42 const std::string
& account_id
);
43 ~AndroidAccessTokenFetcher() override
;
45 // Overrides from OAuth2AccessTokenFetcher:
46 void Start(const std::string
& client_id
,
47 const std::string
& client_secret
,
48 const std::vector
<std::string
>& scopes
) override
;
49 void CancelRequest() override
;
51 // Handles an access token response.
52 void OnAccessTokenResponse(const GoogleServiceAuthError
& error
,
53 const std::string
& access_token
,
54 const base::Time
& expiration_time
);
57 std::string
CombineScopes(const std::vector
<std::string
>& scopes
);
59 std::string account_id_
;
60 bool request_was_cancelled_
;
61 base::WeakPtrFactory
<AndroidAccessTokenFetcher
> weak_factory_
;
63 DISALLOW_COPY_AND_ASSIGN(AndroidAccessTokenFetcher
);
66 AndroidAccessTokenFetcher::AndroidAccessTokenFetcher(
67 OAuth2AccessTokenConsumer
* consumer
,
68 const std::string
& account_id
)
69 : OAuth2AccessTokenFetcher(consumer
),
70 account_id_(account_id
),
71 request_was_cancelled_(false),
75 AndroidAccessTokenFetcher::~AndroidAccessTokenFetcher() {
78 void AndroidAccessTokenFetcher::Start(const std::string
& client_id
,
79 const std::string
& client_secret
,
80 const std::vector
<std::string
>& scopes
) {
81 JNIEnv
* env
= AttachCurrentThread();
82 std::string scope
= CombineScopes(scopes
);
83 ScopedJavaLocalRef
<jstring
> j_username
=
84 ConvertUTF8ToJavaString(env
, account_id_
);
85 ScopedJavaLocalRef
<jstring
> j_scope
= ConvertUTF8ToJavaString(env
, scope
);
86 scoped_ptr
<FetchOAuth2TokenCallback
> heap_callback(
87 new FetchOAuth2TokenCallback(
88 base::Bind(&AndroidAccessTokenFetcher::OnAccessTokenResponse
,
89 weak_factory_
.GetWeakPtr())));
91 // Call into Java to get a new token.
92 Java_OAuth2TokenService_getOAuth2AuthToken(
93 env
, base::android::GetApplicationContext(), j_username
.obj(),
94 j_scope
.obj(), reinterpret_cast<intptr_t>(heap_callback
.release()));
97 void AndroidAccessTokenFetcher::CancelRequest() {
98 request_was_cancelled_
= true;
101 void AndroidAccessTokenFetcher::OnAccessTokenResponse(
102 const GoogleServiceAuthError
& error
,
103 const std::string
& access_token
,
104 const base::Time
& expiration_time
) {
105 if (request_was_cancelled_
) {
106 // Ignore the callback if the request was cancelled.
109 if (error
.state() == GoogleServiceAuthError::NONE
) {
110 FireOnGetTokenSuccess(access_token
, expiration_time
);
112 FireOnGetTokenFailure(error
);
117 std::string
AndroidAccessTokenFetcher::CombineScopes(
118 const std::vector
<std::string
>& scopes
) {
119 // The Android AccountManager supports multiple scopes separated by a space:
120 // https://code.google.com/p/google-api-java-client/wiki/OAuth2#Android
122 for (std::vector
<std::string
>::const_iterator it
= scopes
.begin();
123 it
!= scopes
.end(); ++it
) {
133 bool OAuth2TokenServiceDelegateAndroid::is_testing_profile_
= false;
135 OAuth2TokenServiceDelegateAndroid::ErrorInfo::ErrorInfo()
136 : error(GoogleServiceAuthError::NONE
) {}
138 OAuth2TokenServiceDelegateAndroid::ErrorInfo::ErrorInfo(
139 const GoogleServiceAuthError
& error
)
142 OAuth2TokenServiceDelegateAndroid::OAuth2TokenServiceDelegateAndroid(
143 AccountTrackerService
* account_tracker_service
)
144 : account_tracker_service_(account_tracker_service
),
145 fire_refresh_token_loaded_(RT_LOAD_NOT_START
) {
146 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ctor";
147 DCHECK(account_tracker_service_
);
148 JNIEnv
* env
= AttachCurrentThread();
149 base::android::ScopedJavaLocalRef
<jobject
> local_java_ref
=
150 Java_OAuth2TokenService_create(env
,
151 base::android::GetApplicationContext(),
152 reinterpret_cast<intptr_t>(this));
153 java_ref_
.Reset(env
, local_java_ref
.obj());
155 if (account_tracker_service_
->GetMigrationState() ==
156 AccountTrackerService::MIGRATION_IN_PROGRESS
) {
157 std::vector
<std::string
> accounts
= GetAccounts();
158 std::vector
<std::string
> accounts_id
;
159 for (auto account_name
: accounts
) {
160 AccountInfo account_info
=
161 account_tracker_service_
->FindAccountInfoByEmail(account_name
);
162 DCHECK(!account_info
.gaia
.empty());
163 accounts_id
.push_back(account_info
.gaia
);
165 ScopedJavaLocalRef
<jobjectArray
> java_accounts(
166 base::android::ToJavaArrayOfStrings(env
, accounts_id
));
167 Java_OAuth2TokenService_saveStoredAccounts(
168 env
, base::android::GetApplicationContext(), java_accounts
.obj());
171 if (!is_testing_profile_
) {
172 Java_OAuth2TokenService_validateAccounts(
173 AttachCurrentThread(), java_ref_
.obj(),
174 base::android::GetApplicationContext(), JNI_TRUE
);
178 OAuth2TokenServiceDelegateAndroid::~OAuth2TokenServiceDelegateAndroid() {
182 ScopedJavaLocalRef
<jobject
> OAuth2TokenServiceDelegateAndroid::GetForProfile(
185 jobject j_profile_android
) {
186 Profile
* profile
= ProfileAndroid::FromProfileAndroid(j_profile_android
);
187 ProfileOAuth2TokenService
* service
=
188 ProfileOAuth2TokenServiceFactory::GetForProfile(profile
);
189 OAuth2TokenServiceDelegate
* delegate
= service
->GetDelegate();
190 return ScopedJavaLocalRef
<jobject
>(
191 static_cast<OAuth2TokenServiceDelegateAndroid
*>(delegate
)->java_ref_
);
194 static ScopedJavaLocalRef
<jobject
> GetForProfile(
196 const JavaParamRef
<jclass
>& clazz
,
197 const JavaParamRef
<jobject
>& j_profile_android
) {
198 return OAuth2TokenServiceDelegateAndroid::GetForProfile(env
, clazz
,
202 bool OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable(
203 const std::string
& account_id
) const {
204 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable"
205 << " account= " << account_id
;
206 std::string account_name
= MapAccountIdToAccountName(account_id
);
207 JNIEnv
* env
= AttachCurrentThread();
208 ScopedJavaLocalRef
<jstring
> j_account_id
=
209 ConvertUTF8ToJavaString(env
, account_name
);
210 jboolean refresh_token_is_available
=
211 Java_OAuth2TokenService_hasOAuth2RefreshToken(
212 env
, base::android::GetApplicationContext(), j_account_id
.obj());
213 return refresh_token_is_available
== JNI_TRUE
;
216 bool OAuth2TokenServiceDelegateAndroid::RefreshTokenHasError(
217 const std::string
& account_id
) const {
218 auto it
= errors_
.find(account_id
);
219 // TODO(rogerta): should we distinguish between transient and persistent?
220 return it
== errors_
.end() ? false : IsError(it
->second
.error
);
223 void OAuth2TokenServiceDelegateAndroid::UpdateAuthError(
224 const std::string
& account_id
,
225 const GoogleServiceAuthError
& error
) {
226 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::UpdateAuthError"
227 << " account=" << account_id
228 << " error=" << error
.ToString();
229 if (error
.state() == GoogleServiceAuthError::NONE
) {
230 errors_
.erase(account_id
);
232 // TODO(rogerta): should we distinguish between transient and persistent?
233 errors_
[account_id
] = ErrorInfo(error
);
237 std::vector
<std::string
> OAuth2TokenServiceDelegateAndroid::GetAccounts() {
238 std::vector
<std::string
> accounts
;
239 JNIEnv
* env
= AttachCurrentThread();
240 ScopedJavaLocalRef
<jobjectArray
> j_accounts
=
241 Java_OAuth2TokenService_getAccounts(
242 env
, base::android::GetApplicationContext());
243 // TODO(fgorski): We may decide to filter out some of the accounts.
244 base::android::AppendJavaStringArrayToStringVector(env
, j_accounts
.obj(),
249 std::vector
<std::string
>
250 OAuth2TokenServiceDelegateAndroid::GetSystemAccountNames() {
251 std::vector
<std::string
> account_names
;
252 JNIEnv
* env
= AttachCurrentThread();
253 ScopedJavaLocalRef
<jobjectArray
> j_accounts
=
254 Java_OAuth2TokenService_getSystemAccountNames(
255 env
, base::android::GetApplicationContext());
256 base::android::AppendJavaStringArrayToStringVector(env
, j_accounts
.obj(),
258 return account_names
;
261 OAuth2AccessTokenFetcher
*
262 OAuth2TokenServiceDelegateAndroid::CreateAccessTokenFetcher(
263 const std::string
& account_id
,
264 net::URLRequestContextGetter
* getter
,
265 OAuth2AccessTokenConsumer
* consumer
) {
266 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::CreateAccessTokenFetcher"
267 << " account= " << account_id
;
268 ValidateAccountId(account_id
);
269 return new AndroidAccessTokenFetcher(consumer
,
270 MapAccountIdToAccountName(account_id
));
273 void OAuth2TokenServiceDelegateAndroid::InvalidateAccessToken(
274 const std::string
& account_id
,
275 const std::string
& client_id
,
276 const OAuth2TokenService::ScopeSet
& scopes
,
277 const std::string
& access_token
) {
278 ValidateAccountId(account_id
);
279 JNIEnv
* env
= AttachCurrentThread();
280 ScopedJavaLocalRef
<jstring
> j_access_token
=
281 ConvertUTF8ToJavaString(env
, access_token
);
282 Java_OAuth2TokenService_invalidateOAuth2AuthToken(
283 env
, base::android::GetApplicationContext(), j_access_token
.obj());
286 void OAuth2TokenServiceDelegateAndroid::ValidateAccounts(
289 jstring j_current_acc
,
290 jboolean j_force_notifications
) {
291 std::string signed_in_account
;
292 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts from java";
294 signed_in_account
= ConvertJavaStringToUTF8(env
, j_current_acc
);
295 if (!signed_in_account
.empty())
296 signed_in_account
= gaia::CanonicalizeEmail(signed_in_account
);
298 // Clear any auth errors so that client can retry to get access tokens.
301 ValidateAccounts(MapAccountNameToAccountId(signed_in_account
),
302 j_force_notifications
!= JNI_FALSE
);
305 void OAuth2TokenServiceDelegateAndroid::ValidateAccounts(
306 const std::string
& signed_in_account
,
307 bool force_notifications
) {
308 std::vector
<std::string
> prev_ids
= GetAccounts();
309 std::vector
<std::string
> curr_ids
= GetSystemAccountNames();
310 std::vector
<std::string
> refreshed_ids
;
311 std::vector
<std::string
> revoked_ids
;
312 bool account_validation_result
= true;
314 for (size_t i
= 0; i
< curr_ids
.size(); ++i
)
315 curr_ids
[i
] = MapAccountNameToAccountId(curr_ids
[i
]);
317 for (size_t i
= 0; i
< prev_ids
.size(); ++i
)
318 ValidateAccountId(prev_ids
[i
]);
320 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
321 << " sigined_in_account=" << signed_in_account
322 << " prev_ids=" << prev_ids
.size() << " curr_ids=" << curr_ids
.size()
323 << " force=" << (force_notifications
? "true" : "false");
325 account_validation_result
=
326 ValidateAccounts(signed_in_account
, prev_ids
, curr_ids
, refreshed_ids
,
327 revoked_ids
, force_notifications
);
329 ScopedBatchChange
batch(this);
331 JNIEnv
* env
= AttachCurrentThread();
332 ScopedJavaLocalRef
<jobjectArray
> java_accounts
;
333 if (account_validation_result
) {
334 java_accounts
= base::android::ToJavaArrayOfStrings(env
, curr_ids
);
337 base::android::ToJavaArrayOfStrings(env
, std::vector
<std::string
>());
339 Java_OAuth2TokenService_saveStoredAccounts(
340 env
, base::android::GetApplicationContext(), java_accounts
.obj());
342 for (std::vector
<std::string
>::iterator it
= refreshed_ids
.begin();
343 it
!= refreshed_ids
.end(); it
++) {
344 FireRefreshTokenAvailable(*it
);
347 for (std::vector
<std::string
>::iterator it
= revoked_ids
.begin();
348 it
!= revoked_ids
.end(); it
++) {
349 FireRefreshTokenRevoked(*it
);
352 if (fire_refresh_token_loaded_
== RT_WAIT_FOR_VALIDATION
) {
353 fire_refresh_token_loaded_
= RT_LOADED
;
354 FireRefreshTokensLoaded();
355 } else if (fire_refresh_token_loaded_
== RT_LOAD_NOT_START
) {
356 fire_refresh_token_loaded_
= RT_HAS_BEEN_VALIDATED
;
359 // Clear accounts no longer exist on device from AccountTrackerService.
360 std::vector
<AccountInfo
> accounts_info
=
361 account_tracker_service_
->GetAccounts();
362 for (auto info
: accounts_info
) {
363 auto it
= curr_ids
.begin();
364 for (; it
!= curr_ids
.end(); ++it
) {
365 if (*it
== info
.account_id
)
368 if (it
== curr_ids
.end())
369 account_tracker_service_
->RemoveAccount(info
.account_id
);
373 bool OAuth2TokenServiceDelegateAndroid::ValidateAccounts(
374 const std::string
& signed_in_account
,
375 const std::vector
<std::string
>& prev_account_ids
,
376 const std::vector
<std::string
>& curr_account_ids
,
377 std::vector
<std::string
>& refreshed_ids
,
378 std::vector
<std::string
>& revoked_ids
,
379 bool force_notifications
) {
380 if (std::find(curr_account_ids
.begin(), curr_account_ids
.end(),
381 signed_in_account
) != curr_account_ids
.end()) {
382 // Test to see if an account is removed from the Android AccountManager.
383 // If so, invoke FireRefreshTokenRevoked to notify the reconcilor.
384 for (std::vector
<std::string
>::const_iterator it
= prev_account_ids
.begin();
385 it
!= prev_account_ids
.end(); it
++) {
386 if (*it
== signed_in_account
)
389 if (std::find(curr_account_ids
.begin(), curr_account_ids
.end(), *it
) ==
390 curr_account_ids
.end()) {
391 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
392 << "revoked=" << *it
;
393 revoked_ids
.push_back(*it
);
397 if (force_notifications
||
398 std::find(prev_account_ids
.begin(), prev_account_ids
.end(),
399 signed_in_account
) == prev_account_ids
.end()) {
400 // Always fire the primary signed in account first.
401 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
402 << "refreshed=" << signed_in_account
;
403 refreshed_ids
.push_back(signed_in_account
);
406 for (std::vector
<std::string
>::const_iterator it
= curr_account_ids
.begin();
407 it
!= curr_account_ids
.end(); it
++) {
408 if (*it
!= signed_in_account
) {
409 if (force_notifications
||
410 std::find(prev_account_ids
.begin(), prev_account_ids
.end(), *it
) ==
411 prev_account_ids
.end()) {
412 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
413 << "refreshed=" << *it
;
414 refreshed_ids
.push_back(*it
);
420 // Currently signed in account does not any longer exist among accounts on
421 // system together with all other accounts.
422 if (std::find(prev_account_ids
.begin(), prev_account_ids
.end(),
423 signed_in_account
) != prev_account_ids
.end()) {
424 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
425 << "revoked=" << signed_in_account
;
426 revoked_ids
.push_back(signed_in_account
);
428 for (std::vector
<std::string
>::const_iterator it
= prev_account_ids
.begin();
429 it
!= prev_account_ids
.end(); it
++) {
430 if (*it
== signed_in_account
)
432 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
433 << "revoked=" << *it
;
434 revoked_ids
.push_back(*it
);
440 void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenAvailableFromJava(
443 const jstring account_name
) {
444 std::string account_id
=
445 MapAccountNameToAccountId(ConvertJavaStringToUTF8(env
, account_name
));
446 // Notify native observers.
447 FireRefreshTokenAvailable(account_id
);
450 void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenAvailable(
451 const std::string
& account_id
) {
452 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::FireRefreshTokenAvailable id="
454 JNIEnv
* env
= AttachCurrentThread();
455 ScopedJavaLocalRef
<jstring
> account_name
=
456 ConvertUTF8ToJavaString(env
, MapAccountIdToAccountName(account_id
));
457 Java_OAuth2TokenService_notifyRefreshTokenAvailable(env
, java_ref_
.obj(),
459 OAuth2TokenServiceDelegate::FireRefreshTokenAvailable(account_id
);
462 void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenRevokedFromJava(
465 const jstring account_name
) {
466 std::string account_id
=
467 MapAccountNameToAccountId(ConvertJavaStringToUTF8(env
, account_name
));
468 // Notify native observers.
469 FireRefreshTokenRevoked(account_id
);
472 void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenRevoked(
473 const std::string
& account_id
) {
474 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::FireRefreshTokenRevoked id="
476 JNIEnv
* env
= AttachCurrentThread();
477 ScopedJavaLocalRef
<jstring
> account_name
=
478 ConvertUTF8ToJavaString(env
, MapAccountIdToAccountName(account_id
));
479 Java_OAuth2TokenService_notifyRefreshTokenRevoked(env
, java_ref_
.obj(),
481 OAuth2TokenServiceDelegate::FireRefreshTokenRevoked(account_id
);
484 void OAuth2TokenServiceDelegateAndroid::FireRefreshTokensLoadedFromJava(
487 // Notify native observers.
488 FireRefreshTokensLoaded();
491 void OAuth2TokenServiceDelegateAndroid::FireRefreshTokensLoaded() {
492 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::FireRefreshTokensLoaded";
493 JNIEnv
* env
= AttachCurrentThread();
494 Java_OAuth2TokenService_notifyRefreshTokensLoaded(env
, java_ref_
.obj());
495 OAuth2TokenServiceDelegate::FireRefreshTokensLoaded();
498 void OAuth2TokenServiceDelegateAndroid::RevokeAllCredentials() {
499 DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RevokeAllCredentials";
500 ScopedBatchChange
batch(this);
501 std::vector
<std::string
> accounts
= GetAccounts();
502 for (std::vector
<std::string
>::iterator it
= accounts
.begin();
503 it
!= accounts
.end(); it
++) {
504 FireRefreshTokenRevoked(*it
);
507 // Clear everything on the Java side as well.
508 std::vector
<std::string
> empty
;
509 JNIEnv
* env
= AttachCurrentThread();
510 ScopedJavaLocalRef
<jobjectArray
> java_accounts(
511 base::android::ToJavaArrayOfStrings(env
, empty
));
512 Java_OAuth2TokenService_saveStoredAccounts(
513 env
, base::android::GetApplicationContext(), java_accounts
.obj());
516 void OAuth2TokenServiceDelegateAndroid::LoadCredentials(
517 const std::string
& primary_account_id
) {
518 if (fire_refresh_token_loaded_
== RT_HAS_BEEN_VALIDATED
) {
519 fire_refresh_token_loaded_
= RT_LOADED
;
520 FireRefreshTokensLoaded();
521 } else if (fire_refresh_token_loaded_
== RT_LOAD_NOT_START
) {
522 fire_refresh_token_loaded_
= RT_WAIT_FOR_VALIDATION
;
526 std::string
OAuth2TokenServiceDelegateAndroid::MapAccountIdToAccountName(
527 const std::string
& account_id
) const {
528 std::string account_name
=
529 account_tracker_service_
->GetAccountInfo(account_id
).email
;
530 DCHECK(!account_name
.empty() || account_id
.empty());
534 std::string
OAuth2TokenServiceDelegateAndroid::MapAccountNameToAccountId(
535 const std::string
& account_name
) const {
536 std::string account_id
=
537 account_tracker_service_
->FindAccountInfoByEmail(account_name
).account_id
;
538 DCHECK(!account_id
.empty() || account_name
.empty());
542 // Called from Java when fetching of an OAuth2 token is finished. The
543 // |authToken| param is only valid when |result| is true.
544 void OAuth2TokenFetched(JNIEnv
* env
,
545 const JavaParamRef
<jclass
>& clazz
,
546 const JavaParamRef
<jstring
>& authToken
,
547 jboolean isTransientError
,
548 jlong nativeCallback
) {
551 token
= ConvertJavaStringToUTF8(env
, authToken
);
552 scoped_ptr
<FetchOAuth2TokenCallback
> heap_callback(
553 reinterpret_cast<FetchOAuth2TokenCallback
*>(nativeCallback
));
554 GoogleServiceAuthError
556 ? GoogleServiceAuthError::NONE
558 ? GoogleServiceAuthError::CONNECTION_FAILED
559 : GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS
);
560 heap_callback
->Run(err
, token
, base::Time());
564 bool OAuth2TokenServiceDelegateAndroid::Register(JNIEnv
* env
) {
565 return RegisterNativesImpl(env
);