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