Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 65 additions & 17 deletions app/java/net/openid/appauthdemo/TokenActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package net.openid.appauthdemo;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
Expand All @@ -38,6 +39,8 @@
import net.openid.appauth.AuthorizationServiceDiscovery;
import net.openid.appauth.ClientAuthentication;
import net.openid.appauth.EndSessionRequest;
import net.openid.appauth.LogoutRequest;
import net.openid.appauth.LogoutService;
import net.openid.appauth.TokenRequest;
import net.openid.appauth.TokenResponse;
import okio.Okio;
Expand Down Expand Up @@ -68,6 +71,7 @@ public class TokenActivity extends AppCompatActivity {
private static final String KEY_USER_INFO = "userInfo";

private static final int END_SESSION_REQUEST_CODE = 911;
private static final String EXTRA_AUTH_SERVICE_DISCOVERY = "authServiceDiscovery";

private AuthorizationService mAuthService;
private AuthStateManager mStateManager;
Expand Down Expand Up @@ -325,11 +329,7 @@ private void handleCodeExchangeResponse(
}
}

/**
* Demonstrates the use of {@link AuthState#performActionWithFreshTokens} to retrieve
* user info from the IDP's user info endpoint. This callback will negotiate a new access
* token / id token for use in a follow-up action, or provide an error if this fails.
*/

@MainThread
private void fetchUserInfo() {
displayLoading("Fetching user info");
Expand Down Expand Up @@ -420,21 +420,69 @@ private void endSession() {
startActivityForResult(endSessionEnten, END_SESSION_REQUEST_CODE);
}


static AuthorizationServiceDiscovery getDiscoveryDocFromIntent(Intent intent) {
if (!intent.hasExtra(EXTRA_AUTH_SERVICE_DISCOVERY)) {
return null;
}
String discoveryJson = intent.getStringExtra(EXTRA_AUTH_SERVICE_DISCOVERY);
try {
return new AuthorizationServiceDiscovery(new JSONObject(discoveryJson));
} catch (JSONException | AuthorizationServiceDiscovery.MissingArgumentException ex) {
throw new IllegalStateException("Malformed JSON in discovery doc");
}
}

@MainThread
private void signOut() {
// discard the authorization and token state, but retain the configuration and
// dynamic client registration (if applicable), to save from retrieving them again.
AuthState currentState = mStateManager.getCurrent();
AuthState clearedState =
new AuthState(currentState.getAuthorizationServiceConfiguration());
if (currentState.getLastRegistrationResponse() != null) {
clearedState.update(currentState.getLastRegistrationResponse());
// // discard the authorization and token state, but retain the configuration and
// // dynamic client registration (if applicable), to save from retrieving them again.
// AuthState currentState = mStateManager.getCurrent();
// AuthState clearedState =
// new AuthState(currentState.getAuthorizationServiceConfiguration());
// if (currentState.getLastRegistrationResponse() != null) {
// clearedState.update(currentState.getLastRegistrationResponse());
// }
// mStateManager.replace(clearedState);
//
// Intent mainIntent = new Intent(this, LoginActivity.class);
// mainIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// startActivity(mainIntent);
// finish();

AuthState mAuthState = mStateManager.getCurrent();

if (mAuthState.getAuthorizationServiceConfiguration() == null) {
Log.e(TAG, "Cannot make userInfo request without service configuration");
}
mStateManager.replace(clearedState);

Intent mainIntent = new Intent(this, LoginActivity.class);
mainIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(mainIntent);
finish();
mAuthState.performActionWithFreshTokens(mAuthService, new AuthState.AuthStateAction() {
@Override
public void execute(String accessToken, String idToken, AuthorizationException ex) {
if (ex != null) {
Log.e(TAG, "Token refresh failed when fetching user info");
return;
}

AuthorizationServiceDiscovery discoveryDoc = getDiscoveryDocFromIntent(getIntent());
if (discoveryDoc == null) {
throw new IllegalStateException("no available discovery doc");
}

Uri endSessionEndpoint = Uri.parse(discoveryDoc.getEndSessionEndpoint().toString());

String logoutUri = getResources().getString(R.string.keycloak_auth_logout_uri);
LogoutRequest logoutRequest = new LogoutRequest(endSessionEndpoint,
Uri.parse(logoutUri));

LogoutService logoutService = new LogoutService(TokenActivity.this);
logoutService.performLogoutRequest(
logoutRequest,
PendingIntent.getActivity(
TokenActivity.this, logoutRequest.hashCode(),
new Intent(TokenActivity.this, LoginActivity.class), 0)
);
}
});
}
}
15 changes: 8 additions & 7 deletions app/res/raw/auth_config.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"client_id": "",
"redirect_uri": "net.openid.appauthdemo:/oauth2redirect",
"end_session_uri":"",
"client_id": "truMobile_Android",
"prompt": "login",
"redirect_uri": "net.openid.appauthdemo:/oauthredirect",
"end_session_uri":"https://secure.trufla.dev/auth/realms/sharp/protocol/openid-connect/logout",
"authorization_scope": "openid email profile",
"discovery_uri": "",
"authorization_endpoint_uri": "",
"token_endpoint_uri": "",
"registration_endpoint_uri": "",
"user_info_endpoint_uri": "",
"authorization_endpoint_uri": "https://secure.trufla.dev/auth/realms/Sharp/protocol/openid-connect/auth",
"token_endpoint_uri": "https://secure.trufla.dev/auth/realms/Sharp/protocol/openid-connect/token",
"registration_endpoint_uri": "https://secure.trufla.dev/auth/realms/Sharp/clients-registrations/openid-connect",
"user_info_endpoint_uri": "https://secure.trufla.dev/auth/realms/Sharp/protocol/openid-connect/userinfo",
"https_required": true
}
26 changes: 26 additions & 0 deletions app/res/values/idp_configs.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
This contains the authorization service configuration details that are used to demonstrate
authentication. By default, all authorization services are disabled until you modify this file
to provide your own configuration details.
-->
<eat-comment/>

<bool name="google_enabled">false</bool>
<string name="google_client_id" translatable="false">YOUR_ID.apps.googleusercontent.com</string>
<bool name="keycloak_enabled">true</bool>

<string name="keycloak_client_id" translatable="false">demo-test</string>
<!--
NOTE: This scheme is automatically provisioned by Google for Android OAuth2 clients, and is
the reverse form of the client ID registered above. Handling of this scheme is registered in an
intent filter in the app's manifest.
-->
<string name="google_auth_redirect_scheme" translatable="false">com.googleusercontent.apps.YOUR_ID</string>
<string name="google_auth_redirect_uri" translatable="false">com.googleusercontent.apps.YOUR_ID:/oauth2redirect</string>
<string name="keycloak_auth_redirect_scheme" translatable="false">com.test.demo</string>
<string name="keycloak_auth_logout_scheme" translatable="false">com.test.logout</string>
<string name="keycloak_auth_redirect_uri" translatable="false">com.test.demo:/oauth2Callback</string>
<string name="keycloak_auth_logout_uri" translatable="false">"com.test.logout:/oauth2Callback"</string>
</resources>
1 change: 1 addition & 0 deletions app/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@
<string name="access_token_expired">Access token has expired</string>
<string name="auth_settings">Authorization settings in use:</string>
<string name="use_pending_intents">Use PendingIntent\'s for completion</string>
<string name="logout">logout</string>
</resources>
16 changes: 15 additions & 1 deletion library/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,21 @@
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="${appAuthRedirectScheme}"/>
<data android:scheme="@string/keycloak_auth_redirect_scheme"/>
</intent-filter>
</activity>

<!-- Callback from logout screen -->
<activity android:name="net.openid.appauth.LogoutUriReceiverActivity">
<!--
Filter which captures custom scheme based redirects for Google authorization
requests.
-->
<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="@string/keycloak_auth_logout_scheme"/>
</intent-filter>
</activity>
</application>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -617,4 +617,6 @@ private static StringListField strList(String key, List<String> defaults) {
private static BooleanField bool(String key, boolean defaultValue) {
return new BooleanField(key, defaultValue);
}


}
123 changes: 123 additions & 0 deletions library/java/net/openid/appauth/BrowserHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package net.openid.appauth;

import android.content.ComponentName;
import android.content.Context;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.browser.customtabs.CustomTabsClient;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.browser.customtabs.CustomTabsServiceConnection;
import androidx.browser.customtabs.CustomTabsSession;

import net.openid.appauth.internal.Logger;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
* Created by Marina Wageed on 22,September,2020
* Trufla Technology,
* Cairo, Egypt.
*/
class BrowserHandler
{

/**
* Wait for at most this amount of time for the browser connection to be established.
*/
private static final long CLIENT_WAIT_TIME = 1L;

@NonNull
private final Context mContext;

@NonNull
private final String mBrowserPackage;

@Nullable
private final CustomTabsServiceConnection mConnection;

@NonNull
private final AtomicReference<CustomTabsClient> mClient;

@NonNull
private final CountDownLatch mClientLatch;

BrowserHandler(@NonNull Context context) {
mContext = context;
mBrowserPackage = BrowserPackageHelper.getInstance().getPackageNameToUse(context);
mClient = new AtomicReference<>();
mClientLatch = new CountDownLatch(1);
mConnection = bindCustomTabsService();
}

private CustomTabsServiceConnection bindCustomTabsService() {
CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName componentName) {
Logger.debug("CustomTabsService is disconnected");
setClient(null);
}

@Override
public void onCustomTabsServiceConnected(ComponentName componentName,
CustomTabsClient customTabsClient) {
Logger.debug("CustomTabsService is connected");
customTabsClient.warmup(0);
setClient(customTabsClient);
}

private void setClient(@Nullable CustomTabsClient client) {
mClient.set(client);
mClientLatch.countDown();
}
};

if (!CustomTabsClient.bindCustomTabsService(
mContext,
mBrowserPackage,
connection)) {
// this is expected if the browser does not support custom tabs
Logger.info("Unable to bind custom tabs service");
mClientLatch.countDown();
return null;
}

return connection;
}

public CustomTabsIntent.Builder createCustomTabsIntentBuilder() {
return new CustomTabsIntent.Builder(createSession());
}

public String getBrowserPackage() {
return mBrowserPackage;
}

public void unbind() {
if (mConnection == null) {
return;
}

mContext.unbindService(mConnection);
mClient.set(null);
Logger.debug("CustomTabsService is disconnected");
}

private CustomTabsSession createSession() {
try {
mClientLatch.await(CLIENT_WAIT_TIME, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Logger.info("Interrupted while waiting for browser connection");
mClientLatch.countDown();
}

CustomTabsClient client = mClient.get();
if (client != null) {
return client.newSession(null);
}

return null;
}
}
Loading