android_profile_oauth2_token_service.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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
51bool AndroidProfileOAuth2TokenService::is_testing_profile_ = false;
52
53AndroidProfileOAuth2TokenService::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
61AndroidProfileOAuth2TokenService::~AndroidProfileOAuth2TokenService() {}
62
63// static
64jobject 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
72static jobject GetForProfile(JNIEnv* env,
73                             jclass clazz,
74                             jobject j_profile_android) {
75  return AndroidProfileOAuth2TokenService::GetForProfile(
76      env, clazz, j_profile_android);
77}
78
79void 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
90bool 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
102std::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
115std::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
127void 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
159OAuth2AccessTokenFetcher*
160AndroidProfileOAuth2TokenService::CreateAccessTokenFetcher(
161    const std::string& account_id,
162    net::URLRequestContextGetter* getter,
163    OAuth2AccessTokenConsumer* consumer) {
164  NOTREACHED();
165  return NULL;
166}
167
168void 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
186void 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
196void 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
232bool 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
302void 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
310void 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
325void 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
333void 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
348void AndroidProfileOAuth2TokenService::FireRefreshTokensLoadedFromJava(
349    JNIEnv* env,
350    jobject obj) {
351  AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded();
352}
353
354void 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
364void 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
373// Called from Java when fetching of an OAuth2 token is finished. The
374// |authToken| param is only valid when |result| is true.
375void OAuth2TokenFetched(JNIEnv* env, jclass clazz,
376    jstring authToken,
377    jboolean result,
378    jlong nativeCallback) {
379  std::string token = ConvertJavaStringToUTF8(env, authToken);
380  scoped_ptr<FetchOAuth2TokenCallback> heap_callback(
381      reinterpret_cast<FetchOAuth2TokenCallback*>(nativeCallback));
382  // Android does not provide enough information to know if the credentials are
383  // wrong, so assume any error is transient by using CONNECTION_FAILED.
384  GoogleServiceAuthError err(result ?
385                             GoogleServiceAuthError::NONE :
386                             GoogleServiceAuthError::CONNECTION_FAILED);
387  heap_callback->Run(err, token, base::Time());
388}
389
390// static
391bool AndroidProfileOAuth2TokenService::Register(JNIEnv* env) {
392  return RegisterNativesImpl(env);
393}
394