android_profile_oauth2_token_service.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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
185  if (!ValidateAccounts(signed_in_account, prev_ids, curr_ids)) {
186    curr_ids.clear();
187  }
188
189  JNIEnv* env = AttachCurrentThread();
190  ScopedJavaLocalRef<jobjectArray> java_accounts(
191      base::android::ToJavaArrayOfStrings(env, curr_ids));
192  Java_OAuth2TokenService_saveStoredAccounts(
193      env, base::android::GetApplicationContext(), java_accounts.obj());
194}
195
196bool AndroidProfileOAuth2TokenService::ValidateAccounts(
197    const std::string& signed_in_account,
198    const std::vector<std::string>& prev_account_ids,
199    const std::vector<std::string>& curr_account_ids) {
200  if (std::find(curr_account_ids.begin(),
201                curr_account_ids.end(),
202                signed_in_account) != curr_account_ids.end()) {
203    // Test to see if an account is removed from the Android AccountManager.
204    // If so, invoke FireRefreshTokenRevoked to notify the reconcilor.
205    for (std::vector<std::string>::const_iterator it = prev_account_ids.begin();
206         it != prev_account_ids.end(); it++) {
207      if (*it == signed_in_account)
208        continue;
209
210      if (std::find(curr_account_ids.begin(),
211                    curr_account_ids.end(),
212                    *it) == curr_account_ids.end()) {
213        FireRefreshTokenRevoked(*it);
214      }
215    }
216
217    // Always fire the primary signed in account first.
218    FireRefreshTokenAvailable(signed_in_account);
219
220    for (std::vector<std::string>::const_iterator it = curr_account_ids.begin();
221         it != curr_account_ids.end(); it++) {
222      if (*it != signed_in_account) {
223        FireRefreshTokenAvailable(*it);
224      }
225    }
226    return true;
227  } else {
228    // Currently signed in account does not any longer exist among accounts on
229    // system together with all other accounts.
230    if (!signed_in_account.empty()) {
231      FireRefreshTokenRevoked(signed_in_account);
232    }
233    for (std::vector<std::string>::const_iterator it = prev_account_ids.begin();
234         it != prev_account_ids.end(); it++) {
235      if (*it == signed_in_account)
236        continue;
237      FireRefreshTokenRevoked(*it);
238    }
239    return false;
240  }
241}
242
243void AndroidProfileOAuth2TokenService::FireRefreshTokenAvailableFromJava(
244    JNIEnv* env,
245    jobject obj,
246    const jstring account_name) {
247  std::string account_id = ConvertJavaStringToUTF8(env, account_name);
248  AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable(account_id);
249}
250
251void AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable(
252    const std::string& account_id) {
253  // Notify native observers.
254  OAuth2TokenService::FireRefreshTokenAvailable(account_id);
255  // Notify Java observers.
256  JNIEnv* env = AttachCurrentThread();
257  ScopedJavaLocalRef<jstring> account_name =
258      ConvertUTF8ToJavaString(env, account_id);
259  Java_OAuth2TokenService_notifyRefreshTokenAvailable(
260      env, java_ref_.obj(), account_name.obj());
261}
262
263void AndroidProfileOAuth2TokenService::FireRefreshTokenRevokedFromJava(
264    JNIEnv* env,
265    jobject obj,
266    const jstring account_name) {
267  std::string account_id = ConvertJavaStringToUTF8(env, account_name);
268  AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked(account_id);
269}
270
271void AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked(
272    const std::string& account_id) {
273  // Notify native observers.
274  OAuth2TokenService::FireRefreshTokenRevoked(account_id);
275  // Notify Java observers.
276  JNIEnv* env = AttachCurrentThread();
277  ScopedJavaLocalRef<jstring> account_name =
278      ConvertUTF8ToJavaString(env, account_id);
279  Java_OAuth2TokenService_notifyRefreshTokenRevoked(
280      env, java_ref_.obj(), account_name.obj());
281}
282
283void AndroidProfileOAuth2TokenService::FireRefreshTokensLoadedFromJava(
284    JNIEnv* env,
285    jobject obj) {
286  AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded();
287}
288
289void AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded() {
290  // Notify native observers.
291  OAuth2TokenService::FireRefreshTokensLoaded();
292  // Notify Java observers.
293  JNIEnv* env = AttachCurrentThread();
294  Java_OAuth2TokenService_notifyRefreshTokensLoaded(
295      env, java_ref_.obj());
296}
297
298void AndroidProfileOAuth2TokenService::RevokeAllCredentials() {
299  std::vector<std::string> accounts = GetAccounts();
300  for (std::vector<std::string>::iterator it = accounts.begin();
301       it != accounts.end(); it++) {
302    FireRefreshTokenRevoked(*it);
303  }
304}
305
306// Called from Java when fetching of an OAuth2 token is finished. The
307// |authToken| param is only valid when |result| is true.
308void OAuth2TokenFetched(JNIEnv* env, jclass clazz,
309    jstring authToken,
310    jboolean result,
311    jlong nativeCallback) {
312  std::string token = ConvertJavaStringToUTF8(env, authToken);
313  scoped_ptr<FetchOAuth2TokenCallback> heap_callback(
314      reinterpret_cast<FetchOAuth2TokenCallback*>(nativeCallback));
315  // Android does not provide enough information to know if the credentials are
316  // wrong, so assume any error is transient by using CONNECTION_FAILED.
317  GoogleServiceAuthError err(result ?
318                             GoogleServiceAuthError::NONE :
319                             GoogleServiceAuthError::CONNECTION_FAILED);
320  heap_callback->Run(err, token, base::Time());
321}
322
323// static
324bool AndroidProfileOAuth2TokenService::Register(JNIEnv* env) {
325  return RegisterNativesImpl(env);
326}
327