android_profile_oauth2_token_service.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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  ScopedBacthChange batch(this);
216
217  JNIEnv* env = AttachCurrentThread();
218  ScopedJavaLocalRef<jobjectArray> java_accounts(
219      base::android::ToJavaArrayOfStrings(env, curr_ids));
220  Java_OAuth2TokenService_saveStoredAccounts(
221      env, base::android::GetApplicationContext(), java_accounts.obj());
222
223  for (std::vector<std::string>::iterator it = refreshed_ids.begin();
224       it != refreshed_ids.end(); it++) {
225    FireRefreshTokenAvailable(*it);
226  }
227
228  for (std::vector<std::string>::iterator it = revoked_ids.begin();
229       it != revoked_ids.end(); it++) {
230    FireRefreshTokenRevoked(*it);
231  }
232}
233
234bool AndroidProfileOAuth2TokenService::ValidateAccounts(
235    const std::string& signed_in_account,
236    const std::vector<std::string>& prev_account_ids,
237    const std::vector<std::string>& curr_account_ids,
238    std::vector<std::string>& refreshed_ids,
239    std::vector<std::string>& revoked_ids,
240    bool force_notifications) {
241  if (std::find(curr_account_ids.begin(),
242                curr_account_ids.end(),
243                signed_in_account) != curr_account_ids.end()) {
244    // Test to see if an account is removed from the Android AccountManager.
245    // If so, invoke FireRefreshTokenRevoked to notify the reconcilor.
246    for (std::vector<std::string>::const_iterator it = prev_account_ids.begin();
247         it != prev_account_ids.end(); it++) {
248      if (*it == signed_in_account)
249        continue;
250
251      if (std::find(curr_account_ids.begin(),
252                    curr_account_ids.end(),
253                    *it) == curr_account_ids.end()) {
254        VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:"
255                << "revoked=" << *it;
256        revoked_ids.push_back(*it);
257      }
258    }
259
260    if (force_notifications ||
261        std::find(prev_account_ids.begin(), prev_account_ids.end(),
262                  signed_in_account) == prev_account_ids.end()) {
263      // Always fire the primary signed in account first.
264      VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:"
265              << "refreshed=" << signed_in_account;
266      refreshed_ids.push_back(signed_in_account);
267    }
268
269    for (std::vector<std::string>::const_iterator it = curr_account_ids.begin();
270         it != curr_account_ids.end(); it++) {
271      if (*it != signed_in_account) {
272        if (force_notifications ||
273            std::find(prev_account_ids.begin(),
274                      prev_account_ids.end(),
275                      *it) == prev_account_ids.end()) {
276          VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:"
277                  << "refreshed=" << *it;
278          refreshed_ids.push_back(*it);
279        }
280      }
281    }
282    return true;
283  } else {
284    // Currently signed in account does not any longer exist among accounts on
285    // system together with all other accounts.
286    if (std::find(prev_account_ids.begin(), prev_account_ids.end(),
287                  signed_in_account) != prev_account_ids.end()) {
288      VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:"
289              << "revoked=" << signed_in_account;
290      revoked_ids.push_back(signed_in_account);
291    }
292    for (std::vector<std::string>::const_iterator it = prev_account_ids.begin();
293         it != prev_account_ids.end(); it++) {
294      if (*it == signed_in_account)
295        continue;
296      VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:"
297              << "revoked=" << *it;
298      revoked_ids.push_back(*it);
299    }
300    return false;
301  }
302}
303
304void AndroidProfileOAuth2TokenService::FireRefreshTokenAvailableFromJava(
305    JNIEnv* env,
306    jobject obj,
307    const jstring account_name) {
308  std::string account_id = ConvertJavaStringToUTF8(env, account_name);
309  AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable(account_id);
310}
311
312void AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable(
313    const std::string& account_id) {
314  VLOG(1) << "AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable id="
315          << account_id;
316
317  // Notify native observers.
318  OAuth2TokenService::FireRefreshTokenAvailable(account_id);
319  // Notify Java observers.
320  JNIEnv* env = AttachCurrentThread();
321  ScopedJavaLocalRef<jstring> account_name =
322      ConvertUTF8ToJavaString(env, account_id);
323  Java_OAuth2TokenService_notifyRefreshTokenAvailable(
324      env, java_ref_.obj(), account_name.obj());
325}
326
327void AndroidProfileOAuth2TokenService::FireRefreshTokenRevokedFromJava(
328    JNIEnv* env,
329    jobject obj,
330    const jstring account_name) {
331  std::string account_id = ConvertJavaStringToUTF8(env, account_name);
332  AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked(account_id);
333}
334
335void AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked(
336    const std::string& account_id) {
337  VLOG(1) << "AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked id="
338          << account_id;
339
340  // Notify native observers.
341  OAuth2TokenService::FireRefreshTokenRevoked(account_id);
342  // Notify Java observers.
343  JNIEnv* env = AttachCurrentThread();
344  ScopedJavaLocalRef<jstring> account_name =
345      ConvertUTF8ToJavaString(env, account_id);
346  Java_OAuth2TokenService_notifyRefreshTokenRevoked(
347      env, java_ref_.obj(), account_name.obj());
348}
349
350void AndroidProfileOAuth2TokenService::FireRefreshTokensLoadedFromJava(
351    JNIEnv* env,
352    jobject obj) {
353  AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded();
354}
355
356void AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded() {
357  VLOG(1) << "AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded";
358  // Notify native observers.
359  OAuth2TokenService::FireRefreshTokensLoaded();
360  // Notify Java observers.
361  JNIEnv* env = AttachCurrentThread();
362  Java_OAuth2TokenService_notifyRefreshTokensLoaded(
363      env, java_ref_.obj());
364}
365
366void AndroidProfileOAuth2TokenService::RevokeAllCredentials() {
367  VLOG(1) << "AndroidProfileOAuth2TokenService::RevokeAllCredentials";
368  ScopedBacthChange batch(this);
369  std::vector<std::string> accounts = GetAccounts();
370  for (std::vector<std::string>::iterator it = accounts.begin();
371       it != accounts.end(); it++) {
372    FireRefreshTokenRevoked(*it);
373  }
374
375  // Clear everything on the Java side as well.
376  std::vector<std::string> empty;
377  JNIEnv* env = AttachCurrentThread();
378  ScopedJavaLocalRef<jobjectArray> java_accounts(
379      base::android::ToJavaArrayOfStrings(env, empty));
380  Java_OAuth2TokenService_saveStoredAccounts(
381      env, base::android::GetApplicationContext(), java_accounts.obj());
382}
383
384// Called from Java when fetching of an OAuth2 token is finished. The
385// |authToken| param is only valid when |result| is true.
386void OAuth2TokenFetched(JNIEnv* env, jclass clazz,
387    jstring authToken,
388    jboolean result,
389    jlong nativeCallback) {
390  std::string token = ConvertJavaStringToUTF8(env, authToken);
391  scoped_ptr<FetchOAuth2TokenCallback> heap_callback(
392      reinterpret_cast<FetchOAuth2TokenCallback*>(nativeCallback));
393  // Android does not provide enough information to know if the credentials are
394  // wrong, so assume any error is transient by using CONNECTION_FAILED.
395  GoogleServiceAuthError err(result ?
396                             GoogleServiceAuthError::NONE :
397                             GoogleServiceAuthError::CONNECTION_FAILED);
398  heap_callback->Run(err, token, base::Time());
399}
400
401// static
402bool AndroidProfileOAuth2TokenService::Register(JNIEnv* env) {
403  return RegisterNativesImpl(env);
404}
405