blob: 13815e7a14a4c9133d8048279b80653643e1bd97 [file] [log] [blame]
// 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/oauth2_token_service_delegate_android.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 "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.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 "components/signin/core/browser/account_info.h"
#include "content/public/browser/browser_thread.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "google_apis/gaia/oauth2_access_token_fetcher.h"
#include "jni/OAuth2TokenService_jni.h"
using base::android::AttachCurrentThread;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef;
using base::android::JavaRef;
using base::android::ScopedJavaLocalRef;
using content::BrowserThread;
namespace {
// 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<void(const GoogleServiceAuthError&,
const std::string&,
const base::Time&)> FetchOAuth2TokenCallback;
class AndroidAccessTokenFetcher : public OAuth2AccessTokenFetcher {
public:
AndroidAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer,
const std::string& account_id);
~AndroidAccessTokenFetcher() override;
// Overrides from OAuth2AccessTokenFetcher:
void Start(const std::string& client_id,
const std::string& client_secret,
const std::vector<std::string>& scopes) override;
void CancelRequest() override;
// Handles an access token response.
void OnAccessTokenResponse(const GoogleServiceAuthError& error,
const std::string& access_token,
const base::Time& expiration_time);
private:
std::string CombineScopes(const std::vector<std::string>& scopes);
std::string account_id_;
bool request_was_cancelled_;
base::WeakPtrFactory<AndroidAccessTokenFetcher> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(AndroidAccessTokenFetcher);
};
AndroidAccessTokenFetcher::AndroidAccessTokenFetcher(
OAuth2AccessTokenConsumer* consumer,
const std::string& account_id)
: OAuth2AccessTokenFetcher(consumer),
account_id_(account_id),
request_was_cancelled_(false),
weak_factory_(this) {
}
AndroidAccessTokenFetcher::~AndroidAccessTokenFetcher() {
}
void AndroidAccessTokenFetcher::Start(const std::string& client_id,
const std::string& client_secret,
const std::vector<std::string>& scopes) {
JNIEnv* env = AttachCurrentThread();
std::string scope = CombineScopes(scopes);
ScopedJavaLocalRef<jstring> j_username =
ConvertUTF8ToJavaString(env, account_id_);
ScopedJavaLocalRef<jstring> j_scope = ConvertUTF8ToJavaString(env, scope);
std::unique_ptr<FetchOAuth2TokenCallback> heap_callback(
new FetchOAuth2TokenCallback(
base::Bind(&AndroidAccessTokenFetcher::OnAccessTokenResponse,
weak_factory_.GetWeakPtr())));
// Call into Java to get a new token.
Java_OAuth2TokenService_getOAuth2AuthToken(
env, j_username, j_scope,
reinterpret_cast<intptr_t>(heap_callback.release()));
}
void AndroidAccessTokenFetcher::CancelRequest() {
request_was_cancelled_ = true;
}
void AndroidAccessTokenFetcher::OnAccessTokenResponse(
const GoogleServiceAuthError& error,
const std::string& access_token,
const base::Time& expiration_time) {
if (request_was_cancelled_) {
// Ignore the callback if the request was cancelled.
return;
}
if (error.state() == GoogleServiceAuthError::NONE) {
FireOnGetTokenSuccess(access_token, expiration_time);
} else {
FireOnGetTokenFailure(error);
}
}
// static
std::string AndroidAccessTokenFetcher::CombineScopes(
const std::vector<std::string>& 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 (std::vector<std::string>::const_iterator it = scopes.begin();
it != scopes.end(); ++it) {
if (!scope.empty())
scope += " ";
scope += *it;
}
return scope;
}
} // namespace
bool OAuth2TokenServiceDelegateAndroid::is_testing_profile_ = false;
OAuth2TokenServiceDelegateAndroid::OAuth2TokenServiceDelegateAndroid(
AccountTrackerService* account_tracker_service)
: account_tracker_service_(account_tracker_service),
fire_refresh_token_loaded_(RT_LOAD_NOT_START) {
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ctor";
DCHECK(account_tracker_service_);
JNIEnv* env = AttachCurrentThread();
base::android::ScopedJavaLocalRef<jobject> local_java_ref =
Java_OAuth2TokenService_create(env, reinterpret_cast<intptr_t>(this));
java_ref_.Reset(env, local_java_ref.obj());
if (account_tracker_service_->GetMigrationState() ==
AccountTrackerService::MIGRATION_IN_PROGRESS) {
std::vector<std::string> accounts = GetAccounts();
std::vector<std::string> accounts_id;
for (auto account_name : accounts) {
AccountInfo account_info =
account_tracker_service_->FindAccountInfoByEmail(account_name);
DCHECK(!account_info.gaia.empty());
accounts_id.push_back(account_info.gaia);
}
ScopedJavaLocalRef<jobjectArray> java_accounts(
base::android::ToJavaArrayOfStrings(env, accounts_id));
Java_OAuth2TokenService_saveStoredAccounts(env, java_accounts);
}
if (!is_testing_profile_) {
Java_OAuth2TokenService_validateAccounts(AttachCurrentThread(), java_ref_,
JNI_TRUE);
}
}
OAuth2TokenServiceDelegateAndroid::~OAuth2TokenServiceDelegateAndroid() {
}
// static
ScopedJavaLocalRef<jobject> OAuth2TokenServiceDelegateAndroid::GetForProfile(
JNIEnv* env,
const JavaRef<jobject>& j_profile_android) {
Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile_android);
ProfileOAuth2TokenService* service =
ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
OAuth2TokenServiceDelegate* delegate = service->GetDelegate();
return ScopedJavaLocalRef<jobject>(
static_cast<OAuth2TokenServiceDelegateAndroid*>(delegate)->java_ref_);
}
static ScopedJavaLocalRef<jobject> JNI_OAuth2TokenService_GetForProfile(
JNIEnv* env,
const JavaParamRef<jclass>& clazz,
const JavaParamRef<jobject>& j_profile_android) {
return OAuth2TokenServiceDelegateAndroid::GetForProfile(env,
j_profile_android);
}
bool OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable(
const std::string& account_id) const {
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RefreshTokenIsAvailable"
<< " account= " << account_id;
std::string account_name = MapAccountIdToAccountName(account_id);
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jstring> j_account_id =
ConvertUTF8ToJavaString(env, account_name);
jboolean refresh_token_is_available =
Java_OAuth2TokenService_hasOAuth2RefreshToken(env, j_account_id);
return refresh_token_is_available == JNI_TRUE;
}
GoogleServiceAuthError OAuth2TokenServiceDelegateAndroid::GetAuthError(
const std::string& account_id) const {
auto it = errors_.find(account_id);
return (it == errors_.end()) ? GoogleServiceAuthError::AuthErrorNone()
: it->second;
}
void OAuth2TokenServiceDelegateAndroid::UpdateAuthError(
const std::string& account_id,
const GoogleServiceAuthError& error) {
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::UpdateAuthError"
<< " account=" << account_id
<< " error=" << error.ToString();
if (error.IsTransientError())
return;
auto it = errors_.find(account_id);
if (error.state() == GoogleServiceAuthError::NONE) {
if (it == errors_.end())
return;
errors_.erase(it);
} else {
if (it != errors_.end() && it->second == error)
return;
errors_[account_id] = error;
}
FireAuthErrorChanged(account_id, error);
}
std::vector<std::string> OAuth2TokenServiceDelegateAndroid::GetAccounts() {
std::vector<std::string> accounts;
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobjectArray> j_accounts =
Java_OAuth2TokenService_getAccounts(env);
// TODO(fgorski): We may decide to filter out some of the accounts.
base::android::AppendJavaStringArrayToStringVector(env, j_accounts.obj(),
&accounts);
return accounts;
}
std::vector<std::string>
OAuth2TokenServiceDelegateAndroid::GetSystemAccountNames() {
std::vector<std::string> account_names;
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobjectArray> j_accounts =
Java_OAuth2TokenService_getSystemAccountNames(env);
base::android::AppendJavaStringArrayToStringVector(env, j_accounts.obj(),
&account_names);
return account_names;
}
OAuth2AccessTokenFetcher*
OAuth2TokenServiceDelegateAndroid::CreateAccessTokenFetcher(
const std::string& account_id,
net::URLRequestContextGetter* getter,
OAuth2AccessTokenConsumer* consumer) {
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::CreateAccessTokenFetcher"
<< " account= " << account_id;
ValidateAccountId(account_id);
return new AndroidAccessTokenFetcher(consumer,
MapAccountIdToAccountName(account_id));
}
void OAuth2TokenServiceDelegateAndroid::InvalidateAccessToken(
const std::string& account_id,
const std::string& client_id,
const OAuth2TokenService::ScopeSet& scopes,
const std::string& access_token) {
ValidateAccountId(account_id);
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jstring> j_access_token =
ConvertUTF8ToJavaString(env, access_token);
Java_OAuth2TokenService_invalidateOAuth2AuthToken(env, j_access_token);
}
void OAuth2TokenServiceDelegateAndroid::ValidateAccounts(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jstring>& j_current_acc,
jboolean j_force_notifications) {
std::string signed_in_account_name;
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts from java";
if (j_current_acc)
signed_in_account_name = ConvertJavaStringToUTF8(env, j_current_acc);
if (!signed_in_account_name.empty())
signed_in_account_name = gaia::CanonicalizeEmail(signed_in_account_name);
// Clear any auth errors so that client can retry to get access tokens.
errors_.clear();
ValidateAccounts(MapAccountNameToAccountId(signed_in_account_name),
j_force_notifications != JNI_FALSE);
}
void OAuth2TokenServiceDelegateAndroid::ValidateAccounts(
const std::string& signed_in_account_id,
bool force_notifications) {
std::vector<std::string> curr_ids;
for (const std::string& curr_name : GetSystemAccountNames()) {
std::string curr_id(MapAccountNameToAccountId(curr_name));
if (!curr_id.empty())
curr_ids.push_back(curr_id);
}
std::vector<std::string> prev_ids;
for (const std::string& prev_id : GetAccounts()) {
if (ValidateAccountId(prev_id))
prev_ids.push_back(prev_id);
}
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
<< " sigined_in_account_id=" << signed_in_account_id
<< " prev_ids=" << prev_ids.size() << " curr_ids=" << curr_ids.size()
<< " force=" << (force_notifications ? "true" : "false");
std::vector<std::string> refreshed_ids;
std::vector<std::string> revoked_ids;
bool currently_signed_in =
ValidateAccounts(signed_in_account_id, prev_ids, curr_ids, &refreshed_ids,
&revoked_ids, force_notifications);
ScopedBatchChange batch(this);
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobjectArray> java_accounts;
if (currently_signed_in) {
java_accounts = base::android::ToJavaArrayOfStrings(env, curr_ids);
} else {
java_accounts =
base::android::ToJavaArrayOfStrings(env, std::vector<std::string>());
}
// Save the current accounts in the token service before calling
// FireRefreshToken* methods.
Java_OAuth2TokenService_saveStoredAccounts(env, java_accounts);
for (const std::string& refreshed_id : refreshed_ids)
FireRefreshTokenAvailable(refreshed_id);
for (const std::string& revoked_id : revoked_ids)
FireRefreshTokenRevoked(revoked_id);
if (fire_refresh_token_loaded_ == RT_WAIT_FOR_VALIDATION) {
fire_refresh_token_loaded_ = RT_LOADED;
FireRefreshTokensLoaded();
} else if (fire_refresh_token_loaded_ == RT_LOAD_NOT_START) {
fire_refresh_token_loaded_ = RT_HAS_BEEN_VALIDATED;
}
// Clear accounts no longer exist on device from AccountTrackerService.
std::vector<AccountInfo> accounts_info =
account_tracker_service_->GetAccounts();
for (const AccountInfo& info : accounts_info) {
if (!base::ContainsValue(curr_ids, info.account_id))
account_tracker_service_->RemoveAccount(info.account_id);
}
// No need to wait for SigninManager to finish migration if not signed in.
if (account_tracker_service_->GetMigrationState() ==
AccountTrackerService::MIGRATION_IN_PROGRESS &&
signed_in_account_id.empty()) {
account_tracker_service_->SetMigrationDone();
}
}
bool OAuth2TokenServiceDelegateAndroid::ValidateAccounts(
const std::string& signed_in_id,
const std::vector<std::string>& prev_ids,
const std::vector<std::string>& curr_ids,
std::vector<std::string>* refreshed_ids,
std::vector<std::string>* revoked_ids,
bool force_notifications) {
bool currently_signed_in = base::ContainsValue(curr_ids, signed_in_id);
if (currently_signed_in) {
// Revoke token for ids that have been removed from the device.
for (const std::string& prev_id : prev_ids) {
if (prev_id == signed_in_id)
continue;
if (!base::ContainsValue(curr_ids, prev_id)) {
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
<< "revoked=" << prev_id;
revoked_ids->push_back(prev_id);
}
}
// Refresh token for new ids or all ids if |force_notifications|.
if (force_notifications || !base::ContainsValue(prev_ids, signed_in_id)) {
// Always fire the primary signed in account first.
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
<< "refreshed=" << signed_in_id;
refreshed_ids->push_back(signed_in_id);
}
for (const std::string& curr_id : curr_ids) {
if (curr_id == signed_in_id)
continue;
if (force_notifications || !base::ContainsValue(prev_ids, curr_id)) {
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
<< "refreshed=" << curr_id;
refreshed_ids->push_back(curr_id);
}
}
} else {
if (base::ContainsValue(prev_ids, signed_in_id)) {
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
<< "revoked=" << signed_in_id;
revoked_ids->push_back(signed_in_id);
}
for (const std::string& prev_id : prev_ids) {
if (prev_id == signed_in_id)
continue;
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::ValidateAccounts:"
<< "revoked=" << prev_id;
revoked_ids->push_back(prev_id);
}
}
return currently_signed_in;
}
void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenAvailableFromJava(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jstring>& account_name) {
std::string account_id =
MapAccountNameToAccountId(ConvertJavaStringToUTF8(env, account_name));
// Notify native observers.
FireRefreshTokenAvailable(account_id);
}
void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenAvailable(
const std::string& account_id) {
DCHECK(!account_id.empty());
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::FireRefreshTokenAvailable id="
<< account_id;
std::string account_name = MapAccountIdToAccountName(account_id);
DCHECK(!account_name.empty());
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jstring> j_account_name =
ConvertUTF8ToJavaString(env, account_name);
Java_OAuth2TokenService_notifyRefreshTokenAvailable(env, java_ref_,
j_account_name);
OAuth2TokenServiceDelegate::FireRefreshTokenAvailable(account_id);
}
void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenRevokedFromJava(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
const JavaParamRef<jstring>& account_name) {
std::string account_id =
MapAccountNameToAccountId(ConvertJavaStringToUTF8(env, account_name));
// Notify native observers.
FireRefreshTokenRevoked(account_id);
}
void OAuth2TokenServiceDelegateAndroid::FireRefreshTokenRevoked(
const std::string& account_id) {
DCHECK(!account_id.empty());
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::FireRefreshTokenRevoked id="
<< account_id;
std::string account_name = MapAccountIdToAccountName(account_id);
if (!account_name.empty()) {
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jstring> j_account_name =
ConvertUTF8ToJavaString(env, account_name);
Java_OAuth2TokenService_notifyRefreshTokenRevoked(env, java_ref_,
j_account_name);
} else {
// Current prognosis is that we have an unmigrated account which is due for
// deletion. Record a histogram to debug this.
UMA_HISTOGRAM_ENUMERATION("OAuth2Login.AccountRevoked.MigrationState",
account_tracker_service_->GetMigrationState(),
AccountTrackerService::NUM_MIGRATION_STATES);
bool is_email_id = account_id.find('@') != std::string::npos;
UMA_HISTOGRAM_BOOLEAN("OAuth2Login.AccountRevoked.IsEmailId", is_email_id);
}
OAuth2TokenServiceDelegate::FireRefreshTokenRevoked(account_id);
}
void OAuth2TokenServiceDelegateAndroid::FireRefreshTokensLoadedFromJava(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
// Notify native observers.
FireRefreshTokensLoaded();
}
void OAuth2TokenServiceDelegateAndroid::FireRefreshTokensLoaded() {
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::FireRefreshTokensLoaded";
JNIEnv* env = AttachCurrentThread();
Java_OAuth2TokenService_notifyRefreshTokensLoaded(env, java_ref_);
OAuth2TokenServiceDelegate::FireRefreshTokensLoaded();
}
void OAuth2TokenServiceDelegateAndroid::RevokeAllCredentials() {
DVLOG(1) << "OAuth2TokenServiceDelegateAndroid::RevokeAllCredentials";
ScopedBatchChange batch(this);
std::vector<std::string> accounts_to_revoke = GetAccounts();
// Clear accounts in the token service before calling
// |FireRefreshTokenRevoked|.
JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobjectArray> java_accounts(
base::android::ToJavaArrayOfStrings(env, std::vector<std::string>()));
Java_OAuth2TokenService_saveStoredAccounts(env, java_accounts);
for (const std::string& account : accounts_to_revoke)
FireRefreshTokenRevoked(account);
}
void OAuth2TokenServiceDelegateAndroid::LoadCredentials(
const std::string& primary_account_id) {
if (primary_account_id.empty()) {
FireRefreshTokensLoaded();
return;
}
if (fire_refresh_token_loaded_ == RT_HAS_BEEN_VALIDATED) {
fire_refresh_token_loaded_ = RT_LOADED;
FireRefreshTokensLoaded();
} else if (fire_refresh_token_loaded_ == RT_LOAD_NOT_START) {
fire_refresh_token_loaded_ = RT_WAIT_FOR_VALIDATION;
}
}
std::string OAuth2TokenServiceDelegateAndroid::MapAccountIdToAccountName(
const std::string& account_id) const {
std::string account_name =
account_tracker_service_->GetAccountInfo(account_id).email;
DCHECK(!account_name.empty() || account_id.empty())
<< "Can't find account name, account_id=" << account_id;
return account_name;
}
std::string OAuth2TokenServiceDelegateAndroid::MapAccountNameToAccountId(
const std::string& account_name) const {
std::string account_id =
account_tracker_service_->FindAccountInfoByEmail(account_name).account_id;
DCHECK(!account_id.empty() || account_name.empty())
<< "Can't find account id, account_name=" << account_name;
return account_id;
}
// Called from Java when fetching of an OAuth2 token is finished. The
// |authToken| param is only valid when |result| is true.
void JNI_OAuth2TokenService_OAuth2TokenFetched(
JNIEnv* env,
const JavaParamRef<jclass>& clazz,
const JavaParamRef<jstring>& authToken,
jboolean isTransientError,
jlong nativeCallback) {
std::string token;
if (authToken)
token = ConvertJavaStringToUTF8(env, authToken);
std::unique_ptr<FetchOAuth2TokenCallback> heap_callback(
reinterpret_cast<FetchOAuth2TokenCallback*>(nativeCallback));
GoogleServiceAuthError err = GoogleServiceAuthError::AuthErrorNone();
if (!authToken) {
err =
isTransientError
? GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED)
: GoogleServiceAuthError::FromInvalidGaiaCredentialsReason(
GoogleServiceAuthError::InvalidGaiaCredentialsReason::
CREDENTIALS_REJECTED_BY_SERVER);
}
heap_callback->Run(err, token, base::Time());
}