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 "chrome/browser/signin/android_profile_oauth2_token_service.h" 6 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 "content/public/browser/browser_thread.h" 16#include "google_apis/gaia/oauth2_access_token_fetcher.h" 17#include "jni/OAuth2TokenService_jni.h" 18 19using base::android::AttachCurrentThread; 20using base::android::ConvertJavaStringToUTF8; 21using base::android::ConvertUTF8ToJavaString; 22using base::android::ScopedJavaLocalRef; 23using content::BrowserThread; 24 25namespace { 26 27// Callback from FetchOAuth2TokenWithUsername(). 28// Arguments: 29// - the error, or NONE if the token fetch was successful. 30// - the OAuth2 access token. 31// - the expiry time of the token (may be null, indicating that the expiry 32// time is unknown. 33typedef base::Callback<void( 34 const GoogleServiceAuthError&, const std::string&, const base::Time&)> 35 FetchOAuth2TokenCallback; 36 37class AndroidAccessTokenFetcher : public OAuth2AccessTokenFetcher { 38 public: 39 AndroidAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer, 40 const std::string& account_id); 41 virtual ~AndroidAccessTokenFetcher(); 42 43 // Overrides from OAuth2AccessTokenFetcher: 44 virtual void Start(const std::string& client_id, 45 const std::string& client_secret, 46 const std::vector<std::string>& scopes) OVERRIDE; 47 virtual void CancelRequest() OVERRIDE; 48 49 // Handles an access token response. 50 void OnAccessTokenResponse(const GoogleServiceAuthError& error, 51 const std::string& access_token, 52 const base::Time& expiration_time); 53 54 private: 55 std::string CombineScopes(const std::vector<std::string>& scopes); 56 57 base::WeakPtrFactory<AndroidAccessTokenFetcher> weak_factory_; 58 std::string account_id_; 59 bool request_was_cancelled_; 60 61 DISALLOW_COPY_AND_ASSIGN(AndroidAccessTokenFetcher); 62}; 63 64AndroidAccessTokenFetcher::AndroidAccessTokenFetcher( 65 OAuth2AccessTokenConsumer* consumer, 66 const std::string& account_id) 67 : OAuth2AccessTokenFetcher(consumer), 68 weak_factory_(this), 69 account_id_(account_id), 70 request_was_cancelled_(false) { 71} 72 73AndroidAccessTokenFetcher::~AndroidAccessTokenFetcher() {} 74 75void AndroidAccessTokenFetcher::Start(const std::string& client_id, 76 const std::string& client_secret, 77 const std::vector<std::string>& scopes) { 78 JNIEnv* env = AttachCurrentThread(); 79 std::string scope = CombineScopes(scopes); 80 ScopedJavaLocalRef<jstring> j_username = 81 ConvertUTF8ToJavaString(env, account_id_); 82 ScopedJavaLocalRef<jstring> j_scope = 83 ConvertUTF8ToJavaString(env, scope); 84 scoped_ptr<FetchOAuth2TokenCallback> heap_callback( 85 new FetchOAuth2TokenCallback( 86 base::Bind(&AndroidAccessTokenFetcher::OnAccessTokenResponse, 87 weak_factory_.GetWeakPtr()))); 88 89 // Call into Java to get a new token. 90 Java_OAuth2TokenService_getOAuth2AuthToken( 91 env, base::android::GetApplicationContext(), 92 j_username.obj(), 93 j_scope.obj(), 94 reinterpret_cast<intptr_t>(heap_callback.release())); 95} 96 97void AndroidAccessTokenFetcher::CancelRequest() { 98 request_was_cancelled_ = true; 99} 100 101void 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. 107 return; 108 } 109 if (error.state() == GoogleServiceAuthError::NONE) { 110 FireOnGetTokenSuccess(access_token, expiration_time); 111 } else { 112 FireOnGetTokenFailure(error); 113 } 114} 115 116// static 117std::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 121 std::string scope; 122 for (std::vector<std::string>::const_iterator it = scopes.begin(); 123 it != scopes.end(); ++it) { 124 if (!scope.empty()) 125 scope += " "; 126 scope += *it; 127 } 128 return scope; 129} 130 131} // namespace 132 133bool AndroidProfileOAuth2TokenService::is_testing_profile_ = false; 134 135AndroidProfileOAuth2TokenService::AndroidProfileOAuth2TokenService() { 136 VLOG(1) << "AndroidProfileOAuth2TokenService::ctor"; 137 JNIEnv* env = AttachCurrentThread(); 138 base::android::ScopedJavaLocalRef<jobject> local_java_ref = 139 Java_OAuth2TokenService_create(env, reinterpret_cast<intptr_t>(this)); 140 java_ref_.Reset(env, local_java_ref.obj()); 141} 142 143AndroidProfileOAuth2TokenService::~AndroidProfileOAuth2TokenService() {} 144 145// static 146jobject AndroidProfileOAuth2TokenService::GetForProfile( 147 JNIEnv* env, jclass clazz, jobject j_profile_android) { 148 Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile_android); 149 AndroidProfileOAuth2TokenService* service = 150 ProfileOAuth2TokenServiceFactory::GetPlatformSpecificForProfile(profile); 151 return service->java_ref_.obj(); 152} 153 154static jobject GetForProfile(JNIEnv* env, 155 jclass clazz, 156 jobject j_profile_android) { 157 return AndroidProfileOAuth2TokenService::GetForProfile( 158 env, clazz, j_profile_android); 159} 160 161void AndroidProfileOAuth2TokenService::Initialize(SigninClient* client) { 162 VLOG(1) << "AndroidProfileOAuth2TokenService::Initialize"; 163 ProfileOAuth2TokenService::Initialize(client); 164 165 if (!is_testing_profile_) { 166 Java_OAuth2TokenService_validateAccounts( 167 AttachCurrentThread(), java_ref_.obj(), 168 base::android::GetApplicationContext(), JNI_TRUE); 169 } 170} 171 172bool AndroidProfileOAuth2TokenService::RefreshTokenIsAvailable( 173 const std::string& account_id) const { 174 JNIEnv* env = AttachCurrentThread(); 175 ScopedJavaLocalRef<jstring> j_account_id = 176 ConvertUTF8ToJavaString(env, account_id); 177 jboolean refresh_token_is_available = 178 Java_OAuth2TokenService_hasOAuth2RefreshToken( 179 env, base::android::GetApplicationContext(), 180 j_account_id.obj()); 181 return refresh_token_is_available == JNI_TRUE; 182} 183 184void AndroidProfileOAuth2TokenService::UpdateAuthError( 185 const std::string& account_id, 186 const GoogleServiceAuthError& error) { 187 // TODO(rogerta): do we need to update anything, or does the system handle it? 188} 189 190std::vector<std::string> AndroidProfileOAuth2TokenService::GetAccounts() { 191 std::vector<std::string> accounts; 192 JNIEnv* env = AttachCurrentThread(); 193 ScopedJavaLocalRef<jobjectArray> j_accounts = 194 Java_OAuth2TokenService_getAccounts( 195 env, base::android::GetApplicationContext()); 196 // TODO(fgorski): We may decide to filter out some of the accounts. 197 base::android::AppendJavaStringArrayToStringVector(env, 198 j_accounts.obj(), 199 &accounts); 200 return accounts; 201} 202 203std::vector<std::string> AndroidProfileOAuth2TokenService::GetSystemAccounts() { 204 std::vector<std::string> accounts; 205 JNIEnv* env = AttachCurrentThread(); 206 ScopedJavaLocalRef<jobjectArray> j_accounts = 207 Java_OAuth2TokenService_getSystemAccounts( 208 env, base::android::GetApplicationContext()); 209 base::android::AppendJavaStringArrayToStringVector(env, 210 j_accounts.obj(), 211 &accounts); 212 return accounts; 213} 214 215OAuth2AccessTokenFetcher* 216AndroidProfileOAuth2TokenService::CreateAccessTokenFetcher( 217 const std::string& account_id, 218 net::URLRequestContextGetter* getter, 219 OAuth2AccessTokenConsumer* consumer) { 220 DCHECK(!account_id.empty()); 221 return new AndroidAccessTokenFetcher(consumer, account_id); 222} 223 224void AndroidProfileOAuth2TokenService::InvalidateOAuth2Token( 225 const std::string& account_id, 226 const std::string& client_id, 227 const ScopeSet& scopes, 228 const std::string& access_token) { 229 OAuth2TokenService::InvalidateOAuth2Token(account_id, 230 client_id, 231 scopes, 232 access_token); 233 234 JNIEnv* env = AttachCurrentThread(); 235 ScopedJavaLocalRef<jstring> j_access_token = 236 ConvertUTF8ToJavaString(env, access_token); 237 Java_OAuth2TokenService_invalidateOAuth2AuthToken( 238 env, base::android::GetApplicationContext(), 239 j_access_token.obj()); 240} 241 242void AndroidProfileOAuth2TokenService::ValidateAccounts( 243 JNIEnv* env, 244 jobject obj, 245 jstring j_current_acc, 246 jboolean j_force_notifications) { 247 VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts from java"; 248 std::string signed_in_account = ConvertJavaStringToUTF8(env, j_current_acc); 249 ValidateAccounts(signed_in_account, j_force_notifications != JNI_FALSE); 250} 251 252void AndroidProfileOAuth2TokenService::ValidateAccounts( 253 const std::string& signed_in_account, 254 bool force_notifications) { 255 std::vector<std::string> prev_ids = GetAccounts(); 256 std::vector<std::string> curr_ids = GetSystemAccounts(); 257 std::vector<std::string> refreshed_ids; 258 std::vector<std::string> revoked_ids; 259 260 VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:" 261 << " sigined_in_account=" << signed_in_account 262 << " prev_ids=" << prev_ids.size() 263 << " curr_ids=" << curr_ids.size() 264 << " force=" << (force_notifications ? "true" : "false"); 265 266 if (!ValidateAccounts(signed_in_account, prev_ids, curr_ids, refreshed_ids, 267 revoked_ids, force_notifications)) { 268 curr_ids.clear(); 269 } 270 271 ScopedBacthChange batch(this); 272 273 JNIEnv* env = AttachCurrentThread(); 274 ScopedJavaLocalRef<jobjectArray> java_accounts( 275 base::android::ToJavaArrayOfStrings(env, curr_ids)); 276 Java_OAuth2TokenService_saveStoredAccounts( 277 env, base::android::GetApplicationContext(), java_accounts.obj()); 278 279 for (std::vector<std::string>::iterator it = refreshed_ids.begin(); 280 it != refreshed_ids.end(); it++) { 281 FireRefreshTokenAvailable(*it); 282 } 283 284 for (std::vector<std::string>::iterator it = revoked_ids.begin(); 285 it != revoked_ids.end(); it++) { 286 FireRefreshTokenRevoked(*it); 287 } 288} 289 290bool AndroidProfileOAuth2TokenService::ValidateAccounts( 291 const std::string& signed_in_account, 292 const std::vector<std::string>& prev_account_ids, 293 const std::vector<std::string>& curr_account_ids, 294 std::vector<std::string>& refreshed_ids, 295 std::vector<std::string>& revoked_ids, 296 bool force_notifications) { 297 if (std::find(curr_account_ids.begin(), 298 curr_account_ids.end(), 299 signed_in_account) != curr_account_ids.end()) { 300 // Test to see if an account is removed from the Android AccountManager. 301 // If so, invoke FireRefreshTokenRevoked to notify the reconcilor. 302 for (std::vector<std::string>::const_iterator it = prev_account_ids.begin(); 303 it != prev_account_ids.end(); it++) { 304 if (*it == signed_in_account) 305 continue; 306 307 if (std::find(curr_account_ids.begin(), 308 curr_account_ids.end(), 309 *it) == curr_account_ids.end()) { 310 VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:" 311 << "revoked=" << *it; 312 revoked_ids.push_back(*it); 313 } 314 } 315 316 if (force_notifications || 317 std::find(prev_account_ids.begin(), prev_account_ids.end(), 318 signed_in_account) == prev_account_ids.end()) { 319 // Always fire the primary signed in account first. 320 VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:" 321 << "refreshed=" << signed_in_account; 322 refreshed_ids.push_back(signed_in_account); 323 } 324 325 for (std::vector<std::string>::const_iterator it = curr_account_ids.begin(); 326 it != curr_account_ids.end(); it++) { 327 if (*it != signed_in_account) { 328 if (force_notifications || 329 std::find(prev_account_ids.begin(), 330 prev_account_ids.end(), 331 *it) == prev_account_ids.end()) { 332 VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:" 333 << "refreshed=" << *it; 334 refreshed_ids.push_back(*it); 335 } 336 } 337 } 338 return true; 339 } else { 340 // Currently signed in account does not any longer exist among accounts on 341 // system together with all other accounts. 342 if (std::find(prev_account_ids.begin(), prev_account_ids.end(), 343 signed_in_account) != prev_account_ids.end()) { 344 VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:" 345 << "revoked=" << signed_in_account; 346 revoked_ids.push_back(signed_in_account); 347 } 348 for (std::vector<std::string>::const_iterator it = prev_account_ids.begin(); 349 it != prev_account_ids.end(); it++) { 350 if (*it == signed_in_account) 351 continue; 352 VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:" 353 << "revoked=" << *it; 354 revoked_ids.push_back(*it); 355 } 356 return false; 357 } 358} 359 360void AndroidProfileOAuth2TokenService::FireRefreshTokenAvailableFromJava( 361 JNIEnv* env, 362 jobject obj, 363 const jstring account_name) { 364 std::string account_id = ConvertJavaStringToUTF8(env, account_name); 365 AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable(account_id); 366} 367 368void AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable( 369 const std::string& account_id) { 370 VLOG(1) << "AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable id=" 371 << account_id; 372 373 // Notify native observers. 374 OAuth2TokenService::FireRefreshTokenAvailable(account_id); 375 // Notify Java observers. 376 JNIEnv* env = AttachCurrentThread(); 377 ScopedJavaLocalRef<jstring> account_name = 378 ConvertUTF8ToJavaString(env, account_id); 379 Java_OAuth2TokenService_notifyRefreshTokenAvailable( 380 env, java_ref_.obj(), account_name.obj()); 381} 382 383void AndroidProfileOAuth2TokenService::FireRefreshTokenRevokedFromJava( 384 JNIEnv* env, 385 jobject obj, 386 const jstring account_name) { 387 std::string account_id = ConvertJavaStringToUTF8(env, account_name); 388 AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked(account_id); 389} 390 391void AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked( 392 const std::string& account_id) { 393 VLOG(1) << "AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked id=" 394 << account_id; 395 396 // Notify native observers. 397 OAuth2TokenService::FireRefreshTokenRevoked(account_id); 398 // Notify Java observers. 399 JNIEnv* env = AttachCurrentThread(); 400 ScopedJavaLocalRef<jstring> account_name = 401 ConvertUTF8ToJavaString(env, account_id); 402 Java_OAuth2TokenService_notifyRefreshTokenRevoked( 403 env, java_ref_.obj(), account_name.obj()); 404} 405 406void AndroidProfileOAuth2TokenService::FireRefreshTokensLoadedFromJava( 407 JNIEnv* env, 408 jobject obj) { 409 AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded(); 410} 411 412void AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded() { 413 VLOG(1) << "AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded"; 414 // Notify native observers. 415 OAuth2TokenService::FireRefreshTokensLoaded(); 416 // Notify Java observers. 417 JNIEnv* env = AttachCurrentThread(); 418 Java_OAuth2TokenService_notifyRefreshTokensLoaded( 419 env, java_ref_.obj()); 420} 421 422void AndroidProfileOAuth2TokenService::RevokeAllCredentials() { 423 VLOG(1) << "AndroidProfileOAuth2TokenService::RevokeAllCredentials"; 424 ScopedBacthChange batch(this); 425 std::vector<std::string> accounts = GetAccounts(); 426 for (std::vector<std::string>::iterator it = accounts.begin(); 427 it != accounts.end(); it++) { 428 FireRefreshTokenRevoked(*it); 429 } 430 431 // Clear everything on the Java side as well. 432 std::vector<std::string> empty; 433 JNIEnv* env = AttachCurrentThread(); 434 ScopedJavaLocalRef<jobjectArray> java_accounts( 435 base::android::ToJavaArrayOfStrings(env, empty)); 436 Java_OAuth2TokenService_saveStoredAccounts( 437 env, base::android::GetApplicationContext(), java_accounts.obj()); 438} 439 440// Called from Java when fetching of an OAuth2 token is finished. The 441// |authToken| param is only valid when |result| is true. 442void OAuth2TokenFetched( 443 JNIEnv* env, 444 jclass clazz, 445 jstring authToken, 446 jboolean result, 447 jlong nativeCallback) { 448 std::string token = ConvertJavaStringToUTF8(env, authToken); 449 scoped_ptr<FetchOAuth2TokenCallback> heap_callback( 450 reinterpret_cast<FetchOAuth2TokenCallback*>(nativeCallback)); 451 // Android does not provide enough information to know if the credentials are 452 // wrong, so assume any error is transient by using CONNECTION_FAILED. 453 GoogleServiceAuthError err(result ? 454 GoogleServiceAuthError::NONE : 455 GoogleServiceAuthError::CONNECTION_FAILED); 456 heap_callback->Run(err, token, base::Time()); 457} 458 459// static 460bool AndroidProfileOAuth2TokenService::Register(JNIEnv* env) { 461 return RegisterNativesImpl(env); 462} 463