Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDKS 545 - TextInputCallback Support #395

Merged
merged 2 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TextInputCallback> {

@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.getDefaultText() != null) {
text.setText(callback.getDefaultText().toString());
}
TextInputLayout textInputLayout = view.findViewById(R.id.textInputLayout);
textInputLayout.setHint(callback.getPrompt());
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".callback.TextInputCallbackFragment" android:id="@+id/frameLayout">

<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textInputLayout"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:passwordToggleEnabled="true">

<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/text"
android:inputType="text"/>
</com.google.android.material.textfield.TextInputLayout>

</androidx.constraintlayout.widget.ConstraintLayout>
Original file line number Diff line number Diff line change
@@ -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<FRSession> nodeListenerFuture = new UsernamePasswordNodeListener(context) {
final NodeListener<FRSession> 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());
}
}
6 changes: 3 additions & 3 deletions forgerock-auth/src/androidTest/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@
<string name="forgerock_oauth_client_id" translatable="false">AndroidTest</string>
<string name="forgerock_oauth_redirect_uri" translatable="false">org.forgerock.demo:/oauth2redirect</string>
<string name="forgerock_oauth_scope" translatable="false">openid profile email address phone</string>
<string name="forgerock_oauth_url" translatable="false">https://openam-forgerrock-sdksteanant.forgeblocks.com/am</string>
<string name="forgerock_oauth_url" translatable="false">https://openam-sdks.forgeblocks.com/am</string>
<integer name="forgerock_oauth_threshold" translatable="false">30</integer> <!-- in second -->

<!-- The following setting is for local development environment only -->
<!-- Server -->
<string name="forgerock_url" translatable="false">https://openam-forgerrock-sdksteanant.forgeblocks.com/am</string>
<string name="forgerock_url" translatable="false">https://openam-sdks.forgeblocks.com/am</string>
<string name="forgerock_realm" translatable="false">alpha</string>
<integer name="forgerock_timeout" translatable="false">30</integer>
<string name="forgerock_cookie_name" translatable="false">iPlanetDirectoryPro</string>
<string name="forgerock_cookie_name" translatable="false">5421aeddf91aa20</string>

<!-- Service -->
<string name="forgerock_auth_service" translatable="false">NamePasswordCallbackTest</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -49,6 +49,7 @@ private CallbackFactory() {
register(DeviceBindingCallback.class);
register(DeviceSigningVerifierCallback.class);
register(AppIntegrityCallback.class);
register(TextInputCallback.class);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
}
}
Original file line number Diff line number Diff line change
@@ -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())
}
}
Loading
Loading