From 47f5ed729c0d7746383b6ccbb264d3b73bc820e8 Mon Sep 17 00:00:00 2001 From: andy witrisna Date: Thu, 4 Jan 2024 10:56:21 -0800 Subject: [PATCH 1/8] Update doc for AppIntegrityCallback --- .../auth/callback/AppIntegrityCallback.kt | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/AppIntegrityCallback.kt b/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/AppIntegrityCallback.kt index df20dcbb..5417b255 100644 --- a/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/AppIntegrityCallback.kt +++ b/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/AppIntegrityCallback.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 ForgeRock. All rights reserved. + * Copyright (c) 2023-2024 ForgeRock. All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. @@ -38,7 +38,7 @@ import kotlin.time.toDuration private val TAG = AppIntegrityCallback::class.java.simpleName /** - * Callback to collect the device binding information + * Callback to collect integrity token. */ open class AppIntegrityCallback : NodeAware, AbstractCallback { @@ -55,8 +55,14 @@ open class AppIntegrityCallback : NodeAware, AbstractCallback { val cache = ConcurrentHashMap() } + /** + * The [Node] that associate with this Callback + */ private lateinit var node: Node + /** + * The request type + */ lateinit var requestType: RequestType private set @@ -93,7 +99,7 @@ open class AppIntegrityCallback : NodeAware, AbstractCallback { /** * Input the Client Error to the server - * @param value DeviceBind ErrorType . + * @param value Error String. */ fun setClientError(value: String) { super.setValue(value, 1) @@ -128,7 +134,7 @@ open class AppIntegrityCallback : NodeAware, AbstractCallback { } /** - * Bind the device. + * Request the integrity token * * @param context The Application Context */ @@ -222,6 +228,10 @@ open class AppIntegrityCallback : NodeAware, AbstractCallback { } +/** + * The Request Type, please see https://developer.android.com/google/play/integrity/overview for + * detail. + */ enum class RequestType { CLASSIC, STANDARD; From 8d749de646cc5bde5683387efb80ab184b753ee7 Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Thu, 25 Jan 2024 18:22:20 -0800 Subject: [PATCH 2/8] Added test coverage for the "non active" user and non-existing user. --- .../auth/callback/BaseDeviceBindingTest.java | 2 +- .../callback/DeviceBindingCallbackTest.java | 52 +++++++++++ .../DeviceSigningVerifierCallbackTest.java | 88 ++++++++++++++++--- 3 files changed, 131 insertions(+), 11 deletions(-) diff --git a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/BaseDeviceBindingTest.java b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/BaseDeviceBindingTest.java index 9d297f92..e02243b9 100644 --- a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/BaseDeviceBindingTest.java +++ b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/BaseDeviceBindingTest.java @@ -31,7 +31,7 @@ public abstract class BaseDeviceBindingTest { protected static Context context = ApplicationProvider.getApplicationContext(); // This test uses dynamic configuration with the following settings: - protected final static String AM_URL = "https://openam-sdks.forgeblocks.com/am"; + protected final static String AM_URL = "https://openam-spetrov.forgeblocks.com/am"; protected final static String REALM = "alpha"; protected final static String OAUTH_CLIENT = "AndroidTest"; protected final static String OAUTH_REDIRECT_URI = "org.forgerock.demo:/oauth2redirect"; diff --git a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/DeviceBindingCallbackTest.java b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/DeviceBindingCallbackTest.java index af9c6bfd..64503806 100644 --- a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/DeviceBindingCallbackTest.java +++ b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/DeviceBindingCallbackTest.java @@ -346,6 +346,58 @@ public void onException(Exception e) { Assert.assertNotNull(FRSession.getCurrentSession()); Assert.assertNotNull(FRSession.getCurrentSession().getSessionToken()); } + + /* + * Make sure that when user does NOT exist, the Device Binding node triggers the failure outcome (SDKS-2935) + */ + @Test + public void testDeviceBindingUnknownUser() throws ExecutionException, InterruptedException { + final int[] hit = {0}; + final int[] failureOutcome = {0}; + NodeListenerFuture nodeListenerFuture = new DeviceBindingNodeListener(context, "default") + { + @Override + public void onCallbackReceived(Node node) + { + if (node.getCallback(DeviceSigningVerifierCallback.class) != null) { + DeviceSigningVerifierCallback callback = node.getCallback(DeviceSigningVerifierCallback.class); + + Assertions.fail("Test failed: Received unexpected DeviceSigningVerifierCallback! (see SDKS-2169)" ); + return; + } + if (node.getCallback(NameCallback.class) != null) { + hit[0]++; + node.getCallback(NameCallback.class).setName("UNKNOWN-USER"); + node.next(context, this); + return; + } + // Make sure that the "Failure" outcome has been triggered + if (node.getCallback(TextOutputCallback.class) != null) { + TextOutputCallback textOutputCallback = node.getCallback(TextOutputCallback.class); + assertThat(textOutputCallback.getMessage()).isEqualTo("Device Binding Failed"); + failureOutcome[0]++; + + node.next(context, this); + return; + } + + super.onCallbackReceived(node); + } + }; + + FRSession.authenticate(context, TREE, nodeListenerFuture); + + // Ensure that the journey finishes with failure + thrown.expect(java.util.concurrent.ExecutionException.class); + thrown.expectMessage("ApiException{statusCode=401, error='', description='{\"code\":401,\"reason\":\"Unauthorized\",\"message\":\"Login failure\"}'}"); + + Assert.assertNull(nodeListenerFuture.get()); + Assert.assertNull(FRSession.getCurrentSession()); + Assert.assertNull(FRSession.getCurrentSession().getSessionToken()); + + assertThat(hit[0]).isEqualTo(1); + assertThat(failureOutcome[0]).isEqualTo(1); + } } diff --git a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/DeviceSigningVerifierCallbackTest.java b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/DeviceSigningVerifierCallbackTest.java index e32a0b07..bc175efd 100644 --- a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/DeviceSigningVerifierCallbackTest.java +++ b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/DeviceSigningVerifierCallbackTest.java @@ -17,7 +17,6 @@ import com.nimbusds.jwt.JWTParser; import org.assertj.core.api.Assertions; -import org.assertj.core.api.ClassAssert; import org.forgerock.android.auth.FRListener; import org.forgerock.android.auth.FRSession; import org.forgerock.android.auth.Logger; @@ -92,9 +91,13 @@ public void onException(Exception e) { } } + /* + * When user does NOT exist in AM, the node should trigger the "Failure" outcome (SDKS-2935) + */ @Test public void testDeviceSigningVerifierUnknownUserError() throws ExecutionException, InterruptedException { final int[] hit = {0}; + final int[] failureOutcome = {0}; NodeListenerFuture nodeListenerFuture = new DeviceSigningVerifierNodeListener(context, "default") { @Override @@ -112,6 +115,15 @@ public void onCallbackReceived(Node node) node.next(context, this); return; } + // Make sure that the "Failure" outcome has been triggered + if (node.getCallback(TextOutputCallback.class) != null) { + TextOutputCallback textOutputCallback = node.getCallback(TextOutputCallback.class); + assertThat(textOutputCallback.getMessage()).isEqualTo("Failure"); + failureOutcome[0]++; + + node.next(context, this); + return; + } super.onCallbackReceived(node); } @@ -119,15 +131,11 @@ public void onCallbackReceived(Node node) FRSession.authenticate(context, TREE, nodeListenerFuture); - // Ensure that the journey finishes with failure - thrown.expect(java.util.concurrent.ExecutionException.class); - thrown.expectMessage("ApiException{statusCode=401, error='', description='{\"code\":401,\"reason\":\"Unauthorized\",\"message\":\"Login failure\"}'}"); - - Assert.assertNull(nodeListenerFuture.get()); - Assert.assertNull(FRSession.getCurrentSession()); - Assert.assertNull(FRSession.getCurrentSession().getSessionToken()); - + Assert.assertNotNull(nodeListenerFuture.get()); + Assert.assertNotNull(FRSession.getCurrentSession()); + Assert.assertNotNull(FRSession.getCurrentSession().getSessionToken()); assertThat(hit[0]).isEqualTo(1); + assertThat(failureOutcome[0]).isEqualTo(1); } @Test @@ -891,7 +899,7 @@ public void onException(Exception e) { return; } - // Make sure that by default upon + // Make sure that the "Abort" outcome has been triggered if (node.getCallback(TextOutputCallback.class) != null) { TextOutputCallback textOutputCallback = node.getCallback(TextOutputCallback.class); assertThat(textOutputCallback.getMessage()).isEqualTo("Abort"); @@ -913,4 +921,64 @@ public void onException(Exception e) { Assert.assertNotNull(FRSession.getCurrentSession().getSessionToken()); } + /* + * Make sure that when user's account is not active, + * the Inactive User outcome is triggered (when enabled...) (SDKS-2935) + */ + @Test + public void testDeviceVerificationInactiveUser() throws ExecutionException, InterruptedException { + final int[] signCallback = {0}; + final int[] inactiveUserOutcome = {0}; + + NodeListenerFuture nodeListenerFuture = new DeviceSigningVerifierNodeListener(context, "inactive-user") + { + final NodeListener nodeListener = this; + + @Override + public void onCallbackReceived(Node node) + { + if (node.getCallback(DeviceSigningVerifierCallback.class) != null) { + DeviceSigningVerifierCallback callback = node.getCallback(DeviceSigningVerifierCallback.class); + + signCallback[0]++; + callback.sign(context, new FRListener() { + @Override + public void onSuccess(Void result) { + + node.next(context, nodeListener); + } + @Override + public void onException(Exception e) { + node.next(context, nodeListener); + } + }); + + return; + } + // Make sure that the "Inactive User" outcome has been triggered + if (node.getCallback(TextOutputCallback.class) != null) { + TextOutputCallback textOutputCallback = node.getCallback(TextOutputCallback.class); + assertThat(textOutputCallback.getMessage()).isEqualTo("Inactive User"); + inactiveUserOutcome[0]++; + + node.next(context, nodeListener); + return; + } + + super.onCallbackReceived(node); + } + }; + + FRSession.authenticate(context, TREE, nodeListenerFuture); + Assert.assertNotNull(nodeListenerFuture.get()); + + // Make sure that the node didn't return a callback, and the "inactive user" outcome was triggered. + assertThat(signCallback[0]).isEqualTo(0); + assertThat(inactiveUserOutcome[0]).isEqualTo(1); + + // Ensure that the journey finishes with success + Assert.assertNotNull(FRSession.getCurrentSession()); + Assert.assertNotNull(FRSession.getCurrentSession().getSessionToken()); + + } } From 6935273512a959efa82011433197da3b844b80bb Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Tue, 30 Jan 2024 16:41:50 -0800 Subject: [PATCH 3/8] Updated DeviceSigningVerifier e2e test cases to the latest node changes. --- .../CustomDeviceSigningVerifierCallback.kt | 8 +- .../DeviceSigningVerifierCallbackTest.java | 182 ++++++++++++++++-- 2 files changed, 170 insertions(+), 20 deletions(-) diff --git a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/CustomDeviceSigningVerifierCallback.kt b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/CustomDeviceSigningVerifierCallback.kt index 59ee8929..d5e25a66 100644 --- a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/CustomDeviceSigningVerifierCallback.kt +++ b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/CustomDeviceSigningVerifierCallback.kt @@ -7,6 +7,8 @@ package org.forgerock.android.auth.callback +import android.content.Context +import androidx.test.core.app.ApplicationProvider import com.nimbusds.jose.JOSEObjectType import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.JWSHeader @@ -16,7 +18,8 @@ import com.nimbusds.jwt.SignedJWT import org.json.JSONObject import java.security.KeyPairGenerator import java.security.interfaces.RSAPrivateKey -import java.util.* +import java.util.Calendar +import java.util.Date class CustomDeviceSigningVerifierCallback : DeviceSigningVerifierCallback { constructor() : super() @@ -41,6 +44,9 @@ class CustomDeviceSigningVerifierCallback : DeviceSigningVerifierCallback { val header = JWSHeader.Builder(JWSAlgorithm.RS512).type(JOSEObjectType.JWT).keyID(kid).build() val payload = JWTClaimsSet.Builder().subject(sub).claim("challenge", challenge) + .issuer(ApplicationProvider.getApplicationContext().packageName) + .issueTime(Calendar.getInstance().time) + .notBeforeTime(Calendar.getInstance().time) .expirationTime(getExpiration(null)).build() val signedJWT = SignedJWT(header, payload) signedJWT.sign(RSASSASigner(rsaKey.private as RSAPrivateKey)) diff --git a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/DeviceSigningVerifierCallbackTest.java b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/DeviceSigningVerifierCallbackTest.java index bc175efd..ea380204 100644 --- a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/DeviceSigningVerifierCallbackTest.java +++ b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/DeviceSigningVerifierCallbackTest.java @@ -92,12 +92,13 @@ public void onException(Exception e) { } /* - * When user does NOT exist in AM, the node should trigger the "Failure" outcome (SDKS-2935) + * When user does NOT exist in AM, the node triggers the "Failure" outcome with reason "INVALID_USER" (SDKS-2935) */ @Test public void testDeviceSigningVerifierUnknownUserError() throws ExecutionException, InterruptedException { final int[] hit = {0}; final int[] failureOutcome = {0}; + final int [] failureValueReceived = {0}; NodeListenerFuture nodeListenerFuture = new DeviceSigningVerifierNodeListener(context, "default") { @Override @@ -125,17 +126,35 @@ public void onCallbackReceived(Node node) return; } + // The test tree is configured to send the `DeviceSigningVerifierNode.FAILURE` value in a HiddenValue callback + if (node.getCallback(HiddenValueCallback.class) != null) { + HiddenValueCallback callback = node.getCallback(HiddenValueCallback.class); + assertThat(callback.getId()).isEqualTo("DeviceSigningVerifierFailure"); + assertThat(callback.getValue()).isEqualTo("\"INVALID_USER\""); + failureValueReceived[0]++; + + node.next(context, this ); + return; + } + super.onCallbackReceived(node); } }; FRSession.authenticate(context, TREE, nodeListenerFuture); - Assert.assertNotNull(nodeListenerFuture.get()); - Assert.assertNotNull(FRSession.getCurrentSession()); - Assert.assertNotNull(FRSession.getCurrentSession().getSessionToken()); + // Ensure that the journey finishes with failure + try { + Assert.assertNull(nodeListenerFuture.get()); + } catch (ExecutionException e) { + assertThat(e.getMessage()).isEqualTo("ApiException{statusCode=401, error='', description='{\"code\":401,\"reason\":\"Unauthorized\",\"message\":\"Login failure\"}'}"); + } catch (InterruptedException e) { + } + Assert.assertNull(FRSession.getCurrentSession()); + assertThat(hit[0]).isEqualTo(1); assertThat(failureOutcome[0]).isEqualTo(1); + assertThat(failureValueReceived[0]).isEqualTo(1); } @Test @@ -474,6 +493,7 @@ public void onException(Exception e) { public void testDeviceVerificationFailureExpiredJwt() throws ExecutionException, InterruptedException { final int[] signSuccess = {0}; final int[] failureOutcome = {0}; + final int [] failureValueReceived = {0}; CallbackFactory.getInstance().register(CustomDeviceSigningVerifierCallback.class); @@ -511,6 +531,17 @@ public void onException(Exception e) { return; } + // The test tree is configured to send the `DeviceSigningVerifierNode.FAILURE` value in a HiddenValue callback + if (node.getCallback(HiddenValueCallback.class) != null) { + HiddenValueCallback callback = node.getCallback(HiddenValueCallback.class); + assertThat(callback.getId()).isEqualTo("DeviceSigningVerifierFailure"); + assertThat(callback.getValue()).isEqualTo("\"INVALID_CLAIM\""); + failureValueReceived[0]++; + + node.next(context, this ); + return; + } + super.onCallbackReceived(node); } }; @@ -519,6 +550,7 @@ public void onException(Exception e) { Assert.assertNotNull(nodeListenerFuture.get()); assertThat(signSuccess[0]).isEqualTo(1); assertThat(failureOutcome[0]).isEqualTo(1); + assertThat(failureValueReceived[0]).isEqualTo(1); // Ensure that the journey finishes with success Assert.assertNotNull(FRSession.getCurrentSession()); @@ -528,6 +560,7 @@ public void onException(Exception e) { @Test public void testDeviceVerificationFailureInvalidChallenge() throws ExecutionException, InterruptedException { final int[] failureOutcome = {0}; + final int[] failureValueReceived = {0}; CallbackFactory.getInstance().register(CustomDeviceSigningVerifierCallback.class); NodeListenerFuture nodeListenerFuture = new DeviceSigningVerifierNodeListener(context, "default") @@ -556,6 +589,17 @@ public void onCallbackReceived(Node node) return; } + // The test tree is configured to send the `DeviceSigningVerifierNode.FAILURE` value in a HiddenValue callback + if (node.getCallback(HiddenValueCallback.class) != null) { + HiddenValueCallback callback = node.getCallback(HiddenValueCallback.class); + assertThat(callback.getId()).isEqualTo("DeviceSigningVerifierFailure"); + assertThat(callback.getValue()).isEqualTo("\"INVALID_CLAIM\""); + failureValueReceived[0]++; + + node.next(context, this ); + return; + } + super.onCallbackReceived(node); } }; @@ -564,6 +608,7 @@ public void onCallbackReceived(Node node) Assert.assertNotNull(nodeListenerFuture.get()); assertThat(failureOutcome[0]).isEqualTo(1); + assertThat(failureValueReceived[0]).isEqualTo(1); // Ensure that the journey finishes with success Assert.assertNotNull(FRSession.getCurrentSession()); @@ -617,7 +662,8 @@ public void onCallbackReceived(Node node) @Test public void testDeviceVerificationFailureInvalidSignKey() throws ExecutionException, InterruptedException { - final int[] authSuccess = {0}; + final int[] failureOutcome = {0}; + final int[] failureValueReceived = {0}; CallbackFactory.getInstance().register(CustomDeviceSigningVerifierCallback.class); NodeListenerFuture nodeListenerFuture = new DeviceSigningVerifierNodeListener(context, "default") @@ -641,19 +687,31 @@ public void onCallbackReceived(Node node) if (node.getCallback(TextOutputCallback.class) != null) { TextOutputCallback textOutputCallback = node.getCallback(TextOutputCallback.class); assertThat(textOutputCallback.getMessage()).isEqualTo("Failure"); - authSuccess[0]++; + failureOutcome[0]++; node.next(context, nodeListener); return; } + // The test tree is configured to send the `DeviceSigningVerifierNode.FAILURE` value in a HiddenValue callback + if (node.getCallback(HiddenValueCallback.class) != null) { + HiddenValueCallback callback = node.getCallback(HiddenValueCallback.class); + assertThat(callback.getId()).isEqualTo("DeviceSigningVerifierFailure"); + assertThat(callback.getValue()).isEqualTo("\"INVALID_SIGNATURE\""); + failureValueReceived[0]++; + + node.next(context, this ); + return; + } + super.onCallbackReceived(node); } }; FRSession.authenticate(context, TREE, nodeListenerFuture); Assert.assertNotNull(nodeListenerFuture.get()); - assertThat(authSuccess[0]).isEqualTo(1); + assertThat(failureOutcome[0]).isEqualTo(1); + assertThat(failureValueReceived[0]).isEqualTo(1); // Ensure that the journey finishes with success Assert.assertNotNull(FRSession.getCurrentSession()); @@ -664,6 +722,7 @@ public void onCallbackReceived(Node node) public void testDeviceVerificationFailureTemperedJwt() throws ExecutionException, InterruptedException { final int[] signSuccess = {0}; final int[] failureOutcome = {0}; + final int[] failureValueReceived = {0}; NodeListenerFuture nodeListenerFuture = new DeviceSigningVerifierNodeListener(context, "default") { @@ -683,7 +742,8 @@ public void onSuccess(Void result) { signSuccess[0]++; // Prepare prepare exp value for 10 days ahead of now... Calendar expTempered = Calendar.getInstance(); - expTempered.add(Calendar.SECOND, 864000); + expTempered.add(Calendar.SECOND, 10); + try { String jwtHeader = JWTParser.parse((String) callback.getInputValue(0)).getParsedParts()[0].toString(); @@ -693,12 +753,12 @@ public void onSuccess(Void result) { // Temper the exp value in the original JWT... // The Device Signing Verifier node should detect that the payload has been tempered and therefore should fail! JSONObject temperedPayloadJson = new JSONObject(jwtPayload); - temperedPayloadJson.put("exp", expTempered.getTime().getTime()); + temperedPayloadJson.put("exp", expTempered.getTime().getTime() / 1000); String temperedPayload = Base64.encodeToString(temperedPayloadJson.toString().getBytes(), Base64.DEFAULT); String temperedJwt = new StringBuilder(). append(jwtHeader).append("."). append(temperedPayload).append("."). - append(jwtSignature).toString(); + append(jwtSignature).toString().replace("\n", "").replace("\r", "");; Logger.debug(TAG, "Original JWT: " + callback.getInputValue(0)); Logger.debug(TAG, "Tempered JWT: " + temperedJwt); @@ -709,6 +769,7 @@ public void onSuccess(Void result) { Logger.debug(TAG, e.getMessage()); } node.next(context, nodeListener); + } @Override public void onException(Exception e) { @@ -726,6 +787,17 @@ public void onException(Exception e) { return; } + // The test tree is configured to send the `DeviceSigningVerifierNode.FAILURE` value in a HiddenValue callback + if (node.getCallback(HiddenValueCallback.class) != null) { + HiddenValueCallback callback = node.getCallback(HiddenValueCallback.class); + assertThat(callback.getId()).isEqualTo("DeviceSigningVerifierFailure"); + assertThat(callback.getValue()).isEqualTo("\"INVALID_SIGNATURE\""); + failureValueReceived[0]++; + + node.next(context, this ); + return; + } + super.onCallbackReceived(node); } }; @@ -734,6 +806,7 @@ public void onException(Exception e) { Assert.assertNotNull(nodeListenerFuture.get()); assertThat(signSuccess[0]).isEqualTo(1); assertThat(failureOutcome[0]).isEqualTo(1); + assertThat(failureValueReceived[0]).isEqualTo(1); // Ensure that the journey finishes with success Assert.assertNotNull(FRSession.getCurrentSession()); @@ -922,14 +995,13 @@ public void onException(Exception e) { } /* - * Make sure that when user's account is not active, - * the Inactive User outcome is triggered (when enabled...) (SDKS-2935) + * Make sure that when user's account is not active the failure outcome is triggered with NOT_ACTIVE_USER reason (SDKS-2935) */ @Test public void testDeviceVerificationInactiveUser() throws ExecutionException, InterruptedException { final int[] signCallback = {0}; - final int[] inactiveUserOutcome = {0}; - + final int[] failureOutcome = {0}; + final int[] failureValueReceived = {0}; NodeListenerFuture nodeListenerFuture = new DeviceSigningVerifierNodeListener(context, "inactive-user") { final NodeListener nodeListener = this; @@ -955,16 +1027,27 @@ public void onException(Exception e) { return; } - // Make sure that the "Inactive User" outcome has been triggered + // Make sure that the "failure" outcome has been triggered if (node.getCallback(TextOutputCallback.class) != null) { TextOutputCallback textOutputCallback = node.getCallback(TextOutputCallback.class); - assertThat(textOutputCallback.getMessage()).isEqualTo("Inactive User"); - inactiveUserOutcome[0]++; + assertThat(textOutputCallback.getMessage()).isEqualTo("Failure"); + failureOutcome[0]++; node.next(context, nodeListener); return; } + // The test tree is configured to send the `DeviceSigningVerifierNode.FAILURE` value in a HiddenValue callback + if (node.getCallback(HiddenValueCallback.class) != null) { + HiddenValueCallback callback = node.getCallback(HiddenValueCallback.class); + assertThat(callback.getId()).isEqualTo("DeviceSigningVerifierFailure"); + assertThat(callback.getValue()).isEqualTo("\"NOT_ACTIVE_USER\""); + failureValueReceived[0]++; + + node.next(context, this ); + return; + } + super.onCallbackReceived(node); } }; @@ -972,13 +1055,74 @@ public void onException(Exception e) { FRSession.authenticate(context, TREE, nodeListenerFuture); Assert.assertNotNull(nodeListenerFuture.get()); - // Make sure that the node didn't return a callback, and the "inactive user" outcome was triggered. + // Make sure that the node didn't return a callback, and the "failure" outcome was triggered. assertThat(signCallback[0]).isEqualTo(0); - assertThat(inactiveUserOutcome[0]).isEqualTo(1); + assertThat(failureOutcome[0]).isEqualTo(1); + assertThat(failureValueReceived[0]).isEqualTo(1); // Ensure that the journey finishes with success Assert.assertNotNull(FRSession.getCurrentSession()); Assert.assertNotNull(FRSession.getCurrentSession().getSessionToken()); } + + /* + * Make sure that when the `sub` claim is invalid the failure outcome is triggered with INVALID_SUBJECT reason (SDKS-2935) + */ + @Test + public void testDeviceVerificationInvalidSubject() throws ExecutionException, InterruptedException { + final int[] failureOutcome = {0}; + final int[] failureValueReceived = {0}; + + CallbackFactory.getInstance().register(CustomDeviceSigningVerifierCallback.class); + NodeListenerFuture nodeListenerFuture = new DeviceSigningVerifierNodeListener(context, "default") { + final NodeListener nodeListener = this; + + @Override + public void onCallbackReceived(Node node) { + if (node.getCallback(CustomDeviceSigningVerifierCallback.class) != null) { + CustomDeviceSigningVerifierCallback callback = node.getCallback(CustomDeviceSigningVerifierCallback.class); + String customJwt = callback.getSignedJwt( + KID, + "random-user-id", + callback.getChallenge()); + + callback.setJws(customJwt); + node.next(context, nodeListener); + return; + } + if (node.getCallback(TextOutputCallback.class) != null) { + TextOutputCallback textOutputCallback = node.getCallback(TextOutputCallback.class); + assertThat(textOutputCallback.getMessage()).isEqualTo("Failure"); + failureOutcome[0]++; + + node.next(context, nodeListener); + return; + } + + // The test tree is configured to send the `DeviceSigningVerifierNode.FAILURE` value in a HiddenValue callback + if (node.getCallback(HiddenValueCallback.class) != null) { + HiddenValueCallback callback = node.getCallback(HiddenValueCallback.class); + assertThat(callback.getId()).isEqualTo("DeviceSigningVerifierFailure"); + assertThat(callback.getValue()).isEqualTo("\"INVALID_SUBJECT\""); + failureValueReceived[0]++; + + node.next(context, this); + return; + } + + super.onCallbackReceived(node); + } + }; + + FRSession.authenticate(context, TREE, nodeListenerFuture); + Assert.assertNotNull(nodeListenerFuture.get()); + + assertThat(failureOutcome[0]).isEqualTo(1); + assertThat(failureValueReceived[0]).isEqualTo(1); + + // Ensure that the journey finishes with success + Assert.assertNotNull(FRSession.getCurrentSession()); + Assert.assertNotNull(FRSession.getCurrentSession().getSessionToken()); + } } From 45e3ae2efcd8543f1767aee298e33099f2795508 Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Wed, 31 Jan 2024 08:17:03 -0800 Subject: [PATCH 4/8] Update build tools version --- .github/workflows/bitbar-prepare-artifacts.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/bitbar-prepare-artifacts.yaml b/.github/workflows/bitbar-prepare-artifacts.yaml index 855c26c6..28e14205 100644 --- a/.github/workflows/bitbar-prepare-artifacts.yaml +++ b/.github/workflows/bitbar-prepare-artifacts.yaml @@ -42,6 +42,10 @@ jobs: - name: Prepare device farm artifacts run: ./gradlew assembleDebugAndroidTest --stacktrace --no-daemon + # List the available build tools versions see https://github.com/r0adkll/sign-android-release/issues/84 + - name: List build tools versions + run: ls /Users/runner/Library/Android/sdk/build-tools/ + # Sign auth-debug-androidTest.apk - name: Sign auth-debug-androidTest.apk uses: r0adkll/sign-android-release@v1 @@ -52,7 +56,7 @@ jobs: keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }} env: - BUILD_TOOLS_VERSION: "30.0.3" + BUILD_TOOLS_VERSION: "34.0.0" # Sign forgerock-auth-debug-androidTest.apk - name: Sign forgerock-auth-debug-androidTest.apk @@ -64,7 +68,7 @@ jobs: keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }} keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }} env: - BUILD_TOOLS_VERSION: "30.0.3" + BUILD_TOOLS_VERSION: "34.0.0" # Publish the signed APKs as build artifacts - name: Publish auth-debug-androidTest.apk From 50221b72df03c37ec51e4ddd2d789dc2f5476b12 Mon Sep 17 00:00:00 2001 From: jey Date: Sat, 27 Jan 2024 13:00:46 -0600 Subject: [PATCH 5/8] SDKS-2948 crash fix and improvement --- .../devicebind/BiometricBindingHandler.kt | 4 +- .../android/auth/biometric/BiometricAuth.kt | 180 ++++++++---------- .../biometric/DeviceCredentialFragment.java | 89 --------- 3 files changed, 82 insertions(+), 191 deletions(-) delete mode 100644 forgerock-core/src/main/java/org/forgerock/android/auth/biometric/DeviceCredentialFragment.java diff --git a/forgerock-auth/src/main/java/org/forgerock/android/auth/devicebind/BiometricBindingHandler.kt b/forgerock-auth/src/main/java/org/forgerock/android/auth/devicebind/BiometricBindingHandler.kt index 42489cdc..5970e736 100644 --- a/forgerock-auth/src/main/java/org/forgerock/android/auth/devicebind/BiometricBindingHandler.kt +++ b/forgerock-auth/src/main/java/org/forgerock/android/auth/devicebind/BiometricBindingHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 ForgeRock. All rights reserved. + * Copyright (c) 2022 - 2024 ForgeRock. All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. @@ -11,10 +11,8 @@ import androidx.biometric.BiometricPrompt.AuthenticationCallback import androidx.biometric.BiometricPrompt.CryptoObject import androidx.fragment.app.FragmentActivity import org.forgerock.android.auth.InitProvider -import org.forgerock.android.auth.biometric.AuthenticatorType import org.forgerock.android.auth.biometric.BiometricAuth import org.forgerock.android.auth.callback.DeviceBindingAuthenticationType -import java.util.* /** diff --git a/forgerock-core/src/main/java/org/forgerock/android/auth/biometric/BiometricAuth.kt b/forgerock-core/src/main/java/org/forgerock/android/auth/biometric/BiometricAuth.kt index 87be236b..e8b2b8b4 100644 --- a/forgerock-core/src/main/java/org/forgerock/android/auth/biometric/BiometricAuth.kt +++ b/forgerock-core/src/main/java/org/forgerock/android/auth/biometric/BiometricAuth.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 - 2023 ForgeRock. All rights reserved. + * Copyright (c) 2022 - 2024 ForgeRock. All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. @@ -70,7 +70,7 @@ class BiometricAuth @JvmOverloads constructor( */ private val description: String? = null, -) { + ) { private var biometricManager: BiometricManager? = null @@ -88,85 +88,101 @@ class BiometricAuth @JvmOverloads constructor( private fun handleError(logMessage: String, biometricErrorMessage: String, errorType: Int) { debug(TAG, logMessage) biometricAuthListener?.onAuthenticationError( - errorType, biometricErrorMessage) - } - - private fun tryDisplayBiometricOnlyPrompt(cryptoObject: CryptoObject?) { - if (hasBiometricCapability()) { - initBiometricAuthentication(cryptoObject) - } else { - handleError("allowDeviceCredentials is set to false, but no biometric " + - "hardware found or enrolled.", - "It requires " + - "biometric authentication. No biometric hardware found or enrolled.", - BiometricPrompt.ERROR_NO_BIOMETRICS) - } + errorType, biometricErrorMessage + ) } /* * Starts authentication process. */ fun authenticate(cryptoObject: CryptoObject? = null) { - // if biometric only, try biometric prompt - if (!allowDeviceCredentials) { - tryDisplayBiometricOnlyPrompt(cryptoObject) - return - } - - // API 29 and above, use BiometricPrompt - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - initBiometricAuthentication(cryptoObject) - return - } - - // API 23 - 28, check enrollment with FingerprintManager once BiometricPrompt might not work - if (hasEnrolledWithFingerPrint()) { - initBiometricAuthentication(cryptoObject) - return - } - - // API 23 or higher, no biometric, fallback to device credentials - if (hasDeviceCredential()) { - initDeviceCredentialAuthentication() - } else { - handleError("This device does not support required security features." + - " No Biometric, device PIN, pattern, or password registered.", - "This device does " + - "not support required security features. No Biometric, device PIN, pattern, " + - "or password registered.", - BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL) + getAuthenticators()?.let { + initBiometricAuthentication(cryptoObject, it) + } ?: run { + if (allowDeviceCredentials) { + handleError( + "This device does not support required security features." + + " No Biometric, device PIN, pattern, or password registered.", + "This device does " + + "not support required security features. No Biometric, device PIN, pattern, " + + "or password registered.", + BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL + ) + } else { + handleError( + "allowDeviceCredentials is set to false, but no biometric " + + "hardware found or enrolled.", + "It requires " + + "biometric authentication. No biometric hardware found or enrolled.", + BiometricPrompt.ERROR_NO_BIOMETRICS + ) + } } } // API 23 - 28, check enrollment with FingerprintManager once BiometricPrompt might not work fun hasEnrolledWithFingerPrint(): Boolean { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && fingerprintManager != null && fingerprintManager?.hasEnrolledFingerprints() == true + return fingerprintManager != null && fingerprintManager?.hasEnrolledFingerprints() == true } // API 23 or higher, no biometric, fallback to device credentials fun hasDeviceCredential(): Boolean { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && keyguardManager != null && keyguardManager?.isDeviceSecure == true + return keyguardManager != null && keyguardManager?.isDeviceSecure == true } // validate the biometric capability for given type and return Boolean fun hasBiometricCapability(authenticators: Int): Boolean { - if (biometricManager == null) return false - val canAuthenticate = - biometricManager?.canAuthenticate(authenticators) - return canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS + return biometricManager?.let { + val canAuthenticate = + biometricManager?.canAuthenticate(authenticators) + return canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS + } ?: false + } + + private fun getAuthenticators(): Int? { + biometricManager?.let { + if (allowDeviceCredentials) { + if (hasBiometricCapability(BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) { + return BIOMETRIC_STRONG or DEVICE_CREDENTIAL + } + if (hasBiometricCapability(BIOMETRIC_WEAK or DEVICE_CREDENTIAL) + || hasEnrolledWithFingerPrint() + || hasDeviceCredential()) { + return BIOMETRIC_WEAK or DEVICE_CREDENTIAL + } + return null + } else { + if (hasBiometricCapability(BIOMETRIC_STRONG)) { + return BIOMETRIC_STRONG + } + if (hasBiometricCapability(BIOMETRIC_WEAK)) { + return BIOMETRIC_WEAK + } + } + } + return null + } + + private fun initBiometricAuthentication(cryptoObject: CryptoObject?, authenticators: Int) { + val biometricPrompt = initBiometricPrompt(authenticators) + promptInfo?.let { promptInfo -> + activity.runOnUiThread { + cryptoObject?.let { + biometricPrompt.authenticate(promptInfo, it) + } ?: biometricPrompt.authenticate(promptInfo) + } + } } private fun setServicesFromActivity(activity: FragmentActivity) { val context = activity.baseContext biometricManager = BiometricManager.from(activity) keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as? KeyguardManager - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - fingerprintManager = - context.getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager - } + fingerprintManager = + context.getSystemService(Context.FINGERPRINT_SERVICE) as FingerprintManager } - private fun initBiometricPrompt(): BiometricPrompt { + private fun initBiometricPrompt(authenticators: Int): BiometricPrompt { val executor = ContextCompat.getMainExecutor(activity) val biometricPrompt = biometricAuthListener?.let { BiometricPrompt(activity, executor, it) @@ -177,19 +193,7 @@ class BiometricAuth @JvmOverloads constructor( val builder = BiometricPrompt.PromptInfo.Builder() .setTitle(title ?: "Biometric Authentication for login") .setSubtitle(subtitle ?: "Log in using your biometric credential") - val authenticators: Int - if (allowDeviceCredentials) { - authenticators = if(hasBiometricCapability(BIOMETRIC_STRONG or DEVICE_CREDENTIAL )) { - BIOMETRIC_STRONG or DEVICE_CREDENTIAL - } else { - BIOMETRIC_WEAK or DEVICE_CREDENTIAL - } - } else { - authenticators = if(hasBiometricCapability(BIOMETRIC_STRONG)) { - BIOMETRIC_STRONG - } else { - BIOMETRIC_WEAK - } + if (!allowDeviceCredentials) { builder.setNegativeButtonText("Cancel") } description?.let { @@ -199,29 +203,6 @@ class BiometricAuth @JvmOverloads constructor( promptInfo = builder.build() return biometricPrompt } - - private fun hasBiometricCapability(): Boolean { - if (biometricManager == null) return false - val canAuthenticate = - biometricManager?.canAuthenticate(BIOMETRIC_WEAK) - return canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS - } - - private fun initBiometricAuthentication(cryptoObject: CryptoObject?) { - val biometricPrompt = initBiometricPrompt() - promptInfo?.let { promptInfo -> - activity.runOnUiThread() { - cryptoObject?.let { - biometricPrompt.authenticate(promptInfo, it) - } ?: biometricPrompt.authenticate(promptInfo) - } - } - } - - private fun initDeviceCredentialAuthentication() { - DeviceCredentialFragment.launch(this) - } - companion object { private val TAG = BiometricAuth::class.java.simpleName @@ -241,8 +222,11 @@ class BiometricAuth @JvmOverloads constructor( canAuthenticate = false } // Check if Fingerprint Authentication Permission was granted - if (ContextCompat.checkSelfPermission(applicationContext, - Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { + if (ContextCompat.checkSelfPermission( + applicationContext, + Manifest.permission.USE_FINGERPRINT + ) != PackageManager.PERMISSION_GRANTED + ) { canAuthenticate = false } } else { @@ -252,8 +236,11 @@ class BiometricAuth @JvmOverloads constructor( canAuthenticate = false } // Check if Fingerprint Authentication Permission was granted - if (ContextCompat.checkSelfPermission(applicationContext, - Manifest.permission.USE_BIOMETRIC) != PackageManager.PERMISSION_GRANTED) { + if (ContextCompat.checkSelfPermission( + applicationContext, + Manifest.permission.USE_BIOMETRIC + ) != PackageManager.PERMISSION_GRANTED + ) { canAuthenticate = false } } @@ -264,9 +251,4 @@ class BiometricAuth @JvmOverloads constructor( init { setServicesFromActivity(activity) } -} - -enum class AuthenticatorType { - STRONG, - WEAK } \ No newline at end of file diff --git a/forgerock-core/src/main/java/org/forgerock/android/auth/biometric/DeviceCredentialFragment.java b/forgerock-core/src/main/java/org/forgerock/android/auth/biometric/DeviceCredentialFragment.java deleted file mode 100644 index d514f5bb..00000000 --- a/forgerock-core/src/main/java/org/forgerock/android/auth/biometric/DeviceCredentialFragment.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2022 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.biometric; - -import static android.app.Activity.RESULT_OK; - -import android.content.Intent; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RestrictTo; -import androidx.biometric.BiometricPrompt; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; - -import org.forgerock.android.auth.Logger; -import org.forgerock.android.core.R; - -/** - * Headless Fragment to receive result from KeyguardManager library. - */ -@RestrictTo(RestrictTo.Scope.LIBRARY) -public class DeviceCredentialFragment extends Fragment { - - static final String TAG = DeviceCredentialFragment.class.getName(); - static final int LOCK_REQUEST_CODE = 221; - - private BiometricAuth biometricAuth; - - /** - * Initialize the Fragment to receive device credential authentication result. - */ - static void launch(@NonNull BiometricAuth biometricAuth) { - FragmentManager fragmentManager = biometricAuth.getActivity().getSupportFragmentManager(); - DeviceCredentialFragment existing = (DeviceCredentialFragment) fragmentManager.findFragmentByTag(TAG); - if (existing != null) { - existing.biometricAuth = null; - fragmentManager.beginTransaction().remove(existing).commitNow(); - } - - DeviceCredentialFragment fragment = new DeviceCredentialFragment(); - fragment.biometricAuth = biometricAuth; - fragmentManager.beginTransaction().add(fragment, DeviceCredentialFragment.TAG).commit(); - } - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - String title = this.biometricAuth.getTitle() != null - ? this.biometricAuth.getTitle() - : getString(R.string.biometric_title); - String reason = this.biometricAuth.getSubtitle() != null - ? this.biometricAuth.getSubtitle() - : getString(R.string.biometric_subtitle); - - Intent authIntent = biometricAuth.getKeyguardManager() - .createConfirmDeviceCredentialIntent(title, reason); - - startActivityForResult(authIntent, LOCK_REQUEST_CODE); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - if (getActivity() != null) { - getActivity().getSupportFragmentManager().beginTransaction().remove(this).commitNow(); - } - if (requestCode == LOCK_REQUEST_CODE) { - if (resultCode == RESULT_OK) { - this.biometricAuth.getBiometricAuthListener().onAuthenticationSucceeded(null); - } else { - Logger.debug(TAG, "Fail to approve using device Credentials. requestCode " + - "is %s", resultCode); - this.biometricAuth.getBiometricAuthListener().onAuthenticationError( - BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL, - "Fail to approve using device Credentials."); - } - } - this.biometricAuth = null; - } - -} From b9532f2daaa0093422ef7aa7ff09c10c023a9ec2 Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Thu, 8 Feb 2024 15:13:11 -0800 Subject: [PATCH 6/8] ForgeRock Android SDK 4.3.1 release preparation --- CHANGELOG.md | 5 ++++- .../android/auth/callback/AppIntegrityCallbackTest.java | 4 ++-- .../android/auth/callback/BaseDeviceBindingTest.java | 2 +- gradle.properties | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f8e30fc..0fa1b746 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,13 @@ +## [4.3.1] +#### Fixed +- Fixed and issue where the SDK was crashing during device binding on Android 9 devices [SDKS-2948] + ## [4.3.0] #### Added - Added the ability to customize cookie headers in outgoing requests from the SDK [SDKS-2780] - Added the ability to insert custom claims when performing device signing verification [SDKS-2787] - Added client-side support for the `AppIntegrity` callback [SDKS-2631] - #### Fixed - The SDK now uses `auth-per-use` keys for Device Binding [SDKS-2797] - Improved handling of WebAuthn cancellations [SDKS-2819] diff --git a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/AppIntegrityCallbackTest.java b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/AppIntegrityCallbackTest.java index 5cea36da..daee3649 100644 --- a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/AppIntegrityCallbackTest.java +++ b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/AppIntegrityCallbackTest.java @@ -40,8 +40,8 @@ public class AppIntegrityCallbackTest { protected static Context context = ApplicationProvider.getApplicationContext(); - protected final static String AM_URL = "https://localam.petrov.ca/openam"; - protected final static String REALM = "root"; + protected final static String AM_URL = "https://openam-integrity1.forgeblocks.com/am"; + protected final static String REALM = "alpha"; protected final static String OAUTH_CLIENT = "AndroidTest"; protected final static String OAUTH_REDIRECT_URI = "org.forgerock.demo:/oauth2redirect"; protected final static String SCOPE = "openid profile email address phone"; diff --git a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/BaseDeviceBindingTest.java b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/BaseDeviceBindingTest.java index e02243b9..4789d81c 100644 --- a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/BaseDeviceBindingTest.java +++ b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/BaseDeviceBindingTest.java @@ -31,7 +31,7 @@ public abstract class BaseDeviceBindingTest { protected static Context context = ApplicationProvider.getApplicationContext(); // This test uses dynamic configuration with the following settings: - protected final static String AM_URL = "https://openam-spetrov.forgeblocks.com/am"; + protected final static String AM_URL = "https://openam-dbind.forgeblocks.com/am"; protected final static String REALM = "alpha"; protected final static String OAUTH_CLIENT = "AndroidTest"; protected final static String OAUTH_REDIRECT_URI = "org.forgerock.demo:/oauth2redirect"; diff --git a/gradle.properties b/gradle.properties index 47560012..0de196a5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,8 +23,8 @@ kotlin.code.style=official android.useAndroidX=true GROUP=org.forgerock -VERSION=4.3.0 -VERSION_CODE=19 +VERSION=4.3.1 +VERSION_CODE=20 android.defaults.buildfeatures.buildconfig=true android.nonTransitiveRClass=false android.nonFinalResIds=false \ No newline at end of file From f476310f34ee7b058ab00a4b9ff830767e2bca1b Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Thu, 8 Feb 2024 15:51:32 -0800 Subject: [PATCH 7/8] Fix SSL pinning test cases (updated public key hash value of api.ipify.org --- .../android/auth/callback/AppIntegrityCallbackTest.java | 2 +- .../android/auth/callback/BaseDeviceBindingTest.java | 2 +- .../java/org/forgerock/android/auth/ServerConfigTest.java | 8 ++++---- gradle.properties | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/AppIntegrityCallbackTest.java b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/AppIntegrityCallbackTest.java index daee3649..96711899 100644 --- a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/AppIntegrityCallbackTest.java +++ b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/AppIntegrityCallbackTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 - 2023 ForgeRock. All rights reserved. + * Copyright (c) 2022 - 2024 ForgeRock. All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. diff --git a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/BaseDeviceBindingTest.java b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/BaseDeviceBindingTest.java index 4789d81c..fff2642b 100644 --- a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/BaseDeviceBindingTest.java +++ b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/BaseDeviceBindingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 ForgeRock. All rights reserved. + * Copyright (c) 2023 - 2024 ForgeRock. All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. diff --git a/forgerock-auth/src/test/java/org/forgerock/android/auth/ServerConfigTest.java b/forgerock-auth/src/test/java/org/forgerock/android/auth/ServerConfigTest.java index 021e2400..d1df9aa1 100644 --- a/forgerock-auth/src/test/java/org/forgerock/android/auth/ServerConfigTest.java +++ b/forgerock-auth/src/test/java/org/forgerock/android/auth/ServerConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 - 2023 ForgeRock. All rights reserved. + * Copyright (c) 2019 - 2024 ForgeRock. All rights reserved. * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. @@ -80,7 +80,7 @@ public void testSha256Pinning() throws InterruptedException { ServerConfig serverConfig = ServerConfig.builder() .context(context) .url("https://api.ipify.org") - .pin("9hNxmEFgLKGJXqgp61hyb8yIyiT9u0vgDZh4y8TmY/M=") + .pin("HMSZyV3whmhwmQlqNPIlvpQA9AHHQ9aj1CEDqFkAuyE=") .build(); OkHttpClient client = OkHttpClientProvider.getInstance().lookup(serverConfig); @@ -122,7 +122,7 @@ public void testMultiplePinning() throws InterruptedException { ServerConfig serverConfig = ServerConfig.builder() .context(context) .url("https://api.ipify.org") - .pin("9hNxmEFgLKGJXqgp61hyb8yIyiT9u0vgDZh4y8TmY/M=") + .pin("HMSZyV3whmhwmQlqNPIlvpQA9AHHQ9aj1CEDqFkAuyE=") .pin("invalid") .build(); @@ -214,7 +214,7 @@ public void testBuildStepWithCustomPin() throws InterruptedException { .context(context) .url("https://api.ipify.org") .buildStep(builder -> builder.certificatePinner( - new CertificatePinner.Builder().add("api.ipify.org", "sha1/2vB3hhEJ98C5efhhWpxtD2wxYek=" ).build())) + new CertificatePinner.Builder().add("api.ipify.org", "sha1/40WpRckJNrzdAexnwLvKG7aK3qk=" ).build())) .build(); OkHttpClient client = OkHttpClientProvider.getInstance().lookup(serverConfig); diff --git a/gradle.properties b/gradle.properties index 0de196a5..a2e4c06b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019 - 2022 ForgeRock. All rights reserved. +# Copyright (c) 2019 - 2024 ForgeRock. All rights reserved. # # This software may be modified and distributed under the terms # of the MIT license. See the LICENSE file for details. From 7a98174b124175c58654970d3f38ae708fbd994e Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Fri, 9 Feb 2024 07:29:30 -0800 Subject: [PATCH 8/8] Fix typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fa1b746..d37fb917 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## [4.3.1] #### Fixed -- Fixed and issue where the SDK was crashing during device binding on Android 9 devices [SDKS-2948] +- Fixed an issue where the SDK was crashing during device binding on Android 9 devices [SDKS-2948] ## [4.3.0] #### Added