From e2ba05c0d8a94ad15a9f430f7cbd964e3cfcb002 Mon Sep 17 00:00:00 2001 From: Rodrigo Reis Date: Thu, 15 Feb 2024 14:36:49 -0800 Subject: [PATCH 1/2] SDKS 545 - TextInputCallback Support --- .../auth/ui/CallbackFragmentFactory.java | 3 +- .../callback/TextInputCallbackFragment.java | 59 ++++++++++++++ .../layout/fragment_text_input_callback.xml | 31 ++++++++ .../auth/callback/CallbackFactory.java | 3 +- .../auth/callback/TextInputCallback.kt | 78 +++++++++++++++++++ .../auth/callback/TextInputCallbackTest.kt | 51 ++++++++++++ .../example/app/callback/TextInputCallback.kt | 74 ++++++++++++++++++ .../java/com/example/app/journey/Journey.kt | 6 +- 8 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 forgerock-auth-ui/src/main/java/org/forgerock/android/auth/ui/callback/TextInputCallbackFragment.java create mode 100644 forgerock-auth-ui/src/main/res/layout/fragment_text_input_callback.xml create mode 100644 forgerock-auth/src/main/java/org/forgerock/android/auth/callback/TextInputCallback.kt create mode 100644 forgerock-auth/src/test/java/org/forgerock/android/auth/callback/TextInputCallbackTest.kt create mode 100644 samples/app/src/main/java/com/example/app/callback/TextInputCallback.kt diff --git a/forgerock-auth-ui/src/main/java/org/forgerock/android/auth/ui/CallbackFragmentFactory.java b/forgerock-auth-ui/src/main/java/org/forgerock/android/auth/ui/CallbackFragmentFactory.java index b204c5d3..0a81a22d 100644 --- a/forgerock-auth-ui/src/main/java/org/forgerock/android/auth/ui/CallbackFragmentFactory.java +++ b/forgerock-auth-ui/src/main/java/org/forgerock/android/auth/ui/CallbackFragmentFactory.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. @@ -66,6 +66,7 @@ private CallbackFragmentFactory() { register(WebAuthnAuthenticationCallback.class, WebAuthnAuthenticationCallbackFragment.class); register(SelectIdPCallback.class, SelectIdPCallbackFragment.class); register(IdPCallback.class, IdPCallbackFragment.class); + register(TextInputCallback.class, TextInputCallbackFragment.class); } public static CallbackFragmentFactory getInstance() { diff --git a/forgerock-auth-ui/src/main/java/org/forgerock/android/auth/ui/callback/TextInputCallbackFragment.java b/forgerock-auth-ui/src/main/java/org/forgerock/android/auth/ui/callback/TextInputCallbackFragment.java new file mode 100644 index 00000000..b64e8255 --- /dev/null +++ b/forgerock-auth-ui/src/main/java/org/forgerock/android/auth/ui/callback/TextInputCallbackFragment.java @@ -0,0 +1,59 @@ +/* + * 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.ui.callback; + +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; + +import com.google.android.material.textfield.TextInputLayout; + +import org.forgerock.android.auth.callback.TextInputCallback; +import org.forgerock.android.auth.ui.R; + +/** + * UI representation for {@link TextInputCallback} + */ +public class TextInputCallbackFragment extends CallbackFragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + final View view = inflater.inflate(R.layout.fragment_text_input_callback, container, false); + + EditText text = view.findViewById(R.id.text); + if (callback.getInputValue() != null) { + text.setText(callback.getInputValue().toString()); + } + TextInputLayout textInputLayout = view.findViewById(R.id.textInputLayout); + textInputLayout.setHint(callback.getDefaultText()); + text.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + callback.setValue(s.toString()); + onDataCollected(); + } + }); + return view; + } +} diff --git a/forgerock-auth-ui/src/main/res/layout/fragment_text_input_callback.xml b/forgerock-auth-ui/src/main/res/layout/fragment_text_input_callback.xml new file mode 100644 index 00000000..0bcdb575 --- /dev/null +++ b/forgerock-auth-ui/src/main/res/layout/fragment_text_input_callback.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/CallbackFactory.java b/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/CallbackFactory.java index 7ca0a0d3..e04b57e4 100644 --- a/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/CallbackFactory.java +++ b/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/CallbackFactory.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. @@ -49,6 +49,7 @@ private CallbackFactory() { register(DeviceBindingCallback.class); register(DeviceSigningVerifierCallback.class); register(AppIntegrityCallback.class); + register(TextInputCallback.class); } /** diff --git a/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/TextInputCallback.kt b/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/TextInputCallback.kt new file mode 100644 index 00000000..85b87471 --- /dev/null +++ b/forgerock-auth/src/main/java/org/forgerock/android/auth/callback/TextInputCallback.kt @@ -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 androidx.annotation.Keep +import org.json.JSONObject + +/** + * Callback for collection of a single text input attribute from a user. + * + * + */ +class TextInputCallback : AbstractPromptCallback { + + /** + * TextInputCallback sample. + * + * { + * "type": "TextInputCallback", + * "output": [ + * { + * "name": "prompt", + * "value": "Text input" + * }, + * { + * "name": "defaultText", + * "value": "" + * } + * ], + * "input": [ + * { + * "name": "IDToken1", + * "value": "" + * } + * ] + * } + * + */ + + /** + * The text to be used as the default text displayed with the prompt. + */ + var defaultText: String? = null + private set + + @Keep + @JvmOverloads + constructor() : super() + + @Keep + @JvmOverloads + constructor(raw: JSONObject?, index: Int) : super(raw, index) + + override fun setAttribute(name: String, value: Any) { + super.setAttribute(name, value) + when (name) { + "defaultText" -> defaultText = value as String + else -> {} + } + } + + /** + * Set the text. + * @param value the text, which may be null. + */ + fun setValue(value: String?) { + super.setValue(value) + } + + override fun getType(): String { + return "TextInputCallback" + } +} \ No newline at end of file diff --git a/forgerock-auth/src/test/java/org/forgerock/android/auth/callback/TextInputCallbackTest.kt b/forgerock-auth/src/test/java/org/forgerock/android/auth/callback/TextInputCallbackTest.kt new file mode 100644 index 00000000..7f8871aa --- /dev/null +++ b/forgerock-auth/src/test/java/org/forgerock/android/auth/callback/TextInputCallbackTest.kt @@ -0,0 +1,51 @@ +/* + * 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 androidx.test.ext.junit.runners.AndroidJUnit4 +import org.json.JSONException +import org.json.JSONObject +import org.junit.Assert +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class TextInputCallbackTest { + @Test + @Throws(JSONException::class) + fun basicTest() { + val raw = JSONObject("""{ + "type": "TextInputCallback", + "output": [ + { + "name": "prompt", + "value": "One Time Pin" + }, + { + "name": "defaultText", + "value": "" + } + ], + "input": [ + { + "name": "IDToken1", + "value": "" + } + ], + "_id": 0 + }""") + val textInputCallback = TextInputCallback(raw, 0) + Assert.assertEquals("One Time Pin", textInputCallback.getPrompt()) + Assert.assertEquals("", textInputCallback.defaultText) + textInputCallback.setValue("010101") + Assert.assertEquals((textInputCallback.contentAsJson.getJSONArray("input")[0] as JSONObject).getString( + "value"), + "010101") + Assert.assertEquals(0, textInputCallback.get_id().toLong()) + } +} \ No newline at end of file diff --git a/samples/app/src/main/java/com/example/app/callback/TextInputCallback.kt b/samples/app/src/main/java/com/example/app/callback/TextInputCallback.kt new file mode 100644 index 00000000..cb2d2096 --- /dev/null +++ b/samples/app/src/main/java/com/example/app/callback/TextInputCallback.kt @@ -0,0 +1,74 @@ +/* + * 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 com.example.app.callback + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.forgerock.android.auth.callback.TextInputCallback +import org.json.JSONObject + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TextInputCallback(textInputCallback: TextInputCallback) { + + var text by remember { + mutableStateOf("") + } + + Row(modifier = Modifier + .padding(4.dp) + .fillMaxWidth()) { + OutlinedTextField( + modifier = Modifier, + value = text, + onValueChange = { value -> + text = value + textInputCallback.setValue(text) + }, + label = { Text(textInputCallback.prompt) }, + ) + } + +} + +@Preview +@Composable +fun TextInputCallbackPreview() { + val json = JSONObject("{\n" + + " \"type\": \"TextInputCallback\",\n" + + " \"output\": [\n" + + " {\n" + + " \"name\": \"prompt\",\n" + + " \"value\": \"Enter text\"\n" + + " },\n" + + " {\n" + + " \"name\": \"defaultText\",\n" + + " \"value\": \"\"\n" + + " }\n" + + " ],\n" + + " \"input\": [\n" + + " {\n" + + " \"name\": \"IDToken1\",\n" + + " \"value\": \"\"\n" + + " }\n" + + " ]\n" + + "}") + TextInputCallback(TextInputCallback(json, 0)) +} \ No newline at end of file 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 7d2e3e40..dcad3655 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 @@ -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. @@ -33,6 +33,7 @@ import com.example.app.callback.NameCallback import com.example.app.callback.PasswordCallback import com.example.app.callback.PollingWaitCallback import com.example.app.callback.SelectIdPCallback +import com.example.app.callback.TextInputCallback import com.example.app.callback.TextOutputCallback import com.example.app.callback.WebAuthnAuthenticationCallback import com.example.app.callback.WebAuthnRegistrationCallback @@ -49,6 +50,7 @@ import org.forgerock.android.auth.callback.PollingWaitCallback import org.forgerock.android.auth.callback.ReCaptchaCallback import com.example.app.callback.ReCaptchaCallback import org.forgerock.android.auth.callback.SelectIdPCallback +import org.forgerock.android.auth.callback.TextInputCallback import org.forgerock.android.auth.callback.TextOutputCallback import org.forgerock.android.auth.callback.WebAuthnAuthenticationCallback import org.forgerock.android.auth.callback.WebAuthnRegistrationCallback @@ -151,6 +153,8 @@ fun Journey(state: JourneyState, showNext = false } + is TextInputCallback -> TextInputCallback(it) + else -> { //Unsupported } From 02f11b51160a641542319a958c821d50b7ab7f21 Mon Sep 17 00:00:00 2001 From: Stoyan Petrov Date: Tue, 20 Feb 2024 13:40:39 -0800 Subject: [PATCH 2/2] Added e2e test for TextInputCallback --- .../callback/TextInputCallbackFragment.java | 6 +- .../auth/callback/TextInputCallbackTest.java | 72 +++++++++++++++++++ .../src/androidTest/res/values/strings.xml | 6 +- 3 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/TextInputCallbackTest.java diff --git a/forgerock-auth-ui/src/main/java/org/forgerock/android/auth/ui/callback/TextInputCallbackFragment.java b/forgerock-auth-ui/src/main/java/org/forgerock/android/auth/ui/callback/TextInputCallbackFragment.java index b64e8255..020bb301 100644 --- a/forgerock-auth-ui/src/main/java/org/forgerock/android/auth/ui/callback/TextInputCallbackFragment.java +++ b/forgerock-auth-ui/src/main/java/org/forgerock/android/auth/ui/callback/TextInputCallbackFragment.java @@ -32,11 +32,11 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, final View view = inflater.inflate(R.layout.fragment_text_input_callback, container, false); EditText text = view.findViewById(R.id.text); - if (callback.getInputValue() != null) { - text.setText(callback.getInputValue().toString()); + if (callback.getDefaultText() != null) { + text.setText(callback.getDefaultText().toString()); } TextInputLayout textInputLayout = view.findViewById(R.id.textInputLayout); - textInputLayout.setHint(callback.getDefaultText()); + textInputLayout.setHint(callback.getPrompt()); text.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { diff --git a/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/TextInputCallbackTest.java b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/TextInputCallbackTest.java new file mode 100644 index 00000000..f9a6ea9e --- /dev/null +++ b/forgerock-auth/src/androidTest/java/org/forgerock/android/auth/callback/TextInputCallbackTest.java @@ -0,0 +1,72 @@ +/* + * 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.Assert; +import org.junit.Test; + +import java.util.concurrent.ExecutionException; + +public class TextInputCallbackTest extends AndroidBaseTest { + protected final static String TREE = "TextInputCallbackTest"; + + @Test + public void testTextInputCallback() throws ExecutionException, InterruptedException { + final int[] textInputCallbackReceived = {0}; + final int[] success = {0}; + NodeListenerFuture nodeListenerFuture = new UsernamePasswordNodeListener(context) { + final NodeListener nodeListener = this; + + @Override + public void onCallbackReceived(Node node) { + if (node.getCallback(NameCallback.class) != null) { + NameCallback nameCallback = node.getCallback(NameCallback.class); + nameCallback.setValue(USERNAME); + node.next(context, this ); + return; + } + if (node.getCallback(TextInputCallback.class) != null) { + TextInputCallback callback = node.getCallback(TextInputCallback.class); + assertThat(callback.getPrompt()).isEqualTo("What is your username?"); + assertThat(callback.getDefaultText()).isEqualTo("ForgerRocker"); + textInputCallbackReceived[0]++; + callback.setValue(USERNAME); + node.next(context, nodeListener); + return; + } + // This step here is to ensure that the SDK correctly sets the value in the TextInputCallback... + // The values entered in the NameCallback and TextInputCallback above should match for "success" + if (node.getCallback(TextOutputCallback.class) != null) { + TextOutputCallback callback = node.getCallback(TextOutputCallback.class); + assertThat(callback.getMessage()).isEqualTo("Success"); + success[0]++; + node.next(context, nodeListener); + return; + } + super.onCallbackReceived(node); + } + }; + + FRSession.authenticate(context, TREE, nodeListenerFuture); + Assert.assertNotNull(nodeListenerFuture.get()); + assertThat(textInputCallbackReceived[0]).isEqualTo(1); + assertThat(success[0]).isEqualTo(1); + + // Ensure that the journey finishes with success + Assert.assertNotNull(FRSession.getCurrentSession()); + Assert.assertNotNull(FRSession.getCurrentSession().getSessionToken()); + } +} diff --git a/forgerock-auth/src/androidTest/res/values/strings.xml b/forgerock-auth/src/androidTest/res/values/strings.xml index 0cbd281f..f670ebbd 100644 --- a/forgerock-auth/src/androidTest/res/values/strings.xml +++ b/forgerock-auth/src/androidTest/res/values/strings.xml @@ -14,15 +14,15 @@ AndroidTest org.forgerock.demo:/oauth2redirect openid profile email address phone - https://openam-forgerrock-sdksteanant.forgeblocks.com/am + https://openam-sdks.forgeblocks.com/am 30 - https://openam-forgerrock-sdksteanant.forgeblocks.com/am + https://openam-sdks.forgeblocks.com/am alpha 30 - iPlanetDirectoryPro + 5421aeddf91aa20 NamePasswordCallbackTest