From f2ba7669f5d0886426158735dab7c6ff1d3e35d0 Mon Sep 17 00:00:00 2001 From: George Bafaloukas Date: Mon, 22 Jul 2024 14:09:06 +0100 Subject: [PATCH 1/3] Added conditional sbmittion of TextOutput Callbacks based on the type, skipping 4 Updated the sample apps to handle the ScriptedTextOutputCallbacks and long content better --- .../main/java/org/forgerock/android/auth/Node.java | 14 +++++++++++++- .../com/example/app/callback/TextOutputCallback.kt | 3 ++- .../main/java/com/example/app/journey/Journey.kt | 3 +++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/forgerock-auth/src/main/java/org/forgerock/android/auth/Node.java b/forgerock-auth/src/main/java/org/forgerock/android/auth/Node.java index 53b384cb..134bc528 100644 --- a/forgerock-auth/src/main/java/org/forgerock/android/auth/Node.java +++ b/forgerock-auth/src/main/java/org/forgerock/android/auth/Node.java @@ -11,7 +11,9 @@ import androidx.annotation.VisibleForTesting; +import org.forgerock.android.auth.callback.AdditionalParameterCallback; import org.forgerock.android.auth.callback.Callback; +import org.forgerock.android.auth.callback.TextOutputCallback; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -34,6 +36,7 @@ public class Node implements Serializable { private final String description; private final String authServiceId; private final List callbacks; + private static final String TAG = "Node"; @VisibleForTesting public Node(String authId, String stage, String header, String description, String authServiceId, List callbacks) { @@ -59,7 +62,16 @@ JSONObject toJsonObject() throws JSONException { } JSONArray array = new JSONArray(); for (Callback cb : callbacks) { - array.put(new JSONObject(cb.getContent())); + if ((cb instanceof TextOutputCallback)) { + TextOutputCallback callback = (TextOutputCallback) cb; + if (callback.getMessageType() == 4 ) { + Logger.info(TAG, "TextOutputCallback of unknown type (scripted TextOutputCallback) is skipped from the response"); + } else { + array.put(new JSONObject(cb.getContent())); + } + } else { + array.put(new JSONObject(cb.getContent())); + } } jsonObject.put("callbacks", array); return jsonObject; diff --git a/samples/app/src/main/java/com/example/app/callback/TextOutputCallback.kt b/samples/app/src/main/java/com/example/app/callback/TextOutputCallback.kt index 90b6ffcc..4a3b844f 100644 --- a/samples/app/src/main/java/com/example/app/callback/TextOutputCallback.kt +++ b/samples/app/src/main/java/com/example/app/callback/TextOutputCallback.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Error import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Warning import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -40,7 +41,7 @@ fun TextOutputCallback(callback: TextOutputCallback) { INFORMATION -> Icon(Icons.Filled.Info, null) WARNING -> Icon(Icons.Filled.Warning, null) ERROR -> Icon(Icons.Filled.Error, null) - else -> Icon(Icons.Filled.Info, null) + else -> Icon(Icons.Filled.Settings, null) } Spacer(Modifier.width(8.dp)) Text(text = callback.message, diff --git a/samples/app/src/main/java/com/example/app/journey/Journey.kt b/samples/app/src/main/java/com/example/app/journey/Journey.kt index 13aaf1b9..48a98e20 100644 --- a/samples/app/src/main/java/com/example/app/journey/Journey.kt +++ b/samples/app/src/main/java/com/example/app/journey/Journey.kt @@ -10,6 +10,8 @@ package com.example.app.journey import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -82,6 +84,7 @@ fun Journey(state: JourneyState, onFailure: ((Exception) -> Unit)?) { Column(modifier = Modifier + .verticalScroll(rememberScrollState()) .padding(8.dp) .fillMaxWidth()) { state.session?.apply { From f7930472c38a4a4928e02dd55d4467109c17de00 Mon Sep 17 00:00:00 2001 From: Andy Witrisna Date: Wed, 24 Jul 2024 09:27:42 -0700 Subject: [PATCH 2/3] SDKS-3227 Skip Type 4 TextOutputCallback --- CHANGELOG.md | 4 ++++ .../java/org/forgerock/android/auth/Node.java | 14 ++++-------- .../auth/callback/TextOutputCallback.java | 11 +++++++++- .../auth/callback/TextOutputCallbackTest.kt | 22 ++++++++++++++++++- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f49b876c..1298ca6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [4.X.0] +#### Added +- Skip Type 4 TextOutputCallback [SDKS-3227] + ## [4.5.0] #### Added - Added SDK support for deleting registered WebAuthn devices from the server. [SDKS-1710] diff --git a/forgerock-auth/src/main/java/org/forgerock/android/auth/Node.java b/forgerock-auth/src/main/java/org/forgerock/android/auth/Node.java index 134bc528..fa3f7b8c 100644 --- a/forgerock-auth/src/main/java/org/forgerock/android/auth/Node.java +++ b/forgerock-auth/src/main/java/org/forgerock/android/auth/Node.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. @@ -62,15 +62,9 @@ JSONObject toJsonObject() throws JSONException { } JSONArray array = new JSONArray(); for (Callback cb : callbacks) { - if ((cb instanceof TextOutputCallback)) { - TextOutputCallback callback = (TextOutputCallback) cb; - if (callback.getMessageType() == 4 ) { - Logger.info(TAG, "TextOutputCallback of unknown type (scripted TextOutputCallback) is skipped from the response"); - } else { - array.put(new JSONObject(cb.getContent())); - } - } else { - array.put(new JSONObject(cb.getContent())); + String content = cb.getContent(); + if (content != null) { + array.put(new JSONObject(content)); } } jsonObject.put("callbacks", array); diff --git a/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/TextOutputCallback.java b/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/TextOutputCallback.java index 2b052c8a..b5f5ef46 100644 --- a/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/TextOutputCallback.java +++ b/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/TextOutputCallback.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 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. @@ -72,4 +72,13 @@ public String getType() { return "TextOutputCallback"; } + @Override + public String getContent() { + if (messageType == 4) { + return null; + } else { + return super.getContent(); + } + } + } diff --git a/forgerock-auth/src/test/java/org/forgerock/android/auth/callback/TextOutputCallbackTest.kt b/forgerock-auth/src/test/java/org/forgerock/android/auth/callback/TextOutputCallbackTest.kt index bf747655..8b1d9062 100644 --- a/forgerock-auth/src/test/java/org/forgerock/android/auth/callback/TextOutputCallbackTest.kt +++ b/forgerock-auth/src/test/java/org/forgerock/android/auth/callback/TextOutputCallbackTest.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. @@ -36,4 +36,24 @@ class TextOutputCallbackTest { assertEquals("This is a Message Node", callback.message) assertEquals(1, callback.messageType.toLong()) } + + @Test + @Throws(JSONException::class) + fun testType4Message() { + val raw = JSONObject("""{ + "type": "TextOutputCallback", + "output": [ + { + "name": "message", + "value": "Javascript" + }, + { + "name": "messageType", + "value": "4" + } + ] + }""") + val callback = TextOutputCallback(raw, 0) + assertNull(callback.getContent()) + } } \ No newline at end of file From 68211c712ee24190e94b88fcd4a0a68943db2b99 Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Wed, 24 Jul 2024 09:59:08 -0700 Subject: [PATCH 3/3] Added integration test for TextOutput callback. --- .../auth/callback/TextOutputCallbackTest.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 forgerock-integration-tests/src/androidTest/java/org/forgerock/android/auth/callback/TextOutputCallbackTest.java diff --git a/forgerock-integration-tests/src/androidTest/java/org/forgerock/android/auth/callback/TextOutputCallbackTest.java b/forgerock-integration-tests/src/androidTest/java/org/forgerock/android/auth/callback/TextOutputCallbackTest.java new file mode 100644 index 00000000..4816500a --- /dev/null +++ b/forgerock-integration-tests/src/androidTest/java/org/forgerock/android/auth/callback/TextOutputCallbackTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 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. + */ + +package org.forgerock.android.auth.callback; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.forgerock.android.auth.AndroidBaseTest; +import org.forgerock.android.auth.FRSession; +import org.forgerock.android.auth.Node; +import org.forgerock.android.auth.NodeListener; +import org.forgerock.android.auth.NodeListenerFuture; +import org.forgerock.android.auth.UsernamePasswordNodeListener; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; +import java.util.concurrent.ExecutionException; + +public class TextOutputCallbackTest extends AndroidBaseTest { + protected final static String TREE = "TextOutputCallbackTest"; + + @After + public void logoutSession() { + if (FRSession.getCurrentSession() != null) { + FRSession.getCurrentSession().logout(); + } + } + + @Test + public void testTextOutputCallback() throws ExecutionException, InterruptedException { + final int[] textOutputCallbacksReceived = {0}; + NodeListenerFuture nodeListenerFuture = new UsernamePasswordNodeListener(context) { + final NodeListener nodeListener = this; + + @Override + public void onCallbackReceived(Node node) { + if (node.getCallback(TextOutputCallback.class) != null) { + textOutputCallbacksReceived[0]++; + // The TextOutputCallbackProducer script sends 4 TextOutput callbacks (of all types) + List callbacks = node.getCallbacks(); + assertThat(callbacks.size() == 4); + + for (int i = 0; i < callbacks.size(); i++) { + TextOutputCallback callback = (TextOutputCallback) callbacks.get(i); + assert( callback.getMessage().equals("TextOutput Type 0 (INFO)") || + callback.getMessage().equals("TextOutput Type 1 (WARNING)") || + callback.getMessage().equals("TextOutput Type 2 (ERROR)") || + callback.getMessage().equals("TextOutput Type 4 (SCRIPT)")); + + assert( callback.getMessageType() == 0 || + callback.getMessageType() == 1 || + callback.getMessageType() == 2 || + callback.getMessageType() == 4); + } + node.next(context, nodeListener); + return; + } + super.onCallbackReceived(node); + } + }; + + FRSession.authenticate(context, TREE, nodeListenerFuture); + Assert.assertNotNull(nodeListenerFuture.get()); + assertThat(textOutputCallbacksReceived[0]).isEqualTo(1); + + // Ensure that the journey finishes with success + // Note that the SDK should NOT send TextOutput of type 4 to AM (SDKS-3227) + // If it does the journey will fail (see the `TextOutputCallbackProducer` script...) + Assert.assertNotNull(FRSession.getCurrentSession()); + Assert.assertNotNull(FRSession.getCurrentSession().getSessionToken()); + } +} \ No newline at end of file