// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/signin/android_profile_oauth2_token_service.h" #include "base/android/jni_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/bind.h" #include "base/logging.h" #include "chrome/browser/profiles/profile_android.h" #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" #include "chrome/browser/sync/profile_sync_service_android.h" #include "content/public/browser/browser_thread.h" #include "jni/OAuth2TokenService_jni.h" using base::android::AttachCurrentThread; using base::android::ConvertJavaStringToUTF8; using base::android::ConvertUTF8ToJavaString; using base::android::ScopedJavaLocalRef; using content::BrowserThread; namespace { std::string CombineScopes(const OAuth2TokenService::ScopeSet& scopes) { // The Android AccountManager supports multiple scopes separated by a space: // https://code.google.com/p/google-api-java-client/wiki/OAuth2#Android std::string scope; for (OAuth2TokenService::ScopeSet::const_iterator it = scopes.begin(); it != scopes.end(); ++it) { if (!scope.empty()) scope += " "; scope += *it; } return scope; } // Callback from FetchOAuth2TokenWithUsername(). // Arguments: // - the error, or NONE if the token fetch was successful. // - the OAuth2 access token. // - the expiry time of the token (may be null, indicating that the expiry // time is unknown. typedef base::Callback FetchOAuth2TokenCallback; } // namespace AndroidProfileOAuth2TokenService::AndroidProfileOAuth2TokenService() { JNIEnv* env = AttachCurrentThread(); base::android::ScopedJavaLocalRef local_java_ref = Java_OAuth2TokenService_create(env, reinterpret_cast(this)); java_ref_.Reset(env, local_java_ref.obj()); } AndroidProfileOAuth2TokenService::~AndroidProfileOAuth2TokenService() {} // static jobject AndroidProfileOAuth2TokenService::GetForProfile( JNIEnv* env, jclass clazz, jobject j_profile_android) { Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile_android); AndroidProfileOAuth2TokenService* service = ProfileOAuth2TokenServiceFactory::GetPlatformSpecificForProfile(profile); return service->java_ref_.obj(); } static jobject GetForProfile(JNIEnv* env, jclass clazz, jobject j_profile_android) { return AndroidProfileOAuth2TokenService::GetForProfile( env, clazz, j_profile_android); } bool AndroidProfileOAuth2TokenService::RefreshTokenIsAvailable( const std::string& account_id) const { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef j_account_id = ConvertUTF8ToJavaString(env, account_id); jboolean refresh_token_is_available = Java_OAuth2TokenService_hasOAuth2RefreshToken( env, base::android::GetApplicationContext(), j_account_id.obj()); return refresh_token_is_available != JNI_FALSE; } std::vector AndroidProfileOAuth2TokenService::GetAccounts() { std::vector accounts; JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef j_accounts = Java_OAuth2TokenService_getAccounts( env, base::android::GetApplicationContext()); // TODO(fgorski): We may decide to filter out some of the accounts. base::android::AppendJavaStringArrayToStringVector(env, j_accounts.obj(), &accounts); return accounts; } std::vector AndroidProfileOAuth2TokenService::GetSystemAccounts() { std::vector accounts; JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef j_accounts = Java_OAuth2TokenService_getSystemAccounts( env, base::android::GetApplicationContext()); base::android::AppendJavaStringArrayToStringVector(env, j_accounts.obj(), &accounts); return accounts; } void AndroidProfileOAuth2TokenService::FetchOAuth2Token( RequestImpl* request, const std::string& account_id, net::URLRequestContextGetter* getter, const std::string& client_id, const std::string& client_secret, const OAuth2TokenService::ScopeSet& scopes) { DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); DCHECK(!account_id.empty()); JNIEnv* env = AttachCurrentThread(); std::string scope = CombineScopes(scopes); ScopedJavaLocalRef j_username = ConvertUTF8ToJavaString(env, account_id); ScopedJavaLocalRef j_scope = ConvertUTF8ToJavaString(env, scope); // Allocate a copy of the request WeakPtr on the heap, because the object // needs to be passed through JNI as an int. // It will be passed back to OAuth2TokenFetched(), where it will be freed. scoped_ptr heap_callback( new FetchOAuth2TokenCallback(base::Bind(&RequestImpl::InformConsumer, request->AsWeakPtr()))); // Call into Java to get a new token. Java_OAuth2TokenService_getOAuth2AuthToken( env, base::android::GetApplicationContext(), j_username.obj(), j_scope.obj(), reinterpret_cast(heap_callback.release())); } OAuth2AccessTokenFetcher* AndroidProfileOAuth2TokenService::CreateAccessTokenFetcher( const std::string& account_id, net::URLRequestContextGetter* getter, OAuth2AccessTokenConsumer* consumer) { NOTREACHED(); return NULL; } void AndroidProfileOAuth2TokenService::InvalidateOAuth2Token( const std::string& account_id, const std::string& client_id, const ScopeSet& scopes, const std::string& access_token) { OAuth2TokenService::InvalidateOAuth2Token(account_id, client_id, scopes, access_token); JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef j_access_token = ConvertUTF8ToJavaString(env, access_token); Java_OAuth2TokenService_invalidateOAuth2AuthToken( env, base::android::GetApplicationContext(), j_access_token.obj()); } void AndroidProfileOAuth2TokenService::ValidateAccounts( JNIEnv* env, jobject obj, jstring j_current_acc) { std::string signed_in_account = ConvertJavaStringToUTF8(env, j_current_acc); ValidateAccounts(signed_in_account); } void AndroidProfileOAuth2TokenService::ValidateAccounts( const std::string& signed_in_account) { std::vector prev_ids = GetAccounts(); std::vector curr_ids = GetSystemAccounts(); std::vector refreshed_ids; std::vector revoked_ids; if (!ValidateAccounts( signed_in_account, prev_ids, curr_ids, refreshed_ids, revoked_ids)) { curr_ids.clear(); } JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef java_accounts( base::android::ToJavaArrayOfStrings(env, curr_ids)); Java_OAuth2TokenService_saveStoredAccounts( env, base::android::GetApplicationContext(), java_accounts.obj()); for (std::vector::iterator it = refreshed_ids.begin(); it != refreshed_ids.end(); it++) { FireRefreshTokenAvailable(*it); } for (std::vector::iterator it = revoked_ids.begin(); it != revoked_ids.end(); it++) { FireRefreshTokenRevoked(*it); } } bool AndroidProfileOAuth2TokenService::ValidateAccounts( const std::string& signed_in_account, const std::vector& prev_account_ids, const std::vector& curr_account_ids, std::vector& refreshed_ids, std::vector& revoked_ids) { if (std::find(curr_account_ids.begin(), curr_account_ids.end(), signed_in_account) != curr_account_ids.end()) { // Test to see if an account is removed from the Android AccountManager. // If so, invoke FireRefreshTokenRevoked to notify the reconcilor. for (std::vector::const_iterator it = prev_account_ids.begin(); it != prev_account_ids.end(); it++) { if (*it == signed_in_account) continue; if (std::find(curr_account_ids.begin(), curr_account_ids.end(), *it) == curr_account_ids.end()) { revoked_ids.push_back(*it); } } // Always fire the primary signed in account first. refreshed_ids.push_back(signed_in_account); for (std::vector::const_iterator it = curr_account_ids.begin(); it != curr_account_ids.end(); it++) { if (*it != signed_in_account) { refreshed_ids.push_back(*it); } } return true; } else { // Currently signed in account does not any longer exist among accounts on // system together with all other accounts. if (!signed_in_account.empty()) { revoked_ids.push_back(signed_in_account); } for (std::vector::const_iterator it = prev_account_ids.begin(); it != prev_account_ids.end(); it++) { if (*it == signed_in_account) continue; revoked_ids.push_back(*it); } return false; } } void AndroidProfileOAuth2TokenService::FireRefreshTokenAvailableFromJava( JNIEnv* env, jobject obj, const jstring account_name) { std::string account_id = ConvertJavaStringToUTF8(env, account_name); AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable(account_id); } void AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable( const std::string& account_id) { // Notify native observers. OAuth2TokenService::FireRefreshTokenAvailable(account_id); // Notify Java observers. JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef account_name = ConvertUTF8ToJavaString(env, account_id); Java_OAuth2TokenService_notifyRefreshTokenAvailable( env, java_ref_.obj(), account_name.obj()); } void AndroidProfileOAuth2TokenService::FireRefreshTokenRevokedFromJava( JNIEnv* env, jobject obj, const jstring account_name) { std::string account_id = ConvertJavaStringToUTF8(env, account_name); AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked(account_id); } void AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked( const std::string& account_id) { // Notify native observers. OAuth2TokenService::FireRefreshTokenRevoked(account_id); // Notify Java observers. JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef account_name = ConvertUTF8ToJavaString(env, account_id); Java_OAuth2TokenService_notifyRefreshTokenRevoked( env, java_ref_.obj(), account_name.obj()); } void AndroidProfileOAuth2TokenService::FireRefreshTokensLoadedFromJava( JNIEnv* env, jobject obj) { AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded(); } void AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded() { // Notify native observers. OAuth2TokenService::FireRefreshTokensLoaded(); // Notify Java observers. JNIEnv* env = AttachCurrentThread(); Java_OAuth2TokenService_notifyRefreshTokensLoaded( env, java_ref_.obj()); } void AndroidProfileOAuth2TokenService::RevokeAllCredentials() { std::vector accounts = GetAccounts(); for (std::vector::iterator it = accounts.begin(); it != accounts.end(); it++) { FireRefreshTokenRevoked(*it); } } // Called from Java when fetching of an OAuth2 token is finished. The // |authToken| param is only valid when |result| is true. void OAuth2TokenFetched(JNIEnv* env, jclass clazz, jstring authToken, jboolean result, jlong nativeCallback) { std::string token = ConvertJavaStringToUTF8(env, authToken); scoped_ptr heap_callback( reinterpret_cast(nativeCallback)); // Android does not provide enough information to know if the credentials are // wrong, so assume any error is transient by using CONNECTION_FAILED. GoogleServiceAuthError err(result ? GoogleServiceAuthError::NONE : GoogleServiceAuthError::CONNECTION_FAILED); heap_callback->Run(err, token, base::Time()); } // static bool AndroidProfileOAuth2TokenService::Register(JNIEnv* env) { return RegisterNativesImpl(env); }