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 "google_apis/gaia/oauth2_access_token_fetcher.h"
17#include "jni/OAuth2TokenService_jni.h"
18
19using base::android::AttachCurrentThread;
20using base::android::ConvertJavaStringToUTF8;
21using base::android::ConvertUTF8ToJavaString;
22using base::android::ScopedJavaLocalRef;
23using content::BrowserThread;
24
25namespace {
26
27// Callback from FetchOAuth2TokenWithUsername().
28// Arguments:
29// - the error, or NONE if the token fetch was successful.
30// - the OAuth2 access token.
31// - the expiry time of the token (may be null, indicating that the expiry
32//   time is unknown.
33typedef base::Callback<void(
34    const GoogleServiceAuthError&, const std::string&, const base::Time&)>
35        FetchOAuth2TokenCallback;
36
37class AndroidAccessTokenFetcher : public OAuth2AccessTokenFetcher {
38 public:
39  AndroidAccessTokenFetcher(OAuth2AccessTokenConsumer* consumer,
40                            const std::string& account_id);
41  virtual ~AndroidAccessTokenFetcher();
42
43  // Overrides from OAuth2AccessTokenFetcher:
44  virtual void Start(const std::string& client_id,
45                     const std::string& client_secret,
46                     const std::vector<std::string>& scopes) OVERRIDE;
47  virtual void CancelRequest() OVERRIDE;
48
49  // Handles an access token response.
50  void OnAccessTokenResponse(const GoogleServiceAuthError& error,
51                             const std::string& access_token,
52                             const base::Time& expiration_time);
53
54 private:
55  std::string CombineScopes(const std::vector<std::string>& scopes);
56
57  base::WeakPtrFactory<AndroidAccessTokenFetcher> weak_factory_;
58  std::string account_id_;
59  bool request_was_cancelled_;
60
61  DISALLOW_COPY_AND_ASSIGN(AndroidAccessTokenFetcher);
62};
63
64AndroidAccessTokenFetcher::AndroidAccessTokenFetcher(
65    OAuth2AccessTokenConsumer* consumer,
66    const std::string& account_id)
67    : OAuth2AccessTokenFetcher(consumer),
68      weak_factory_(this),
69      account_id_(account_id),
70      request_was_cancelled_(false) {
71}
72
73AndroidAccessTokenFetcher::~AndroidAccessTokenFetcher() {}
74
75void AndroidAccessTokenFetcher::Start(const std::string& client_id,
76                                      const std::string& client_secret,
77                                      const std::vector<std::string>& scopes) {
78  JNIEnv* env = AttachCurrentThread();
79  std::string scope = CombineScopes(scopes);
80  ScopedJavaLocalRef<jstring> j_username =
81      ConvertUTF8ToJavaString(env, account_id_);
82  ScopedJavaLocalRef<jstring> j_scope =
83      ConvertUTF8ToJavaString(env, scope);
84  scoped_ptr<FetchOAuth2TokenCallback> heap_callback(
85      new FetchOAuth2TokenCallback(
86          base::Bind(&AndroidAccessTokenFetcher::OnAccessTokenResponse,
87                     weak_factory_.GetWeakPtr())));
88
89  // Call into Java to get a new token.
90  Java_OAuth2TokenService_getOAuth2AuthToken(
91      env, base::android::GetApplicationContext(),
92      j_username.obj(),
93      j_scope.obj(),
94      reinterpret_cast<intptr_t>(heap_callback.release()));
95}
96
97void AndroidAccessTokenFetcher::CancelRequest() {
98  request_was_cancelled_ = true;
99}
100
101void AndroidAccessTokenFetcher::OnAccessTokenResponse(
102    const GoogleServiceAuthError& error,
103    const std::string& access_token,
104    const base::Time& expiration_time) {
105  if (request_was_cancelled_) {
106    // Ignore the callback if the request was cancelled.
107    return;
108  }
109  if (error.state() == GoogleServiceAuthError::NONE) {
110    FireOnGetTokenSuccess(access_token, expiration_time);
111  } else {
112    FireOnGetTokenFailure(error);
113  }
114}
115
116// static
117std::string AndroidAccessTokenFetcher::CombineScopes(
118    const std::vector<std::string>& scopes) {
119  // The Android AccountManager supports multiple scopes separated by a space:
120  // https://code.google.com/p/google-api-java-client/wiki/OAuth2#Android
121  std::string scope;
122  for (std::vector<std::string>::const_iterator it = scopes.begin();
123       it != scopes.end(); ++it) {
124    if (!scope.empty())
125      scope += " ";
126    scope += *it;
127  }
128  return scope;
129}
130
131}  // namespace
132
133bool AndroidProfileOAuth2TokenService::is_testing_profile_ = false;
134
135AndroidProfileOAuth2TokenService::AndroidProfileOAuth2TokenService() {
136  VLOG(1) << "AndroidProfileOAuth2TokenService::ctor";
137  JNIEnv* env = AttachCurrentThread();
138  base::android::ScopedJavaLocalRef<jobject> local_java_ref =
139      Java_OAuth2TokenService_create(env, reinterpret_cast<intptr_t>(this));
140  java_ref_.Reset(env, local_java_ref.obj());
141}
142
143AndroidProfileOAuth2TokenService::~AndroidProfileOAuth2TokenService() {}
144
145// static
146jobject AndroidProfileOAuth2TokenService::GetForProfile(
147    JNIEnv* env, jclass clazz, jobject j_profile_android) {
148  Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile_android);
149  AndroidProfileOAuth2TokenService* service =
150      ProfileOAuth2TokenServiceFactory::GetPlatformSpecificForProfile(profile);
151  return service->java_ref_.obj();
152}
153
154static jobject GetForProfile(JNIEnv* env,
155                             jclass clazz,
156                             jobject j_profile_android) {
157  return AndroidProfileOAuth2TokenService::GetForProfile(
158      env, clazz, j_profile_android);
159}
160
161void AndroidProfileOAuth2TokenService::Initialize(SigninClient* client) {
162  VLOG(1) << "AndroidProfileOAuth2TokenService::Initialize";
163  ProfileOAuth2TokenService::Initialize(client);
164
165  if (!is_testing_profile_) {
166    Java_OAuth2TokenService_validateAccounts(
167        AttachCurrentThread(), java_ref_.obj(),
168        base::android::GetApplicationContext(), JNI_TRUE);
169  }
170}
171
172bool AndroidProfileOAuth2TokenService::RefreshTokenIsAvailable(
173    const std::string& account_id) const {
174  JNIEnv* env = AttachCurrentThread();
175  ScopedJavaLocalRef<jstring> j_account_id =
176      ConvertUTF8ToJavaString(env, account_id);
177  jboolean refresh_token_is_available =
178      Java_OAuth2TokenService_hasOAuth2RefreshToken(
179          env, base::android::GetApplicationContext(),
180          j_account_id.obj());
181  return refresh_token_is_available == JNI_TRUE;
182}
183
184void AndroidProfileOAuth2TokenService::UpdateAuthError(
185    const std::string& account_id,
186    const GoogleServiceAuthError& error) {
187  // TODO(rogerta): do we need to update anything, or does the system handle it?
188}
189
190std::vector<std::string> AndroidProfileOAuth2TokenService::GetAccounts() {
191  std::vector<std::string> accounts;
192  JNIEnv* env = AttachCurrentThread();
193  ScopedJavaLocalRef<jobjectArray> j_accounts =
194      Java_OAuth2TokenService_getAccounts(
195          env, base::android::GetApplicationContext());
196  // TODO(fgorski): We may decide to filter out some of the accounts.
197  base::android::AppendJavaStringArrayToStringVector(env,
198                                                     j_accounts.obj(),
199                                                     &accounts);
200  return accounts;
201}
202
203std::vector<std::string> AndroidProfileOAuth2TokenService::GetSystemAccounts() {
204  std::vector<std::string> accounts;
205  JNIEnv* env = AttachCurrentThread();
206  ScopedJavaLocalRef<jobjectArray> j_accounts =
207      Java_OAuth2TokenService_getSystemAccounts(
208          env, base::android::GetApplicationContext());
209  base::android::AppendJavaStringArrayToStringVector(env,
210                                                     j_accounts.obj(),
211                                                     &accounts);
212  return accounts;
213}
214
215OAuth2AccessTokenFetcher*
216AndroidProfileOAuth2TokenService::CreateAccessTokenFetcher(
217    const std::string& account_id,
218    net::URLRequestContextGetter* getter,
219    OAuth2AccessTokenConsumer* consumer) {
220  DCHECK(!account_id.empty());
221  return new AndroidAccessTokenFetcher(consumer, account_id);
222}
223
224void AndroidProfileOAuth2TokenService::InvalidateOAuth2Token(
225    const std::string& account_id,
226    const std::string& client_id,
227    const ScopeSet& scopes,
228    const std::string& access_token) {
229  OAuth2TokenService::InvalidateOAuth2Token(account_id,
230                                            client_id,
231                                            scopes,
232                                            access_token);
233
234  JNIEnv* env = AttachCurrentThread();
235  ScopedJavaLocalRef<jstring> j_access_token =
236      ConvertUTF8ToJavaString(env, access_token);
237  Java_OAuth2TokenService_invalidateOAuth2AuthToken(
238      env, base::android::GetApplicationContext(),
239      j_access_token.obj());
240}
241
242void AndroidProfileOAuth2TokenService::ValidateAccounts(
243    JNIEnv* env,
244    jobject obj,
245    jstring j_current_acc,
246    jboolean j_force_notifications) {
247  VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts from java";
248  std::string signed_in_account = ConvertJavaStringToUTF8(env, j_current_acc);
249  ValidateAccounts(signed_in_account, j_force_notifications != JNI_FALSE);
250}
251
252void AndroidProfileOAuth2TokenService::ValidateAccounts(
253    const std::string& signed_in_account,
254    bool force_notifications) {
255  std::vector<std::string> prev_ids = GetAccounts();
256  std::vector<std::string> curr_ids = GetSystemAccounts();
257  std::vector<std::string> refreshed_ids;
258  std::vector<std::string> revoked_ids;
259
260  VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:"
261          << " sigined_in_account=" << signed_in_account
262          << " prev_ids=" << prev_ids.size()
263          << " curr_ids=" << curr_ids.size()
264          << " force=" << (force_notifications ? "true" : "false");
265
266  if (!ValidateAccounts(signed_in_account, prev_ids, curr_ids, refreshed_ids,
267                        revoked_ids, force_notifications)) {
268    curr_ids.clear();
269  }
270
271  ScopedBacthChange batch(this);
272
273  JNIEnv* env = AttachCurrentThread();
274  ScopedJavaLocalRef<jobjectArray> java_accounts(
275      base::android::ToJavaArrayOfStrings(env, curr_ids));
276  Java_OAuth2TokenService_saveStoredAccounts(
277      env, base::android::GetApplicationContext(), java_accounts.obj());
278
279  for (std::vector<std::string>::iterator it = refreshed_ids.begin();
280       it != refreshed_ids.end(); it++) {
281    FireRefreshTokenAvailable(*it);
282  }
283
284  for (std::vector<std::string>::iterator it = revoked_ids.begin();
285       it != revoked_ids.end(); it++) {
286    FireRefreshTokenRevoked(*it);
287  }
288}
289
290bool AndroidProfileOAuth2TokenService::ValidateAccounts(
291    const std::string& signed_in_account,
292    const std::vector<std::string>& prev_account_ids,
293    const std::vector<std::string>& curr_account_ids,
294    std::vector<std::string>& refreshed_ids,
295    std::vector<std::string>& revoked_ids,
296    bool force_notifications) {
297  if (std::find(curr_account_ids.begin(),
298                curr_account_ids.end(),
299                signed_in_account) != curr_account_ids.end()) {
300    // Test to see if an account is removed from the Android AccountManager.
301    // If so, invoke FireRefreshTokenRevoked to notify the reconcilor.
302    for (std::vector<std::string>::const_iterator it = prev_account_ids.begin();
303         it != prev_account_ids.end(); it++) {
304      if (*it == signed_in_account)
305        continue;
306
307      if (std::find(curr_account_ids.begin(),
308                    curr_account_ids.end(),
309                    *it) == curr_account_ids.end()) {
310        VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:"
311                << "revoked=" << *it;
312        revoked_ids.push_back(*it);
313      }
314    }
315
316    if (force_notifications ||
317        std::find(prev_account_ids.begin(), prev_account_ids.end(),
318                  signed_in_account) == prev_account_ids.end()) {
319      // Always fire the primary signed in account first.
320      VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:"
321              << "refreshed=" << signed_in_account;
322      refreshed_ids.push_back(signed_in_account);
323    }
324
325    for (std::vector<std::string>::const_iterator it = curr_account_ids.begin();
326         it != curr_account_ids.end(); it++) {
327      if (*it != signed_in_account) {
328        if (force_notifications ||
329            std::find(prev_account_ids.begin(),
330                      prev_account_ids.end(),
331                      *it) == prev_account_ids.end()) {
332          VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:"
333                  << "refreshed=" << *it;
334          refreshed_ids.push_back(*it);
335        }
336      }
337    }
338    return true;
339  } else {
340    // Currently signed in account does not any longer exist among accounts on
341    // system together with all other accounts.
342    if (std::find(prev_account_ids.begin(), prev_account_ids.end(),
343                  signed_in_account) != prev_account_ids.end()) {
344      VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:"
345              << "revoked=" << signed_in_account;
346      revoked_ids.push_back(signed_in_account);
347    }
348    for (std::vector<std::string>::const_iterator it = prev_account_ids.begin();
349         it != prev_account_ids.end(); it++) {
350      if (*it == signed_in_account)
351        continue;
352      VLOG(1) << "AndroidProfileOAuth2TokenService::ValidateAccounts:"
353              << "revoked=" << *it;
354      revoked_ids.push_back(*it);
355    }
356    return false;
357  }
358}
359
360void AndroidProfileOAuth2TokenService::FireRefreshTokenAvailableFromJava(
361    JNIEnv* env,
362    jobject obj,
363    const jstring account_name) {
364  std::string account_id = ConvertJavaStringToUTF8(env, account_name);
365  AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable(account_id);
366}
367
368void AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable(
369    const std::string& account_id) {
370  VLOG(1) << "AndroidProfileOAuth2TokenService::FireRefreshTokenAvailable id="
371          << account_id;
372
373  // Notify native observers.
374  OAuth2TokenService::FireRefreshTokenAvailable(account_id);
375  // Notify Java observers.
376  JNIEnv* env = AttachCurrentThread();
377  ScopedJavaLocalRef<jstring> account_name =
378      ConvertUTF8ToJavaString(env, account_id);
379  Java_OAuth2TokenService_notifyRefreshTokenAvailable(
380      env, java_ref_.obj(), account_name.obj());
381}
382
383void AndroidProfileOAuth2TokenService::FireRefreshTokenRevokedFromJava(
384    JNIEnv* env,
385    jobject obj,
386    const jstring account_name) {
387  std::string account_id = ConvertJavaStringToUTF8(env, account_name);
388  AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked(account_id);
389}
390
391void AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked(
392    const std::string& account_id) {
393  VLOG(1) << "AndroidProfileOAuth2TokenService::FireRefreshTokenRevoked id="
394          << account_id;
395
396  // Notify native observers.
397  OAuth2TokenService::FireRefreshTokenRevoked(account_id);
398  // Notify Java observers.
399  JNIEnv* env = AttachCurrentThread();
400  ScopedJavaLocalRef<jstring> account_name =
401      ConvertUTF8ToJavaString(env, account_id);
402  Java_OAuth2TokenService_notifyRefreshTokenRevoked(
403      env, java_ref_.obj(), account_name.obj());
404}
405
406void AndroidProfileOAuth2TokenService::FireRefreshTokensLoadedFromJava(
407    JNIEnv* env,
408    jobject obj) {
409  AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded();
410}
411
412void AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded() {
413  VLOG(1) << "AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded";
414  // Notify native observers.
415  OAuth2TokenService::FireRefreshTokensLoaded();
416  // Notify Java observers.
417  JNIEnv* env = AttachCurrentThread();
418  Java_OAuth2TokenService_notifyRefreshTokensLoaded(
419      env, java_ref_.obj());
420}
421
422void AndroidProfileOAuth2TokenService::RevokeAllCredentials() {
423  VLOG(1) << "AndroidProfileOAuth2TokenService::RevokeAllCredentials";
424  ScopedBacthChange batch(this);
425  std::vector<std::string> accounts = GetAccounts();
426  for (std::vector<std::string>::iterator it = accounts.begin();
427       it != accounts.end(); it++) {
428    FireRefreshTokenRevoked(*it);
429  }
430
431  // Clear everything on the Java side as well.
432  std::vector<std::string> empty;
433  JNIEnv* env = AttachCurrentThread();
434  ScopedJavaLocalRef<jobjectArray> java_accounts(
435      base::android::ToJavaArrayOfStrings(env, empty));
436  Java_OAuth2TokenService_saveStoredAccounts(
437      env, base::android::GetApplicationContext(), java_accounts.obj());
438}
439
440// Called from Java when fetching of an OAuth2 token is finished. The
441// |authToken| param is only valid when |result| is true.
442void OAuth2TokenFetched(
443    JNIEnv* env,
444    jclass clazz,
445    jstring authToken,
446    jboolean result,
447    jlong nativeCallback) {
448  std::string token = ConvertJavaStringToUTF8(env, authToken);
449  scoped_ptr<FetchOAuth2TokenCallback> heap_callback(
450      reinterpret_cast<FetchOAuth2TokenCallback*>(nativeCallback));
451  // Android does not provide enough information to know if the credentials are
452  // wrong, so assume any error is transient by using CONNECTION_FAILED.
453  GoogleServiceAuthError err(result ?
454                             GoogleServiceAuthError::NONE :
455                             GoogleServiceAuthError::CONNECTION_FAILED);
456  heap_callback->Run(err, token, base::Time());
457}
458
459// static
460bool AndroidProfileOAuth2TokenService::Register(JNIEnv* env) {
461  return RegisterNativesImpl(env);
462}
463