From 0ed544c4f79e6c1bab3355458d5d9a36c83a7b70 Mon Sep 17 00:00:00 2001 From: hoi4 Date: Fri, 28 Jan 2022 16:04:36 +0100 Subject: [PATCH] refactor: added accessToken to Android implementation (#173) Co-authored-by: Philipp Heuer --- android/src/main/AndroidManifest.xml | 2 + .../capacitor/GoogleAuth/GoogleAuth.java | 128 +++++++++++++++--- 2 files changed, 112 insertions(+), 18 deletions(-) diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index d170179..e63fa19 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,5 +1,7 @@ + + \ No newline at end of file diff --git a/android/src/main/java/com/codetrixstudio/capacitor/GoogleAuth/GoogleAuth.java b/android/src/main/java/com/codetrixstudio/capacitor/GoogleAuth/GoogleAuth.java index e099842..2002b86 100644 --- a/android/src/main/java/com/codetrixstudio/capacitor/GoogleAuth/GoogleAuth.java +++ b/android/src/main/java/com/codetrixstudio/capacitor/GoogleAuth/GoogleAuth.java @@ -1,6 +1,12 @@ package com.codetrixstudio.capacitor.GoogleAuth; +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + import androidx.activity.result.ActivityResult; import com.codetrixstudio.capacitor.GoogleAuth.capacitorgoogleauth.R; @@ -18,11 +24,27 @@ import com.google.android.gms.common.api.Scope; import com.google.android.gms.tasks.Task; -import org.json.JSONArray; import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; @CapacitorPlugin() public class GoogleAuth extends Plugin { + private final static String VERIFY_TOKEN_URL = "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token="; + private final static String FIELD_TOKEN_EXPIRES_IN = "expires_in"; + private final static String FIELD_ACCESS_TOKEN = "accessToken"; + private final static String FIELD_TOKEN_EXPIRES = "expires"; + public static final int KAssumeStaleTokenSec = 60; + private GoogleSignInClient googleSignInClient; @Override @@ -55,7 +77,6 @@ public void load() { @PluginMethod() public void signIn(PluginCall call) { - saveCall(call); Intent signInIntent = googleSignInClient.getSignInIntent(); startActivityForResult(call, signInIntent, "signInResult"); } @@ -69,22 +90,36 @@ protected void signInResult(PluginCall call, ActivityResult result) { try { GoogleSignInAccount account = completedTask.getResult(ApiException.class); - JSObject authentication = new JSObject(); - authentication.put("idToken", account.getIdToken()); - - JSObject user = new JSObject(); - user.put("serverAuthCode", account.getServerAuthCode()); - user.put("idToken", account.getIdToken()); - user.put("authentication", authentication); - - user.put("displayName", account.getDisplayName()); - user.put("email", account.getEmail()); - user.put("familyName", account.getFamilyName()); - user.put("givenName", account.getGivenName()); - user.put("id", account.getId()); - user.put("imageUrl", account.getPhotoUrl()); - - call.resolve(user); + // The accessToken is retrieved by executing a network request against the Google API, so it needs to run in a thread + ExecutorService executor = Executors.newSingleThreadExecutor(); + executor.execute(() -> { + try { + JSONObject accessTokenObject = getAuthToken(account.getAccount(), true); + + JSObject authentication = new JSObject(); + authentication.put("idToken", account.getIdToken()); + authentication.put(FIELD_ACCESS_TOKEN, accessTokenObject.get(FIELD_ACCESS_TOKEN)); + authentication.put(FIELD_TOKEN_EXPIRES, accessTokenObject.get(FIELD_TOKEN_EXPIRES)); + authentication.put(FIELD_TOKEN_EXPIRES_IN, accessTokenObject.get(FIELD_TOKEN_EXPIRES_IN)); + + JSObject user = new JSObject(); + user.put("serverAuthCode", account.getServerAuthCode()); + user.put("idToken", account.getIdToken()); + user.put("authentication", authentication); + + user.put("displayName", account.getDisplayName()); + user.put("email", account.getEmail()); + user.put("familyName", account.getFamilyName()); + user.put("givenName", account.getGivenName()); + user.put("id", account.getId()); + user.put("imageUrl", account.getPhotoUrl()); + + call.resolve(user); + } catch (Exception e) { + e.printStackTrace(); + call.reject("Something went wrong while retrieving access token", e); + } + }); } catch (ApiException e) { call.reject("Something went wrong", e); } @@ -106,4 +141,61 @@ public void initialize(final PluginCall call) { call.resolve(); } + // Logic to retrieve accessToken, see https://github.com/EddyVerbruggen/cordova-plugin-googleplus/blob/master/src/android/GooglePlus.java + private JSONObject getAuthToken(Account account, boolean retry) throws Exception { + AccountManager manager = AccountManager.get(getContext()); + AccountManagerFuture future = manager.getAuthToken(account, "oauth2:profile email", null, false, null, null); + Bundle bundle = future.getResult(); + String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN); + try { + return verifyToken(authToken); + } catch (IOException e) { + if (retry) { + manager.invalidateAuthToken("com.google", authToken); + return getAuthToken(account, false); + } else { + throw e; + } + } + } + + private JSONObject verifyToken(String authToken) throws IOException, JSONException { + URL url = new URL(VERIFY_TOKEN_URL + authToken); + HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); + urlConnection.setInstanceFollowRedirects(true); + String stringResponse = fromStream(new BufferedInputStream(urlConnection.getInputStream())); + /* expecting: + { + "issued_to": "xxxxxx-xxxxxxxxxxxxxxx.apps.googleusercontent.com", + "audience": "xxxxxx-xxxxxxxxxxxxxxxx.apps.googleusercontent.com", + "user_id": "xxxxxxxxxxxxxxxxxxxx", + "scope": "https://www.googleapis.com/auth/userinfo.email openid https://www.googleapis.com/auth/userinfo.profile", + "expires_in": 3220, + "email": "xxxxxxx@xxxxx.com", + "verified_email": true, + "access_type": "online" + } + */ + + Log.d("AuthenticatedBackend", "token: " + authToken + ", verification: " + stringResponse); + JSONObject jsonResponse = new JSONObject(stringResponse); + int expires_in = jsonResponse.getInt(FIELD_TOKEN_EXPIRES_IN); + if (expires_in < KAssumeStaleTokenSec) { + throw new IOException("Auth token soon expiring."); + } + jsonResponse.put(FIELD_ACCESS_TOKEN, authToken); + jsonResponse.put(FIELD_TOKEN_EXPIRES, expires_in + (System.currentTimeMillis() / 1000)); + return jsonResponse; + } + + private static String fromStream(InputStream is) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + reader.close(); + return sb.toString(); + } }