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