增加google第三方登录相关代码
This commit is contained in:
parent
8db5491391
commit
5fd6c4a682
17
.idea/deploymentTargetDropDown.xml
generated
Normal file
17
.idea/deploymentTargetDropDown.xml
generated
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<targetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="QUICK_BOOT_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="VIRTUAL_DEVICE_PATH" />
|
||||
<value value="$USER_HOME$/.android/avd/Pixel_4_API_28_2.avd" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</targetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2022-09-09T05:20:37.906784Z" />
|
||||
</component>
|
||||
</project>
|
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.fitchgc.headlesscocos">
|
||||
|
||||
<application
|
||||
@ -14,6 +15,7 @@
|
||||
android:value="cocos2djs" />
|
||||
<activity android:name=".MainActivity"
|
||||
android:screenOrientation="sensorLandscape"
|
||||
android:exported="true"
|
||||
>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
@ -26,6 +28,32 @@
|
||||
<activity
|
||||
android:name="com.king.zxing.CaptureActivity"
|
||||
android:theme="@style/CaptureTheme"/>
|
||||
<!--
|
||||
This activity declaration is merged with the version from the library manifest.
|
||||
It demonstrates how an https redirect can be captured, in addition to or instead of
|
||||
a custom scheme.
|
||||
|
||||
Generally, this should be done in conjunction with an app link declaration for Android M
|
||||
and above, for additional security and an improved user experience. See:
|
||||
|
||||
https://developer.android.com/training/app-links/index.html
|
||||
|
||||
The declaration from the library can be completely replaced by adding
|
||||
|
||||
tools:node="replace"
|
||||
|
||||
To the list of attributes on the activity element.
|
||||
-->
|
||||
<activity
|
||||
android:name="net.openid.appauth.RedirectUriReceiverActivity"
|
||||
tools:node="replace">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="com.googleusercontent.apps.165555585193-ud80sst45po348ohec2h33t2m6mjnlt0"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
|
@ -39,6 +39,10 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
manifestPlaceholders = [
|
||||
'appAuthRedirectScheme': 'com.googleusercontent.apps.165555585193-ud80sst45po348ohec2h33t2m6mjnlt0'
|
||||
]
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
@ -57,10 +61,28 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file("${rootDir}/keys/release")
|
||||
storePassword '7654321'
|
||||
keyAlias 'release'
|
||||
keyPassword '7654321'
|
||||
}
|
||||
release {
|
||||
storeFile file("${rootDir}/keys/release")
|
||||
storePassword "7654321"
|
||||
keyAlias "release"
|
||||
keyPassword "7654321"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
release {
|
||||
minifyEnabled false
|
||||
signingConfig signingConfigs.release
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
@ -114,4 +136,6 @@ dependencies {
|
||||
implementation 'pub.devrel:easypermissions:3.0.0'
|
||||
implementation 'com.github.jenly1314:zxing-lite:2.1.1'
|
||||
implementation project(path: ':libcocos2dx')
|
||||
implementation 'net.openid:appauth:0.11.1'
|
||||
implementation "com.squareup.okio:okio:2.10.0"
|
||||
}
|
@ -4,40 +4,68 @@ import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Window;
|
||||
|
||||
import com.google.android.gms.auth.api.identity.BeginSignInRequest;
|
||||
import com.google.android.gms.auth.api.identity.BeginSignInResult;
|
||||
import com.google.android.gms.auth.api.identity.Identity;
|
||||
import com.google.android.gms.auth.api.identity.SignInClient;
|
||||
import com.google.android.gms.auth.api.identity.SignInCredential;
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn;
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
import com.google.android.gms.common.api.ApiException;
|
||||
import com.google.android.gms.common.api.CommonStatusCodes;
|
||||
import com.google.android.gms.tasks.OnFailureListener;
|
||||
import com.google.android.gms.tasks.OnSuccessListener;
|
||||
import com.google.android.gms.common.api.Scope;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.jc.jcfw.JcSDK;
|
||||
import com.jc.jcfw.appauth.AuthStateManager;
|
||||
import com.jc.jcfw.appauth.JConfiguration;
|
||||
import com.king.zxing.CameraScan;
|
||||
import com.king.zxing.CaptureActivity;
|
||||
import com.king.zxing.util.CodeUtils;
|
||||
import com.king.zxing.util.LogUtils;
|
||||
import com.unity3d.player.UnityPlayer;
|
||||
|
||||
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;
|
||||
|
||||
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 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 pub.devrel.easypermissions.AfterPermissionGranted;
|
||||
import pub.devrel.easypermissions.EasyPermissions;
|
||||
@ -54,9 +82,13 @@ public class MainActivity extends Activity
|
||||
public static final String KEY_IS_QR_CODE = "key_code";
|
||||
public static final String KEY_IS_CONTINUOUS = "key_continuous_scan";
|
||||
|
||||
// scan QRCode
|
||||
public static final int REQUEST_CODE_SCAN = 0X01;
|
||||
public static final int REQUEST_CODE_PHOTO = 0X02;
|
||||
public static final int REQ_ONE_TAP = 0X03;
|
||||
// AppAuth
|
||||
private static final int RC_AUTH = 0X04;
|
||||
// google signin
|
||||
private static final int RC_SIGN_IN = 0X05;
|
||||
|
||||
public static final int RC_CAMERA = 0X01;
|
||||
|
||||
@ -64,8 +96,22 @@ public class MainActivity extends Activity
|
||||
private String title;
|
||||
private String funId;
|
||||
|
||||
private SignInClient oneTapClient;
|
||||
private BeginSignInRequest signInRequest;
|
||||
//AppAuth
|
||||
private AuthorizationService mAuthService;
|
||||
private AuthStateManager mAuthStateManager;
|
||||
private JConfiguration mConfiguration;
|
||||
private ExecutorService mExecutor;
|
||||
private final AtomicReference<String> mClientId = new AtomicReference<>();
|
||||
private final AtomicReference<AuthorizationRequest> mAuthRequest = new AtomicReference<>();
|
||||
private final AtomicReference<CustomTabsIntent> mAuthIntent = new AtomicReference<>();
|
||||
private CountDownLatch mAuthIntentLatch = new CountDownLatch(1);
|
||||
private static final String EXTRA_FAILED = "failed";
|
||||
|
||||
@NonNull
|
||||
private final BrowserMatcher mBrowserMatcher = AnyBrowserMatcher.INSTANCE;
|
||||
|
||||
private GoogleSignInClient mGoogleSignInClient;
|
||||
|
||||
|
||||
protected String updateUnityCommandLineArguments(String cmdLine) {
|
||||
return cmdLine;
|
||||
@ -89,21 +135,19 @@ public class MainActivity extends Activity
|
||||
Cocos2dxHelper.init(this);
|
||||
CocosJSHelper.initJSEnv(getApplicationContext());
|
||||
|
||||
oneTapClient = Identity.getSignInClient(this);
|
||||
signInRequest = BeginSignInRequest.builder()
|
||||
.setPasswordRequestOptions(BeginSignInRequest.PasswordRequestOptions.builder()
|
||||
.setSupported(true)
|
||||
.build())
|
||||
.setGoogleIdTokenRequestOptions(BeginSignInRequest.GoogleIdTokenRequestOptions.builder()
|
||||
.setSupported(true)
|
||||
// Your server's client ID, not your Android client ID.
|
||||
.setServerClientId(getString(R.string.default_web_client_id))
|
||||
// Only show accounts previously used to sign in.
|
||||
.setFilterByAuthorizedAccounts(true)
|
||||
.build())
|
||||
// Automatically sign in when exactly one credential is retrieved.
|
||||
.setAutoSelectEnabled(true)
|
||||
.build();
|
||||
// begin of google sign
|
||||
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
||||
.requestIdToken(getString(R.string.default_web_client_id))
|
||||
.requestScopes(new Scope("https://www.googleapis.com/auth/drive.appdata"))
|
||||
.build();
|
||||
mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
|
||||
// end of google sign
|
||||
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
mAuthStateManager = AuthStateManager.getInstance(this);
|
||||
mConfiguration = JConfiguration.getInstance(this);
|
||||
|
||||
mExecutor.submit(this::initializeAppAuth);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -121,38 +165,26 @@ public class MainActivity extends Activity
|
||||
case REQUEST_CODE_PHOTO:
|
||||
parsePhoto(data);
|
||||
break;
|
||||
case REQ_ONE_TAP:
|
||||
try {
|
||||
SignInCredential credential = oneTapClient.getSignInCredentialFromIntent(data);
|
||||
String idToken = credential.getGoogleIdToken();
|
||||
String username = credential.getId();
|
||||
String password = credential.getPassword();
|
||||
if (idToken != null) {
|
||||
// Got an ID token from Google. Use it to authenticate
|
||||
// with your backend.
|
||||
Log.d(TAG, "Got ID token: " + idToken);
|
||||
} else if (password != null) {
|
||||
// Got a saved username and password. Use them to authenticate
|
||||
// with your backend.
|
||||
Log.d(TAG, "Got password.");
|
||||
}
|
||||
} catch (ApiException e) {
|
||||
switch (e.getStatusCode()) {
|
||||
case CommonStatusCodes.CANCELED:
|
||||
Log.d(TAG, "One-tap dialog was closed.");
|
||||
// Don't re-prompt the user.
|
||||
// showOneTapUI = false;
|
||||
break;
|
||||
case CommonStatusCodes.NETWORK_ERROR:
|
||||
Log.d(TAG, "One-tap encountered a network error.");
|
||||
// Try again or just ignore.
|
||||
break;
|
||||
default:
|
||||
Log.d(TAG, "Couldn't get credential from result."
|
||||
+ e.getLocalizedMessage());
|
||||
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());
|
||||
} else {
|
||||
Log.i(TAG, "No authorization state retained - reauthorization required");
|
||||
}
|
||||
break;
|
||||
case RC_SIGN_IN:
|
||||
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
|
||||
handleSignInResult(task);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@ -169,6 +201,10 @@ public class MainActivity extends Activity
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
mUnityPlayer.start();
|
||||
|
||||
if (mExecutor.isShutdown()) {
|
||||
mExecutor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
}
|
||||
|
||||
// Resume Unity
|
||||
@ -375,32 +411,294 @@ public class MainActivity extends Activity
|
||||
// end of qrcode
|
||||
|
||||
public void signWithGoogle(String funId) {
|
||||
oneTapClient.beginSignIn(signInRequest)
|
||||
.addOnSuccessListener(this, new OnSuccessListener<BeginSignInResult>() {
|
||||
@Override
|
||||
public void onSuccess(BeginSignInResult result) {
|
||||
try {
|
||||
startIntentSenderForResult(
|
||||
result.getPendingIntent().getIntentSender(), REQ_ONE_TAP,
|
||||
null, 0, 0, 0);
|
||||
} catch (IntentSender.SendIntentException e) {
|
||||
Log.e(TAG, "Couldn't start One Tap UI: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
})
|
||||
.addOnFailureListener(this, new OnFailureListener() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Exception e) {
|
||||
// No saved credentials found. Launch the One Tap sign-up flow, or
|
||||
// do nothing and continue presenting the signed-out UI.
|
||||
Log.d(TAG, e.getLocalizedMessage());
|
||||
}
|
||||
});
|
||||
|
||||
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS) {
|
||||
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
|
||||
if (account != null) {
|
||||
Log.w(TAG, "already login: " + account.getIdToken());
|
||||
} else {
|
||||
Intent signInIntent = mGoogleSignInClient.getSignInIntent();
|
||||
startActivityForResult(signInIntent, RC_SIGN_IN);
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "no gms, use app auth");
|
||||
AuthState state = mAuthStateManager.getCurrent();
|
||||
if (state.isAuthorized() && state.getIdToken() != null) {
|
||||
Log.w(TAG, "already login: " + state.getIdToken());
|
||||
} else {
|
||||
mExecutor.submit(this::doAuth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void signOutGoogle(String funId) {
|
||||
oneTapClient.signOut();
|
||||
mGoogleSignInClient.signOut()
|
||||
.addOnCompleteListener(this, task -> {
|
||||
// ...
|
||||
});
|
||||
}
|
||||
|
||||
private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
|
||||
try {
|
||||
GoogleSignInAccount account = completedTask.getResult(ApiException.class);
|
||||
Log.w(TAG, "signIn success: ");
|
||||
Log.w(TAG, "idToken: " + account.getIdToken());
|
||||
// Signed in successfully, show authenticated UI.
|
||||
} catch (ApiException e) {
|
||||
// The ApiException status code indicates the detailed failure reason.
|
||||
// Please refer to the GoogleSignInStatusCodes class reference for more information.
|
||||
Log.w(TAG, "signInResult:failed code=" + e.getStatusCode());
|
||||
}
|
||||
}
|
||||
|
||||
// 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())
|
||||
.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");
|
||||
CustomTabsIntent.Builder intentBuilder =
|
||||
mAuthService.createCustomTabsIntentBuilder(mAuthRequest.get().toUri());
|
||||
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);
|
||||
}
|
||||
|
||||
@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 : "");
|
||||
|
||||
// WrongThread inference is incorrect for lambdas
|
||||
//noinspection WrongThread
|
||||
Log.d(TAG, message);
|
||||
} else {
|
||||
Log.d(TAG, "success");
|
||||
AuthState state = mAuthStateManager.getCurrent();
|
||||
Log.d(TAG, String.valueOf(state.isAuthorized()));
|
||||
Log.d(TAG, String.valueOf(state.getIdToken()));
|
||||
mAuthStateManager.replace(state);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
150
app/src/com/jc/jcfw/appauth/AuthStateManager.java
Normal file
150
app/src/com/jc/jcfw/appauth/AuthStateManager.java
Normal file
@ -0,0 +1,150 @@
|
||||
package com.jc.jcfw.appauth;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import net.openid.appauth.AuthState;
|
||||
import net.openid.appauth.AuthorizationException;
|
||||
import net.openid.appauth.AuthorizationResponse;
|
||||
import net.openid.appauth.RegistrationResponse;
|
||||
import net.openid.appauth.TokenResponse;
|
||||
|
||||
import org.json.JSONException;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class AuthStateManager {
|
||||
|
||||
private static final AtomicReference<WeakReference<AuthStateManager>> INSTANCE_REF =
|
||||
new AtomicReference<>(new WeakReference<>(null));
|
||||
|
||||
private static final String TAG = "AuthStateManager";
|
||||
|
||||
private static final String STORE_NAME = "AuthState";
|
||||
private static final String KEY_STATE = "state";
|
||||
|
||||
private final SharedPreferences mPrefs;
|
||||
private final ReentrantLock mPrefsLock;
|
||||
private final AtomicReference<AuthState> mCurrentAuthState;
|
||||
|
||||
@AnyThread
|
||||
public static AuthStateManager getInstance(@NonNull Context context) {
|
||||
AuthStateManager manager = INSTANCE_REF.get().get();
|
||||
if (manager == null) {
|
||||
manager = new AuthStateManager(context.getApplicationContext());
|
||||
INSTANCE_REF.set(new WeakReference<>(manager));
|
||||
}
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
private AuthStateManager(Context context) {
|
||||
mPrefs = context.getSharedPreferences(STORE_NAME, Context.MODE_PRIVATE);
|
||||
mPrefsLock = new ReentrantLock();
|
||||
mCurrentAuthState = new AtomicReference<>();
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@NonNull
|
||||
public AuthState getCurrent() {
|
||||
if (mCurrentAuthState.get() != null) {
|
||||
return mCurrentAuthState.get();
|
||||
}
|
||||
|
||||
AuthState state = readState();
|
||||
if (mCurrentAuthState.compareAndSet(null, state)) {
|
||||
return state;
|
||||
} else {
|
||||
return mCurrentAuthState.get();
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@NonNull
|
||||
public AuthState replace(@NonNull AuthState state) {
|
||||
writeState(state);
|
||||
mCurrentAuthState.set(state);
|
||||
return state;
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@NonNull
|
||||
public AuthState updateAfterAuthorization(
|
||||
@Nullable AuthorizationResponse response,
|
||||
@Nullable AuthorizationException ex) {
|
||||
AuthState current = getCurrent();
|
||||
current.update(response, ex);
|
||||
return replace(current);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@NonNull
|
||||
public AuthState updateAfterTokenResponse(
|
||||
@Nullable TokenResponse response,
|
||||
@Nullable AuthorizationException ex) {
|
||||
AuthState current = getCurrent();
|
||||
current.update(response, ex);
|
||||
return replace(current);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@NonNull
|
||||
public AuthState updateAfterRegistration(
|
||||
RegistrationResponse response,
|
||||
AuthorizationException ex) {
|
||||
AuthState current = getCurrent();
|
||||
if (ex != null) {
|
||||
return current;
|
||||
}
|
||||
|
||||
current.update(response);
|
||||
return replace(current);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@NonNull
|
||||
private AuthState readState() {
|
||||
mPrefsLock.lock();
|
||||
try {
|
||||
String currentState = mPrefs.getString(KEY_STATE, null);
|
||||
if (currentState == null) {
|
||||
return new AuthState();
|
||||
}
|
||||
|
||||
try {
|
||||
return AuthState.jsonDeserialize(currentState);
|
||||
} catch (JSONException ex) {
|
||||
Log.w(TAG, "Failed to deserialize stored auth state - discarding");
|
||||
return new AuthState();
|
||||
}
|
||||
} finally {
|
||||
mPrefsLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
private void writeState(@Nullable AuthState state) {
|
||||
mPrefsLock.lock();
|
||||
try {
|
||||
SharedPreferences.Editor editor = mPrefs.edit();
|
||||
if (state == null) {
|
||||
editor.remove(KEY_STATE);
|
||||
} else {
|
||||
editor.putString(KEY_STATE, state.jsonSerializeString());
|
||||
}
|
||||
|
||||
if (!editor.commit()) {
|
||||
throw new IllegalStateException("Failed to write state to shared prefs");
|
||||
}
|
||||
} finally {
|
||||
mPrefsLock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
108
app/src/com/jc/jcfw/appauth/ConnectionBuilderForTesting.java
Normal file
108
app/src/com/jc/jcfw/appauth/ConnectionBuilderForTesting.java
Normal file
@ -0,0 +1,108 @@
|
||||
package com.jc.jcfw.appauth;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import net.openid.appauth.Preconditions;
|
||||
import net.openid.appauth.connectivity.ConnectionBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public final class ConnectionBuilderForTesting implements ConnectionBuilder {
|
||||
|
||||
public static final ConnectionBuilderForTesting INSTANCE = new ConnectionBuilderForTesting();
|
||||
|
||||
private static final String TAG = "ConnBuilder";
|
||||
|
||||
private static final int CONNECTION_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(15);
|
||||
private static final int READ_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(10);
|
||||
|
||||
private static final String HTTP = "http";
|
||||
private static final String HTTPS = "https";
|
||||
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
private static final TrustManager[] ANY_CERT_MANAGER = new TrustManager[] {
|
||||
new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
||||
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressLint("BadHostnameVerifier")
|
||||
private static final HostnameVerifier ANY_HOSTNAME_VERIFIER = new HostnameVerifier() {
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
private static final SSLContext TRUSTING_CONTEXT;
|
||||
|
||||
static {
|
||||
SSLContext context;
|
||||
try {
|
||||
context = SSLContext.getInstance("SSL");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e("ConnBuilder", "Unable to acquire SSL context");
|
||||
context = null;
|
||||
}
|
||||
|
||||
SSLContext initializedContext = null;
|
||||
if (context != null) {
|
||||
try {
|
||||
context.init(null, ANY_CERT_MANAGER, new java.security.SecureRandom());
|
||||
initializedContext = context;
|
||||
} catch (KeyManagementException e) {
|
||||
Log.e(TAG, "Failed to initialize trusting SSL context");
|
||||
}
|
||||
}
|
||||
|
||||
TRUSTING_CONTEXT = initializedContext;
|
||||
}
|
||||
|
||||
private ConnectionBuilderForTesting() {
|
||||
// no need to construct new instances
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException {
|
||||
Preconditions.checkNotNull(uri, "url must not be null");
|
||||
Preconditions.checkArgument(HTTP.equals(uri.getScheme()) || HTTPS.equals(uri.getScheme()),
|
||||
"scheme or uri must be http or https");
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(uri.toString()).openConnection();
|
||||
conn.setConnectTimeout(CONNECTION_TIMEOUT_MS);
|
||||
conn.setReadTimeout(READ_TIMEOUT_MS);
|
||||
conn.setInstanceFollowRedirects(false);
|
||||
|
||||
if (conn instanceof HttpsURLConnection && TRUSTING_CONTEXT != null) {
|
||||
HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
|
||||
httpsConn.setSSLSocketFactory(TRUSTING_CONTEXT.getSocketFactory());
|
||||
httpsConn.setHostnameVerifier(ANY_HOSTNAME_VERIFIER);
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
}
|
310
app/src/com/jc/jcfw/appauth/JConfiguration.java
Normal file
310
app/src/com/jc/jcfw/appauth/JConfiguration.java
Normal file
@ -0,0 +1,310 @@
|
||||
package com.jc.jcfw.appauth;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.fitchgc.headlesscocos.R;
|
||||
|
||||
import net.openid.appauth.connectivity.ConnectionBuilder;
|
||||
import net.openid.appauth.connectivity.DefaultConnectionBuilder;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
import okio.Okio;
|
||||
|
||||
public final class JConfiguration {
|
||||
|
||||
private static final String TAG = "Configuration";
|
||||
|
||||
private static final String PREFS_NAME = "config";
|
||||
private static final String KEY_LAST_HASH = "lastHash";
|
||||
|
||||
private static WeakReference<JConfiguration> sInstance = new WeakReference<>(null);
|
||||
|
||||
private final Context mContext;
|
||||
private final SharedPreferences mPrefs;
|
||||
private final Resources mResources;
|
||||
|
||||
private JSONObject mConfigJson;
|
||||
private String mConfigHash;
|
||||
private String mConfigError;
|
||||
|
||||
private String mClientId;
|
||||
private String mScope;
|
||||
private Uri mRedirectUri;
|
||||
private Uri mEndSessionRedirectUri;
|
||||
private Uri mDiscoveryUri;
|
||||
private Uri mAuthEndpointUri;
|
||||
private Uri mTokenEndpointUri;
|
||||
private Uri mEndSessionEndpoint;
|
||||
private Uri mRegistrationEndpointUri;
|
||||
private Uri mUserInfoEndpointUri;
|
||||
private boolean mHttpsRequired;
|
||||
|
||||
public static JConfiguration getInstance(Context context) {
|
||||
JConfiguration config = sInstance.get();
|
||||
if (config == null) {
|
||||
config = new JConfiguration(context);
|
||||
sInstance = new WeakReference<JConfiguration>(config);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
public JConfiguration(Context context) {
|
||||
mContext = context;
|
||||
mPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
mResources = context.getResources();
|
||||
|
||||
try {
|
||||
readConfiguration();
|
||||
} catch (InvalidConfigurationException ex) {
|
||||
mConfigError = ex.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the configuration has changed from the last known valid state.
|
||||
*/
|
||||
public boolean hasConfigurationChanged() {
|
||||
String lastHash = getLastKnownConfigHash();
|
||||
return !mConfigHash.equals(lastHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the current configuration is valid.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return mConfigError == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description of the configuration error, if the configuration is invalid.
|
||||
*/
|
||||
@Nullable
|
||||
public String getConfigurationError() {
|
||||
return mConfigError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that the current configuration should be accepted as the "last known valid"
|
||||
* configuration.
|
||||
*/
|
||||
public void acceptConfiguration() {
|
||||
mPrefs.edit().putString(KEY_LAST_HASH, mConfigHash).apply();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getClientId() {
|
||||
return mClientId;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String getScope() {
|
||||
return mScope;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Uri getRedirectUri() {
|
||||
return mRedirectUri;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Uri getDiscoveryUri() {
|
||||
return mDiscoveryUri;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Uri getEndSessionRedirectUri() { return mEndSessionRedirectUri; }
|
||||
|
||||
@Nullable
|
||||
public Uri getAuthEndpointUri() {
|
||||
return mAuthEndpointUri;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Uri getTokenEndpointUri() {
|
||||
return mTokenEndpointUri;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Uri getEndSessionEndpoint() {
|
||||
return mEndSessionEndpoint;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Uri getRegistrationEndpointUri() {
|
||||
return mRegistrationEndpointUri;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Uri getUserInfoEndpointUri() {
|
||||
return mUserInfoEndpointUri;
|
||||
}
|
||||
|
||||
public boolean isHttpsRequired() {
|
||||
return mHttpsRequired;
|
||||
}
|
||||
|
||||
public ConnectionBuilder getConnectionBuilder() {
|
||||
if (isHttpsRequired()) {
|
||||
return DefaultConnectionBuilder.INSTANCE;
|
||||
}
|
||||
return ConnectionBuilderForTesting.INSTANCE;
|
||||
}
|
||||
|
||||
private String getLastKnownConfigHash() {
|
||||
return mPrefs.getString(KEY_LAST_HASH, null);
|
||||
}
|
||||
|
||||
private void readConfiguration() throws InvalidConfigurationException {
|
||||
BufferedSource configSource =
|
||||
Okio.buffer(Okio.source(mResources.openRawResource(R.raw.auth_config)));
|
||||
Buffer configData = new Buffer();
|
||||
try {
|
||||
configSource.readAll(configData);
|
||||
mConfigJson = new JSONObject(configData.readString(Charset.forName("UTF-8")));
|
||||
} catch (IOException ex) {
|
||||
throw new InvalidConfigurationException(
|
||||
"Failed to read configuration: " + ex.getMessage());
|
||||
} catch (JSONException ex) {
|
||||
throw new InvalidConfigurationException(
|
||||
"Unable to parse configuration: " + ex.getMessage());
|
||||
}
|
||||
|
||||
mConfigHash = configData.sha256().base64();
|
||||
mClientId = getConfigString("client_id");
|
||||
mScope = getRequiredConfigString("authorization_scope");
|
||||
mRedirectUri = getRequiredConfigUri("redirect_uri");
|
||||
mEndSessionRedirectUri = getRequiredConfigUri("end_session_redirect_uri");
|
||||
|
||||
if (!isRedirectUriRegistered()) {
|
||||
throw new InvalidConfigurationException(
|
||||
"redirect_uri is not handled by any activity in this app! "
|
||||
+ "Ensure that the appAuthRedirectScheme in your build.gradle file "
|
||||
+ "is correctly configured, or that an appropriate intent filter "
|
||||
+ "exists in your app manifest.");
|
||||
}
|
||||
|
||||
if (getConfigString("discovery_uri") == null) {
|
||||
mAuthEndpointUri = getRequiredConfigWebUri("authorization_endpoint_uri");
|
||||
|
||||
mTokenEndpointUri = getRequiredConfigWebUri("token_endpoint_uri");
|
||||
mUserInfoEndpointUri = getRequiredConfigWebUri("user_info_endpoint_uri");
|
||||
mEndSessionEndpoint = getRequiredConfigUri("end_session_endpoint");
|
||||
|
||||
if (mClientId == null) {
|
||||
mRegistrationEndpointUri = getRequiredConfigWebUri("registration_endpoint_uri");
|
||||
}
|
||||
} else {
|
||||
mDiscoveryUri = getRequiredConfigWebUri("discovery_uri");
|
||||
}
|
||||
|
||||
mHttpsRequired = mConfigJson.optBoolean("https_required", true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getConfigString(String propName) {
|
||||
String value = mConfigJson.optString(propName);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
value = value.trim();
|
||||
if (TextUtils.isEmpty(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String getRequiredConfigString(String propName)
|
||||
throws InvalidConfigurationException {
|
||||
String value = getConfigString(propName);
|
||||
if (value == null) {
|
||||
throw new InvalidConfigurationException(
|
||||
propName + " is required but not specified in the configuration");
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Uri getRequiredConfigUri(String propName)
|
||||
throws InvalidConfigurationException {
|
||||
String uriStr = getRequiredConfigString(propName);
|
||||
Uri uri;
|
||||
try {
|
||||
uri = Uri.parse(uriStr);
|
||||
} catch (Throwable ex) {
|
||||
throw new InvalidConfigurationException(propName + " could not be parsed", ex);
|
||||
}
|
||||
|
||||
if (!uri.isHierarchical() || !uri.isAbsolute()) {
|
||||
throw new InvalidConfigurationException(
|
||||
propName + " must be hierarchical and absolute");
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(uri.getEncodedUserInfo())) {
|
||||
throw new InvalidConfigurationException(propName + " must not have user info");
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(uri.getEncodedQuery())) {
|
||||
throw new InvalidConfigurationException(propName + " must not have query parameters");
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(uri.getEncodedFragment())) {
|
||||
throw new InvalidConfigurationException(propName + " must not have a fragment");
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
Uri getRequiredConfigWebUri(String propName)
|
||||
throws InvalidConfigurationException {
|
||||
Uri uri = getRequiredConfigUri(propName);
|
||||
String scheme = uri.getScheme();
|
||||
if (TextUtils.isEmpty(scheme) || !("http".equals(scheme) || "https".equals(scheme))) {
|
||||
throw new InvalidConfigurationException(
|
||||
propName + " must have an http or https scheme");
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
private boolean isRedirectUriRegistered() {
|
||||
// ensure that the redirect URI declared in the configuration is handled by some activity
|
||||
// in the app, by querying the package manager speculatively
|
||||
Intent redirectIntent = new Intent();
|
||||
redirectIntent.setPackage(mContext.getPackageName());
|
||||
redirectIntent.setAction(Intent.ACTION_VIEW);
|
||||
redirectIntent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
redirectIntent.setData(mRedirectUri);
|
||||
|
||||
return !mContext.getPackageManager().queryIntentActivities(redirectIntent, 0).isEmpty();
|
||||
}
|
||||
|
||||
public static final class InvalidConfigurationException extends Exception {
|
||||
InvalidConfigurationException(String reason) {
|
||||
super(reason);
|
||||
}
|
||||
|
||||
InvalidConfigurationException(String reason, Throwable cause) {
|
||||
super(reason, cause);
|
||||
}
|
||||
}
|
||||
}
|
12
res/raw/auth_config.json
Normal file
12
res/raw/auth_config.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"client_id": "165555585193-ud80sst45po348ohec2h33t2m6mjnlt0.apps.googleusercontent.com",
|
||||
"redirect_uri": "com.googleusercontent.apps.165555585193-ud80sst45po348ohec2h33t2m6mjnlt0:/oauth2redirect",
|
||||
"end_session_redirect_uri": "com.googleusercontent.apps.165555585193-ud80sst45po348ohec2h33t2m6mjnlt0:/oauth2redirect",
|
||||
"authorization_scope": "openid email profile https://www.googleapis.com/auth/drive.appdata",
|
||||
"discovery_uri": "https://accounts.google.com/.well-known/openid-configuration",
|
||||
"authorization_endpoint_uri": "",
|
||||
"token_endpoint_uri": "",
|
||||
"registration_endpoint_uri": "",
|
||||
"user_info_endpoint_uri": "",
|
||||
"https_required": true
|
||||
}
|
@ -2,5 +2,5 @@
|
||||
<string name="app_name">HeadlessCocos</string>
|
||||
<string name="game_view_content_description">Game view</string>
|
||||
<string name="permission_camera">Scan QRCode need camera permission</string>
|
||||
<string name="default_web_client_id">165555585193-ud80sst45po348ohec2h33t2m6mjnlt0.apps.googleusercontent.com</string>
|
||||
<string name="default_web_client_id">165555585193-j22geb66hjku5ns5lq5voaj4v7811cl7.apps.googleusercontent.com</string>
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user