From 04d21c8769f78b47dfa3c630b23d5550d8d3f78d Mon Sep 17 00:00:00 2001 From: CounterFire2023 <136581895+CounterFire2023@users.noreply.github.com> Date: Wed, 6 Sep 2023 14:54:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96appauth=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/cege/games/release/MainActivity.java | 372 ++--------------- .../games/release/appauth/AppAuthSvr.java | 394 ++++++++++++++++++ 2 files changed, 423 insertions(+), 343 deletions(-) create mode 100644 app/src/com/cege/games/release/appauth/AppAuthSvr.java diff --git a/app/src/com/cege/games/release/MainActivity.java b/app/src/com/cege/games/release/MainActivity.java index bd4bf49..2a6bb10 100644 --- a/app/src/com/cege/games/release/MainActivity.java +++ b/app/src/com/cege/games/release/MainActivity.java @@ -11,15 +11,10 @@ import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; -import android.text.TextUtils; import android.util.Log; import android.view.Window; -import androidx.annotation.MainThread; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; -import androidx.browser.customtabs.CustomTabsIntent; import androidx.core.app.ActivityOptionsCompat; import com.bytedance.sdk.open.tiktok.TikTokOpenApiFactory; @@ -30,6 +25,7 @@ import com.bytedance.sdk.open.tiktok.base.VideoObject; import com.bytedance.sdk.open.tiktok.share.Share; import com.cege.games.release.activity.CustomCaptureActivity; import com.cege.games.release.activity.WebPageActivity; +import com.cege.games.release.appauth.AppAuthSvr; import com.cege.games.release.apple.AppleLoginActivity; import com.cege.games.release.wallet.WalletUtil; import com.facebook.AccessToken; @@ -54,8 +50,6 @@ import com.google.android.gms.tasks.Task; import com.google.api.services.drive.DriveScopes; import com.google.firebase.analytics.FirebaseAnalytics; import com.jc.jcfw.JcSDK; -import com.jc.jcfw.appauth.AuthStateManager; -import com.jc.jcfw.appauth.JConfiguration; import com.jc.jcfw.google.PayClient; import com.jc.jcfw.util.JsonUtils; import com.king.zxing.CameraScan; @@ -63,22 +57,7 @@ import com.king.zxing.util.CodeUtils; import com.king.zxing.util.LogUtils; import com.unity3d.player.UnityPlayerActivity; -import net.openid.appauth.AppAuthConfiguration; import net.openid.appauth.AuthState; -import net.openid.appauth.AuthorizationException; -import net.openid.appauth.AuthorizationRequest; -import net.openid.appauth.AuthorizationResponse; -import net.openid.appauth.AuthorizationService; -import net.openid.appauth.AuthorizationServiceConfiguration; -import net.openid.appauth.ClientAuthentication; -import net.openid.appauth.ClientSecretBasic; -import net.openid.appauth.RegistrationRequest; -import net.openid.appauth.RegistrationResponse; -import net.openid.appauth.ResponseTypeValues; -import net.openid.appauth.TokenRequest; -import net.openid.appauth.TokenResponse; -import net.openid.appauth.browser.AnyBrowserMatcher; -import net.openid.appauth.browser.BrowserMatcher; import org.cocos2dx.lib.Cocos2dxHelper; import org.cocos2dx.lib.CocosJSHelper; @@ -86,13 +65,9 @@ import org.json.JSONException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; import pub.devrel.easypermissions.AfterPermissionGranted; import pub.devrel.easypermissions.EasyPermissions; @@ -126,14 +101,9 @@ public class MainActivity extends UnityPlayerActivity private String mFunID; // AppAuth - private AuthorizationService mAuthService; - private AuthStateManager mAuthStateManager; - private JConfiguration mConfiguration; + private AppAuthSvr mAppAuthSvr; private ExecutorService mExecutor; - private final AtomicReference mClientId = new AtomicReference<>(); - private final AtomicReference mAuthRequest = new AtomicReference<>(); - private final AtomicReference mAuthIntent = new AtomicReference<>(); - private CountDownLatch mAuthIntentLatch = new CountDownLatch(1); + private TikTokOpenApi tiktokOpenApi; @@ -141,9 +111,6 @@ public class MainActivity extends UnityPlayerActivity return mFunID; } - @NonNull - private final BrowserMatcher mBrowserMatcher = AnyBrowserMatcher.INSTANCE; - private GoogleSignInClient mGoogleSignInClient; // facebook login private CallbackManager mCallbackManager; @@ -152,7 +119,7 @@ public class MainActivity extends UnityPlayerActivity private AppEventsLogger fbLogger; private WalletUtil mWalletUtil; - private Consumer loginCbAction = null; + public boolean isGooglePlayServicesAvailable() { return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS; @@ -180,10 +147,9 @@ public class MainActivity extends UnityPlayerActivity // end of google sign // begin of google oauth sign + mAppAuthSvr = new AppAuthSvr(this); mExecutor = Executors.newSingleThreadExecutor(); - mAuthStateManager = AuthStateManager.getInstance(this); - mConfiguration = JConfiguration.getInstance(this); - mExecutor.submit(this::initializeAppAuth); + mAppAuthSvr.init(mExecutor); // end of google oauth sign // begin of facebook login @@ -217,22 +183,7 @@ public class MainActivity extends UnityPlayerActivity parsePhoto(data); break; case RC_AUTH: - AuthorizationResponse response = AuthorizationResponse.fromIntent(data); - AuthorizationException ex = AuthorizationException.fromIntent(data); - if (response != null || ex != null) { - mAuthStateManager.updateAfterAuthorization(response, ex); - } - if (response != null && response.authorizationCode != null) { - // authorization code exchange is required - mAuthStateManager.updateAfterAuthorization(response, ex); - exchangeAuthorizationCode(response); - } else if (ex != null) { - Log.i(TAG, "Authorization flow failed: " + ex.getMessage()); - JcSDK.nativeCb(mFunID, "Authorization flow failed: " + ex.getMessage(), null); - } else { - Log.i(TAG, "No authorization state retained - reauthorization required"); - JcSDK.nativeCb(mFunID, "No authorization state retained - reauthorization required", null); - } + mAppAuthSvr.parseLoginResult(data); break; case RC_SIGN_IN: handleSignInResult(GoogleSignIn.getSignedInAccountFromIntent(data)); @@ -427,18 +378,20 @@ public class MainActivity extends UnityPlayerActivity } } else { Log.d(TAG, "no gms, use app auth"); - AuthState state = mAuthStateManager.getCurrent(); + AuthState state = mAppAuthSvr.getCurrentState(); if (state.isAuthorized()) { if (state.getNeedsTokenRefresh()) { Log.w(TAG, "need refresh accessToken"); - performTokenRequest(state.createTokenRefreshRequest(), this::handleCodeExchangeResponse); + mAppAuthSvr.refreshToken(funId, null); } else { Log.w(TAG, "already login, accessToken not expired, id token:: " + state.getIdToken()); JcSDK.nativeCb(this.mFunID, null, state.getIdToken()); } } else { Log.w(TAG, "not login"); - mExecutor.submit(this::doAuth); + mExecutor.submit(() -> { + mAppAuthSvr.doAuth(funId, null); + }); } } } @@ -466,271 +419,6 @@ public class MainActivity extends UnityPlayerActivity } } - // begin of AppAuth - /** - * Initializes the authorization service configuration if necessary, either from - * the local - * static values or by retrieving an OpenID discovery document. - */ - @WorkerThread - private void initializeAppAuth() { - Log.i(TAG, "Initializing AppAuth"); - recreateAuthorizationService(); - - if (mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration() != null) { - // configuration is already created, skip to client initialization - Log.i(TAG, "auth config already established"); - initializeClient(); - return; - } - - // if we are not using discovery, build the authorization service configuration - // directly - // from the static configuration values. - if (mConfiguration.getDiscoveryUri() == null) { - Log.i(TAG, "Creating auth config from res/raw/auth_config.json"); - AuthorizationServiceConfiguration config = new AuthorizationServiceConfiguration( - mConfiguration.getAuthEndpointUri(), - mConfiguration.getTokenEndpointUri(), - mConfiguration.getRegistrationEndpointUri(), - mConfiguration.getEndSessionEndpoint()); - - mAuthStateManager.replace(new AuthState(config)); - initializeClient(); - return; - } - - // WrongThread inference is incorrect for lambdas - // noinspection WrongThread - Log.i(TAG, "Retrieving OpenID discovery doc"); - AuthorizationServiceConfiguration.fetchFromUrl( - mConfiguration.getDiscoveryUri(), - this::handleConfigurationRetrievalResult, - mConfiguration.getConnectionBuilder()); - } - - @MainThread - private void handleConfigurationRetrievalResult( - AuthorizationServiceConfiguration config, - AuthorizationException ex) { - if (config == null) { - Log.i(TAG, "Failed to retrieve discovery document", ex); - return; - } - - Log.i(TAG, "Discovery document retrieved"); - mAuthStateManager.replace(new AuthState(config)); - mExecutor.submit(this::initializeClient); - } - - private void recreateAuthorizationService() { - if (mAuthService != null) { - Log.i(TAG, "Discarding existing AuthService instance"); - mAuthService.dispose(); - } - mAuthService = createAuthorizationService(); - mAuthRequest.set(null); - mAuthIntent.set(null); - } - - private AuthorizationService createAuthorizationService() { - Log.i(TAG, "Creating authorization service"); - AppAuthConfiguration.Builder builder = new AppAuthConfiguration.Builder(); - builder.setBrowserMatcher(mBrowserMatcher); - builder.setConnectionBuilder(mConfiguration.getConnectionBuilder()); - - return new AuthorizationService(this, builder.build()); - } - - private void createAuthRequest(@Nullable String loginHint) { - Log.i(TAG, "Creating auth request for login hint: " + loginHint); - AuthorizationRequest.Builder authRequestBuilder = new AuthorizationRequest.Builder( - mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration(), - mClientId.get(), - ResponseTypeValues.CODE, - mConfiguration.getRedirectUri()) - // apple need `form_post` when authorization_scope has `name email ` - // .setResponseMode("form_post") - // .setResponseType("code id_token") - .setScope(mConfiguration.getScope()); - if (!TextUtils.isEmpty(loginHint)) { - authRequestBuilder.setLoginHint(loginHint); - } - mAuthRequest.set(authRequestBuilder.build()); - } - - private void warmUpBrowser() { - mAuthIntentLatch = new CountDownLatch(1); - mExecutor.execute(() -> { - Log.i(TAG, "Warming up browser instance for auth request"); - Uri uri = mAuthRequest.get().toUri(); - Log.d(TAG, "URI: " + uri); - CustomTabsIntent.Builder intentBuilder = mAuthService.createCustomTabsIntentBuilder(uri); - mAuthIntent.set(intentBuilder.build()); - mAuthIntentLatch.countDown(); - }); - } - - @MainThread - private void initializeAuthRequest() { - createAuthRequest(""); - warmUpBrowser(); - displayAuthOptions(); - } - - /** - * Initiates a dynamic registration request if a client ID is not provided by - * the static - * configuration. - */ - @WorkerThread - private void initializeClient() { - if (mConfiguration.getClientId() != null) { - Log.i(TAG, "Using static client ID: " + mConfiguration.getClientId()); - // use a statically configured client ID - mClientId.set(mConfiguration.getClientId()); - runOnUiThread(this::initializeAuthRequest); - return; - } - - RegistrationResponse lastResponse = mAuthStateManager.getCurrent().getLastRegistrationResponse(); - if (lastResponse != null) { - Log.i(TAG, "Using dynamic client ID: " + lastResponse.clientId); - // already dynamically registered a client ID - mClientId.set(lastResponse.clientId); - runOnUiThread(this::initializeAuthRequest); - return; - } - - // WrongThread inference is incorrect for lambdas - // noinspection WrongThread - Log.i(TAG, "Dynamically registering client"); - - RegistrationRequest registrationRequest = new RegistrationRequest.Builder( - mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration(), - Collections.singletonList(mConfiguration.getRedirectUri())) - .setTokenEndpointAuthenticationMethod(ClientSecretBasic.NAME) - .build(); - - mAuthService.performRegistrationRequest( - registrationRequest, - this::handleRegistrationResponse); - } - - private void handleRegistrationResponse( - RegistrationResponse response, - AuthorizationException ex) { - mAuthStateManager.updateAfterRegistration(response, ex); - if (response == null) { - Log.i(TAG, "Failed to dynamically register client", ex); - return; - } - - Log.i(TAG, "Dynamically registered client: " + response.clientId); - mClientId.set(response.clientId); - initializeAuthRequest(); - } - - private void displayAuthOptions() { - AuthState state = mAuthStateManager.getCurrent(); - AuthorizationServiceConfiguration config = state.getAuthorizationServiceConfiguration(); - - String authEndpointStr; - if (config.discoveryDoc != null) { - authEndpointStr = "Discovered auth endpoint: \n"; - } else { - authEndpointStr = "Static auth endpoint: \n"; - } - authEndpointStr += config.authorizationEndpoint; - Log.i(TAG, authEndpointStr); - - String clientIdStr; - if (state.getLastRegistrationResponse() != null) { - clientIdStr = "Dynamic client ID: \n"; - } else { - clientIdStr = "Static client ID: \n"; - } - clientIdStr += mClientId; - Log.i(TAG, clientIdStr); - } - - /** - * Performs the authorization request, using the browser selected in the - * spinner, - * and a user-provided `login_hint` if available. - */ - @WorkerThread - private void doAuth() { - try { - mAuthIntentLatch.await(); - } catch (InterruptedException ex) { - Log.w(TAG, "Interrupted while waiting for auth intent"); - } - - Intent intent = mAuthService.getAuthorizationRequestIntent( - mAuthRequest.get(), - mAuthIntent.get()); - startActivityForResult(intent, RC_AUTH); - } - - @MainThread - private void exchangeAuthorizationCode(AuthorizationResponse authorizationResponse) { - Log.d(TAG, "Exchanging authorization code"); - performTokenRequest( - authorizationResponse.createTokenExchangeRequest(), - this::handleCodeExchangeResponse); - } - - @MainThread - private void performTokenRequest( - TokenRequest request, - AuthorizationService.TokenResponseCallback callback) { - ClientAuthentication clientAuthentication; - try { - clientAuthentication = mAuthStateManager.getCurrent().getClientAuthentication(); - } catch (ClientAuthentication.UnsupportedAuthenticationMethod ex) { - Log.d(TAG, "Token request cannot be made, client authentication for the token " - + "endpoint could not be constructed (%s)", ex); - return; - } - - mAuthService.performTokenRequest( - request, - clientAuthentication, - callback); - } - - private void checkAuthStateAndCB() { - AuthState state = mAuthStateManager.getCurrent(); - Log.d(TAG, "login success, auth state: " + state.isAuthorized()); - Log.d(TAG, "id token : " + state.getIdToken()); - Log.d(TAG, "access token: " + state.getAccessToken()); - Log.d(TAG, "refresh token: " + state.getRefreshToken()); - mAuthStateManager.replace(state); - if (loginCbAction != null) { - mExecutor.submit(() -> { - loginCbAction.accept("success"); - loginCbAction = null; - }); - } else { - JcSDK.nativeCb(this.mFunID, null, state.getIdToken()); - } - } - - @WorkerThread - private void handleCodeExchangeResponse( - @Nullable TokenResponse tokenResponse, - @Nullable AuthorizationException authException) { - mAuthStateManager.updateAfterTokenResponse(tokenResponse, authException); - if (!mAuthStateManager.getCurrent().isAuthorized()) { - final String message = "Authorization Code exchange failed: " - + ((authException != null) ? authException.error : ""); - Log.d(TAG, message); - JcSDK.nativeCb(mFunID, message, null); - } else { - checkAuthStateAndCB(); - } - } // sign with tiktok public void signWithTiktok(String funId) { @@ -909,24 +597,23 @@ public class MainActivity extends UnityPlayerActivity mWalletUtil.uploadCfgWithGPS(_result -> {}); } } else { - AuthState state = mAuthStateManager.getCurrent(); + AuthState state = mAppAuthSvr.getCurrentState(); if (state.isAuthorized()) { if (state.getNeedsTokenRefresh()) { Log.d(TAG, "need refresh accessToken"); - loginCbAction = _result -> { + mAppAuthSvr.refreshToken(funID, _result -> { mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{}); - }; - performTokenRequest(state.createTokenRefreshRequest(), - this::handleCodeExchangeResponse); + }); } else { mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{}); } } else { Log.w(TAG, "not login"); - loginCbAction = _result -> { - mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{}); - }; - mExecutor.submit(this::doAuth); + mExecutor.submit(() -> { + mAppAuthSvr.doAuth(funID, _result -> { + mWalletUtil.uploadCfgWithApi(state.getAccessToken(), _r->{}); + }); + }); } } }); @@ -960,20 +647,18 @@ public class MainActivity extends UnityPlayerActivity } } else { if (!mWalletUtil.localCfgExists(account)) { - AuthState state = mAuthStateManager.getCurrent(); + AuthState state = mAppAuthSvr.getCurrentState(); // 1. check whether the google account has been logged in, this situation occurs when non-google login // 2. check if already had permission of drive appdata, add permission of drive appdata to auth_config // 3. check if need refresh access token if (state.isAuthorized()) { if (state.getNeedsTokenRefresh()) { Log.d(TAG, "need refresh accessToken"); - loginCbAction = _result -> { + mAppAuthSvr.refreshToken(funID, _result -> { mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> { mWalletUtil.getPassLocal(); }); - }; - performTokenRequest(state.createTokenRefreshRequest(), - this::handleCodeExchangeResponse); + }); } else { Log.d(TAG, "access token no need refresh"); mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> { @@ -982,12 +667,13 @@ public class MainActivity extends UnityPlayerActivity } } else { Log.w(TAG, "not login"); - loginCbAction = _result -> { - mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> { - mWalletUtil.getPassLocal(); + mExecutor.submit(() -> { + mAppAuthSvr.doAuth(funID, _result -> { + mWalletUtil.downloadCfgWithApi(state.getAccessToken(), _file -> { + mWalletUtil.getPassLocal(); + }); }); - }; - mExecutor.submit(this::doAuth); + }); } } else { mWalletUtil.getPassLocal(); diff --git a/app/src/com/cege/games/release/appauth/AppAuthSvr.java b/app/src/com/cege/games/release/appauth/AppAuthSvr.java new file mode 100644 index 0000000..3ceb97a --- /dev/null +++ b/app/src/com/cege/games/release/appauth/AppAuthSvr.java @@ -0,0 +1,394 @@ +package com.cege.games.release.appauth; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; +import androidx.browser.customtabs.CustomTabsIntent; + +import com.jc.jcfw.JcSDK; +import com.jc.jcfw.appauth.AuthStateManager; +import com.jc.jcfw.appauth.JConfiguration; +import com.jc.jcfw.util.ThreadUtils; + +import net.openid.appauth.AppAuthConfiguration; +import net.openid.appauth.AuthState; +import net.openid.appauth.AuthorizationException; +import net.openid.appauth.AuthorizationRequest; +import net.openid.appauth.AuthorizationResponse; +import net.openid.appauth.AuthorizationService; +import net.openid.appauth.AuthorizationServiceConfiguration; +import net.openid.appauth.ClientAuthentication; +import net.openid.appauth.ClientSecretBasic; +import net.openid.appauth.RegistrationRequest; +import net.openid.appauth.RegistrationResponse; +import net.openid.appauth.ResponseTypeValues; +import net.openid.appauth.TokenRequest; +import net.openid.appauth.TokenResponse; +import net.openid.appauth.browser.AnyBrowserMatcher; +import net.openid.appauth.browser.BrowserMatcher; + +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; + +public class AppAuthSvr { + private final String TAG = getClass().getSimpleName(); + private final Activity activity; + private AuthorizationService mAuthService; + private AuthStateManager mAuthStateManager; + private JConfiguration mConfiguration; + private ExecutorService mExecutor; + private final AtomicReference mClientId = new AtomicReference<>(); + private final AtomicReference mAuthRequest = new AtomicReference<>(); + private final AtomicReference mAuthIntent = new AtomicReference<>(); + private CountDownLatch mAuthIntentLatch = new CountDownLatch(1); + + private String mFunID; + + private static final int RC_AUTH = 0X04; + + private Consumer loginCbAction = null; + + @NonNull + private final BrowserMatcher mBrowserMatcher = AnyBrowserMatcher.INSTANCE; + + public AppAuthSvr(Activity activity) { + this.activity = activity; + } + + public void init(ExecutorService executorService) { + mExecutor = executorService; + mAuthStateManager = AuthStateManager.getInstance(activity); + mConfiguration = JConfiguration.getInstance(activity); + mExecutor.submit(this::initializeAppAuth); + } + + public void setFunID(String funID) { + this.mFunID = funID; + } + + public void setLoginCbAction(Consumer func) { + this.loginCbAction = func; + } + + public AuthState getCurrentState() { + return mAuthStateManager.getCurrent(); + } + + /** + * Performs the authorization request, using the browser selected in the + * spinner, + * and a user-provided `login_hint` if available. + */ + @WorkerThread + public void doAuth(String funID, Consumer func) { + this.mFunID = funID; + this.loginCbAction = func; + try { + mAuthIntentLatch.await(); + } catch (InterruptedException ex) { + Log.w(TAG, "Interrupted while waiting for auth intent"); + } + + Intent intent = mAuthService.getAuthorizationRequestIntent( + mAuthRequest.get(), + mAuthIntent.get()); + activity.startActivityForResult(intent, RC_AUTH); + } + + public void parseLoginResult(@NonNull Intent data) { + AuthorizationResponse response = AuthorizationResponse.fromIntent(data); + AuthorizationException ex = AuthorizationException.fromIntent(data); + if (response != null || ex != null) { + mAuthStateManager.updateAfterAuthorization(response, ex); + } + if (response != null && response.authorizationCode != null) { + // authorization code exchange is required + mAuthStateManager.updateAfterAuthorization(response, ex); + exchangeAuthorizationCode(response); + } else if (ex != null) { + Log.i(TAG, "Authorization flow failed: " + ex.getMessage()); + errorCB("Authorization flow failed: " + ex.getMessage()); + } else { + Log.i(TAG, "No authorization state retained - reauthorization required"); + errorCB("No authorization state retained - reauthorization required"); + } + } + + public void refreshToken(String funID, Consumer func) { + this.mFunID = funID; + this.loginCbAction = func; + AuthState state = getCurrentState(); + performTokenRequest(state.createTokenRefreshRequest(), this::handleCodeExchangeResponse); + } + + /** + * Initializes the authorization service configuration if necessary, either from + * the local + * static values or by retrieving an OpenID discovery document. + */ + @WorkerThread + private void initializeAppAuth() { + Log.i(TAG, "Initializing AppAuth"); + recreateAuthorizationService(); + + if (mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration() != null) { + // configuration is already created, skip to client initialization + Log.i(TAG, "auth config already established"); + initializeClient(); + return; + } + + // if we are not using discovery, build the authorization service configuration + // directly + // from the static configuration values. + if (mConfiguration.getDiscoveryUri() == null) { + Log.i(TAG, "Creating auth config from res/raw/auth_config.json"); + AuthorizationServiceConfiguration config = new AuthorizationServiceConfiguration( + mConfiguration.getAuthEndpointUri(), + mConfiguration.getTokenEndpointUri(), + mConfiguration.getRegistrationEndpointUri(), + mConfiguration.getEndSessionEndpoint()); + + mAuthStateManager.replace(new AuthState(config)); + initializeClient(); + return; + } + + // WrongThread inference is incorrect for lambdas + // noinspection WrongThread + Log.i(TAG, "Retrieving OpenID discovery doc"); + AuthorizationServiceConfiguration.fetchFromUrl( + mConfiguration.getDiscoveryUri(), + this::handleConfigurationRetrievalResult, + mConfiguration.getConnectionBuilder()); + } + + @MainThread + private void handleConfigurationRetrievalResult( + AuthorizationServiceConfiguration config, + AuthorizationException ex) { + if (config == null) { + Log.i(TAG, "Failed to retrieve discovery document", ex); + return; + } + + Log.i(TAG, "Discovery document retrieved"); + mAuthStateManager.replace(new AuthState(config)); + mExecutor.submit(this::initializeClient); + } + + private void recreateAuthorizationService() { + if (mAuthService != null) { + Log.i(TAG, "Discarding existing AuthService instance"); + mAuthService.dispose(); + } + mAuthService = createAuthorizationService(); + mAuthRequest.set(null); + mAuthIntent.set(null); + } + + private AuthorizationService createAuthorizationService() { + Log.i(TAG, "Creating authorization service"); + AppAuthConfiguration.Builder builder = new AppAuthConfiguration.Builder(); + builder.setBrowserMatcher(mBrowserMatcher); + builder.setConnectionBuilder(mConfiguration.getConnectionBuilder()); + + return new AuthorizationService(activity, builder.build()); + } + + private void createAuthRequest(@Nullable String loginHint) { + Log.i(TAG, "Creating auth request for login hint: " + loginHint); + AuthorizationRequest.Builder authRequestBuilder = new AuthorizationRequest.Builder( + mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration(), + mClientId.get(), + ResponseTypeValues.CODE, + mConfiguration.getRedirectUri()) + // apple need `form_post` when authorization_scope has `name email ` + // .setResponseMode("form_post") + // .setResponseType("code id_token") + .setScope(mConfiguration.getScope()); + if (!TextUtils.isEmpty(loginHint)) { + authRequestBuilder.setLoginHint(loginHint); + } + mAuthRequest.set(authRequestBuilder.build()); + } + + private void warmUpBrowser() { + mAuthIntentLatch = new CountDownLatch(1); + mExecutor.execute(() -> { + Log.i(TAG, "Warming up browser instance for auth request"); + Uri uri = mAuthRequest.get().toUri(); + Log.d(TAG, "URI: " + uri); + CustomTabsIntent.Builder intentBuilder = mAuthService.createCustomTabsIntentBuilder(uri); + mAuthIntent.set(intentBuilder.build()); + mAuthIntentLatch.countDown(); + }); + } + + @MainThread + private void initializeAuthRequest() { + createAuthRequest(""); + warmUpBrowser(); + displayAuthOptions(); + } + + /** + * Initiates a dynamic registration request if a client ID is not provided by + * the static + * configuration. + */ + @WorkerThread + private void initializeClient() { + if (mConfiguration.getClientId() != null) { + Log.i(TAG, "Using static client ID: " + mConfiguration.getClientId()); + // use a statically configured client ID + mClientId.set(mConfiguration.getClientId()); + ThreadUtils.runInMain(this::initializeAuthRequest); + return; + } + + RegistrationResponse lastResponse = mAuthStateManager.getCurrent().getLastRegistrationResponse(); + if (lastResponse != null) { + Log.i(TAG, "Using dynamic client ID: " + lastResponse.clientId); + // already dynamically registered a client ID + mClientId.set(lastResponse.clientId); + ThreadUtils.runInMain(this::initializeAuthRequest); + return; + } + + // WrongThread inference is incorrect for lambdas + // noinspection WrongThread + Log.i(TAG, "Dynamically registering client"); + + RegistrationRequest registrationRequest = new RegistrationRequest.Builder( + mAuthStateManager.getCurrent().getAuthorizationServiceConfiguration(), + Collections.singletonList(mConfiguration.getRedirectUri())) + .setTokenEndpointAuthenticationMethod(ClientSecretBasic.NAME) + .build(); + + mAuthService.performRegistrationRequest( + registrationRequest, + this::handleRegistrationResponse); + } + + private void handleRegistrationResponse( + RegistrationResponse response, + AuthorizationException ex) { + mAuthStateManager.updateAfterRegistration(response, ex); + if (response == null) { + Log.i(TAG, "Failed to dynamically register client", ex); + return; + } + + Log.i(TAG, "Dynamically registered client: " + response.clientId); + mClientId.set(response.clientId); + initializeAuthRequest(); + } + + private void displayAuthOptions() { + AuthState state = mAuthStateManager.getCurrent(); + AuthorizationServiceConfiguration config = state.getAuthorizationServiceConfiguration(); + + String authEndpointStr; + if (config.discoveryDoc != null) { + authEndpointStr = "Discovered auth endpoint: \n"; + } else { + authEndpointStr = "Static auth endpoint: \n"; + } + authEndpointStr += config.authorizationEndpoint; + Log.i(TAG, authEndpointStr); + + String clientIdStr; + if (state.getLastRegistrationResponse() != null) { + clientIdStr = "Dynamic client ID: \n"; + } else { + clientIdStr = "Static client ID: \n"; + } + clientIdStr += mClientId; + Log.i(TAG, clientIdStr); + } + + @MainThread + private void exchangeAuthorizationCode(AuthorizationResponse authorizationResponse) { + Log.d(TAG, "Exchanging authorization code"); + performTokenRequest( + authorizationResponse.createTokenExchangeRequest(), + this::handleCodeExchangeResponse); + } + + @MainThread + private void performTokenRequest( + TokenRequest request, + AuthorizationService.TokenResponseCallback callback) { + ClientAuthentication clientAuthentication; + try { + clientAuthentication = mAuthStateManager.getCurrent().getClientAuthentication(); + } catch (ClientAuthentication.UnsupportedAuthenticationMethod ex) { + Log.d(TAG, "Token request cannot be made, client authentication for the token " + + "endpoint could not be constructed (%s)", ex); + return; + } + + mAuthService.performTokenRequest( + request, + clientAuthentication, + callback); + } + + @WorkerThread + private void handleCodeExchangeResponse( + @Nullable TokenResponse tokenResponse, + @Nullable AuthorizationException authException) { + mAuthStateManager.updateAfterTokenResponse(tokenResponse, authException); + if (!mAuthStateManager.getCurrent().isAuthorized()) { + final String message = "Authorization Code exchange failed: " + + ((authException != null) ? authException.error : ""); + Log.d(TAG, message); + errorCB(message); + } else { + checkAuthStateAndCB(); + } + } + + private void checkAuthStateAndCB() { + AuthState state = mAuthStateManager.getCurrent(); + Log.d(TAG, "login success, auth state: " + state.isAuthorized()); + Log.d(TAG, "id token : " + state.getIdToken()); + Log.d(TAG, "access token: " + state.getAccessToken()); + Log.d(TAG, "refresh token: " + state.getRefreshToken()); + mAuthStateManager.replace(state); + if (loginCbAction != null) { + mExecutor.submit(() -> { + loginCbAction.accept("success"); + clearParams(); + }); + } else { + successCB(state.getIdToken()); + } + } + private void clearParams() { + this.mFunID = null; + this.loginCbAction = null; + } + public void errorCB(String msg) { + JcSDK.nativeCb(mFunID, msg, null); + clearParams(); + } + + public void successCB(String dataStr) { + JcSDK.nativeCb(mFunID, null, dataStr); + clearParams(); + } + +}