Skip to content

Commit

Permalink
SDKS-2631 Android App Integrity Implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
witrisna committed Oct 24, 2023
1 parent 623241d commit a243317
Show file tree
Hide file tree
Showing 27 changed files with 988 additions and 176 deletions.
11 changes: 11 additions & 0 deletions forgerock-auth/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ android {
viewBinding true
}

kotlinOptions {
freeCompilerArgs = ['-Xjvm-default=all']
}

}

apply from: '../config/logger.gradle'
Expand Down Expand Up @@ -124,6 +128,9 @@ dependencies {
compileOnly 'com.google.android.gms:play-services-auth:20.6.0'
compileOnly 'com.facebook.android:facebook-login:16.0.0'

//For App integrity
compileOnly 'com.google.android.play:integrity:1.2.0'

androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'com.squareup.okhttp:mockwebserver:2.7.5'
Expand Down Expand Up @@ -164,6 +171,10 @@ dependencies {
//Application Pin
testImplementation 'com.madgag.spongycastle:bcpkix-jdk15on:1.58.0.0'
testImplementation 'androidx.security:security-crypto:1.1.0-alpha06'

//App Integrity
testImplementation 'com.google.android.play:integrity:1.2.0'

testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.2'

testImplementation 'org.mockito:mockito-core:4.8.1'
Expand Down
62 changes: 42 additions & 20 deletions forgerock-auth/src/main/java/org/forgerock/android/auth/Node.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 ForgeRock. All rights reserved.
* Copyright (c) 2019 - 2023 ForgeRock. All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
Expand All @@ -18,11 +18,6 @@
import java.io.Serializable;
import java.util.List;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public class Node implements Serializable {

public static final String AUTH_ID = "authId";
Expand All @@ -38,6 +33,15 @@ public class Node implements Serializable {
private final String authServiceId;
private final List<Callback> callbacks;

public Node(String authId, String stage, String header, String description, String authServiceId, List<Callback> callbacks) {
this.authId = authId;
this.stage = stage;
this.header = header;
this.description = description;
this.authServiceId = authServiceId;
this.callbacks = callbacks;
}

/**
* Returns {@link JSONObject} mapping of the object
*
Expand Down Expand Up @@ -85,20 +89,19 @@ public List<Callback> getCallbacks() {
*
* @param context The Application Context
* @param listener Listener for receiving {@link AuthService} related changes
* <b> {@link NodeListener#onSuccess(Object)} on success login.
* <b> {@link NodeListener#onCallbackReceived(Node)} step to the next node, {@link Node} is returned.
* <b> throws {@link IllegalStateException} when the tree is invalid, e.g the authentication tree has been completed.
* <b> throws {@link org.forgerock.android.auth.exception.AuthenticationException} when server returns {@link java.net.HttpURLConnection#HTTP_UNAUTHORIZED}
* <b> throws {@link org.forgerock.android.auth.exception.ApiException} When server return errors.
* <b> throws {@link javax.security.auth.callback.UnsupportedCallbackException}
* When {@link org.forgerock.android.auth.callback.Callback} returned from Server is not supported by the SDK.
* <b> throws {@link org.forgerock.android.auth.exception.SuspendedAuthSessionException} When Suspended ID timeout
* <b> throws {@link org.forgerock.android.auth.exception.AuthenticationTimeoutException} When Authentication tree timeout
* <b> throws {@link org.json.JSONException} when failed to parse server response as JSON String.
* <b> throws {@link IOException } When there is any network error.
* <b> throws {@link java.net.MalformedURLException} When failed to parse the URL for API request.
* <b> throws {@link NoSuchMethodException} or {@link SecurityException} When failed to initialize the Callback class.
* <b> {@link NodeListener#onSuccess(Object)} on success login.
* <b> {@link NodeListener#onCallbackReceived(Node)} step to the next node, {@link Node} is returned.
* <b> throws {@link IllegalStateException} when the tree is invalid, e.g the authentication tree has been completed.
* <b> throws {@link org.forgerock.android.auth.exception.AuthenticationException} when server returns {@link java.net.HttpURLConnection#HTTP_UNAUTHORIZED}
* <b> throws {@link org.forgerock.android.auth.exception.ApiException} When server return errors.
* <b> throws {@link javax.security.auth.callback.UnsupportedCallbackException}
* When {@link org.forgerock.android.auth.callback.Callback} returned from Server is not supported by the SDK.
* <b> throws {@link org.forgerock.android.auth.exception.SuspendedAuthSessionException} When Suspended ID timeout
* <b> throws {@link org.forgerock.android.auth.exception.AuthenticationTimeoutException} When Authentication tree timeout
* <b> throws {@link org.json.JSONException} when failed to parse server response as JSON String.
* <b> throws {@link IOException } When there is any network error.
* <b> throws {@link java.net.MalformedURLException} When failed to parse the URL for API request.
* <b> throws {@link NoSuchMethodException} or {@link SecurityException} When failed to initialize the Callback class.
*/
public void next(Context context, NodeListener<?> listener) {
AuthService.goToNext(context, this, listener);
Expand All @@ -123,4 +126,23 @@ public void setCallback(Callback callback) {
}
}

public String getAuthId() {
return this.authId;
}

public String getStage() {
return this.stage;
}

public String getHeader() {
return this.header;
}

public String getDescription() {
return this.description;
}

public String getAuthServiceId() {
return this.authServiceId;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019 ForgeRock. All rights reserved.
* Copyright (c) 2019 - 2023 ForgeRock. All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
Expand All @@ -9,6 +9,8 @@

import android.content.Context;

import androidx.annotation.NonNull;

import java.util.List;

/**
Expand All @@ -22,7 +24,7 @@ class NodeInterceptorHandler extends InterceptorHandler implements NodeListener<
}

@Override
public void onCallbackReceived(Node node) {
public void onCallbackReceived(@NonNull Node node) {
((NodeListener<?>)getListener()).onCallbackReceived(node);
}

Expand All @@ -32,9 +34,7 @@ public void onSuccess(SSOToken result) {
}

@Override
public void onException(Exception e) {
public void onException(@NonNull Exception e) {
getListener().onException(e);
}


}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright (c) 2019 - 2023 ForgeRock. All rights reserved.
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
package org.forgerock.android.auth

import org.forgerock.android.auth.Logger.Companion.debug
import org.forgerock.android.auth.callback.Callback
import org.forgerock.android.auth.callback.CallbackFactory
import org.forgerock.android.auth.callback.DerivableCallback
import org.forgerock.android.auth.callback.MetadataCallback
import org.forgerock.android.auth.callback.NodeAware
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import javax.security.auth.callback.UnsupportedCallbackException

/**
* Interface for an object that listens to changes resulting from a [AuthService].
*/
interface NodeListener<T> : FRListener<T> {
/**
* Notify the listener that the [AuthService] has been started and moved to the first node.
*
* @param node The first Node
*/
fun onCallbackReceived(node: Node)

/**
* Transform the response from AM Intelligent Tree to Node Object, after the transformation
* [.onCallbackReceived] will be invoked with the returned [Node].
*
* @param authServiceId Unique Auth Service Id
* @param response The JSON Response from AM Intelligent Tree
* @return The Node Object
* @throws Exception Any error during the transformation
*/
@Throws(Exception::class)
fun onCallbackReceived(authServiceId: String,
response: JSONObject): Node {
val callbacks =
parseCallback(response.getJSONArray("callbacks"))
val node = Node(response.getString(Node.AUTH_ID),
response.optString(Node.STAGE, getStage(callbacks)),
response.optString(Node.HEADER, null),
response.optString(Node.DESCRIPTION, null),
authServiceId,
callbacks)
callbacks.forEach {
if (it is NodeAware) {
it.setNode(node)
}
}
return node
}

/**
* Parse the JSON Array callback response from AM, and transform to [Callback] instances.
*
* @param jsonArray The JSON Array callback response from AM
* @return A List of [Callback] Object
* @throws Exception Any error during the transformation
*/
@Throws(Exception::class)
fun parseCallback(jsonArray: JSONArray): List<Callback> {
val callbacks: MutableList<Callback> = ArrayList()
for (i in 0 until jsonArray.length()) {
val cb = jsonArray.getJSONObject(i)
val type = cb.getString("type")
// Return the Callback Class which represent the Callback from AM
val clazz = CallbackFactory.getInstance().callbacks[type]
?: //When Callback is not registered to the SDK
throw UnsupportedCallbackException(null,
"Callback Type Not Supported: " + cb.getString("type"))
var callback =
clazz.getConstructor(JSONObject::class.java, Int::class.javaPrimitiveType)
.newInstance(cb, i)
if (callback is DerivableCallback) {
val derivedClass = (callback as DerivableCallback).derivedCallback
if (derivedClass != null) {
callback = derivedClass.getConstructor(JSONObject::class.java,
Int::class.javaPrimitiveType).newInstance(cb, i)
} else {
debug(TAG, "Derive class not found.")
}
}
callbacks.add(callback)
}
return callbacks
}

/**
* Workaround stage property for AM version < 7.0.
* https://github.com/jaredjensen/forgerock-sdk-blog/blob/master/auth_tree_stage.md
*
* @param callbacks Callback from Intelligent Tree
* @return stage or null if not found.
*/
fun getStage(callbacks: List<Callback>): String? {
for (callback in callbacks) {
if (callback.javaClass == MetadataCallback::class.java) {
try {
return (callback as MetadataCallback).value.getString("stage")
} catch (e: JSONException) {
//ignore and continue to find the next metadata callback.
}
}
}
return null
}

companion object {
@JvmField
val TAG = NodeListener::class.java.simpleName
}
}
Loading

0 comments on commit a243317

Please sign in to comment.