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-3297 Support MetadataCallback used by Marketplace PingOne Protect #436

Merged
merged 4 commits into from
Aug 14, 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
@@ -0,0 +1,147 @@
/*
* 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.forgerock.android.auth.Node
import org.json.JSONException
import org.json.JSONObject

const val _ACTION = "_action"
const val _TYPE = "_type"
const val CLIENT_ERROR = "clientError"
const val PING_ONE_RISK_EVALUATION_SIGNALS = "pingone_risk_evaluation_signals"
const val PING_ONE_PROTECT = "PingOneProtect"
const val PROTECT_INITIALIZE = "protect_initialize"
const val PROTECT_RISK_EVALUATION = "protect_risk_evaluation"
const val PING_ONE_PROTECT_INITIALIZE_CALLBACK = "PingOneProtectInitializeCallback"
const val PING_ONE_PROTECT_EVALUATION_CALLBACK = "PingOneProtectEvaluationCallback"

/**
* Abstract Protect Callback that provides the raw content of the Callback, and common methods
* for sub classes to access.
*/
abstract class AbstractProtectCallback: NodeAware, AbstractCallback {

/**
* Indicates if this callback is derived from a [MetadataCallback]
*/
protected var derivedCallback: Boolean = false

Copy link
Contributor

@jeyanthanperiyasamy jeyanthanperiyasamy Aug 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we can keep two constructors

abstract class AbstractProtectCallback: NodeAware, AbstractCallback {

@Keep
constructor(jsonObject: JSONObject, index: Int) : super(jsonObject, index)

@Keep
constructor() : super()

}

/**
* Constructor for [AbstractProtectCallback]. Capable of parsing the [MetadataCallback] used for Protect.
*/
@Keep
constructor(jsonObject: JSONObject, index: Int) : super(jsonObject, index) {
val type = jsonObject.optString("type")
if (type == "MetadataCallback") {
derivedCallback = true
jsonObject.optJSONArray("output")?.let { output ->
output.getJSONObject(0)?.let { nameValuePair ->
if (nameValuePair.optString("name") == "data") {
nameValuePair.optJSONObject("value")?.let { value ->
value.keys().forEach { attr ->
setAttribute(attr, value.get(attr))
}
}
}
}
}
}
}

@Keep
constructor() : super()

/**
* The [Node] that associate with this Callback
*/
private lateinit var node: Node

override fun setNode(node: Node) {
this.node = node
}

/**
* Get the [Node] that associate with this Callback
*
* @return The [Node] that associate with this Callback
*/
protected fun getNode(): Node {
return node
}

/**
* Input the Client Error to the server
*
* @param value Protect ErrorType
* @param index The index of the error
*/
fun setClientError(value: String, index: Int) {
if (derivedCallback) {
setClientErrorInHiddenCallback(value);
} else {
super.setValue(value, index)
}
}

/**
* Set the client error to the [HiddenValueCallback] which associated with the Protect
* Callback.
*
* @param value The Value to set to the [HiddenValueCallback]
*/
private fun setClientErrorInHiddenCallback(value: String) {
for (callback in node.callbacks) {
if (callback is HiddenValueCallback) {
if (callback.id.contains(CLIENT_ERROR)) {
callback.value = value
}
}
}
}

companion object {

/**
* Check if this callback is [PingOneProtectInitializeCallback] Type
*
* @param value The callback raw data json.
* @return True if this is a [PingOneProtectInitializeCallback] Type, else false
*/
@JvmStatic
fun isPingOneProtectInitializeCallback(value: JSONObject): Boolean {
return try {
value.has(_TYPE) && value.getString(_TYPE) == PING_ONE_PROTECT && (value.has(
_ACTION
) && value.getString(_ACTION) == PROTECT_INITIALIZE)
} catch (e: JSONException) {
false
}
}

/**
* Check if this callback is [PingOneProtectEvaluationCallback] Type
*
* @param value The callback raw data json.
* @return True if this is a [PingOneProtectEvaluationCallback] Type, else false
*/
@JvmStatic
fun isPingOneProtectEvaluationCallback(value: JSONObject): Boolean {
return try {
value.has(_TYPE) && value.getString(_TYPE) == PING_ONE_PROTECT && (value.has(
_ACTION
) && value.getString(_ACTION) == PROTECT_RISK_EVALUATION)
} catch (e: JSONException) {
false
}
}

}

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
/*
* Copyright (c) 2021 - 2022 ForgeRock. All rights reserved.
* Copyright (c) 2021 - 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.forgerock.android.auth.callback.AbstractProtectCallbackKt.PING_ONE_PROTECT_EVALUATION_CALLBACK;
import static org.forgerock.android.auth.callback.AbstractProtectCallbackKt.PING_ONE_PROTECT_INITIALIZE_CALLBACK;

import androidx.annotation.Keep;

import org.json.JSONException;
Expand Down Expand Up @@ -86,6 +89,12 @@ public Class<? extends Callback> getDerivedCallback() {
if (WebAuthnAuthenticationCallback.instanceOf(value)) {
return CallbackFactory.getInstance().getCallbacks().get("WebAuthnAuthenticationCallback");
}
if (AbstractProtectCallback.isPingOneProtectInitializeCallback(value)) {
return CallbackFactory.getInstance().getCallbacks().get(PING_ONE_PROTECT_INITIALIZE_CALLBACK);
}
if (AbstractProtectCallback.isPingOneProtectEvaluationCallback(value)) {
return CallbackFactory.getInstance().getCallbacks().get(PING_ONE_PROTECT_EVALUATION_CALLBACK);
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -58,6 +58,22 @@ class MetadataCallbackTest : BaseTest() {
WebAuthnAuthenticationCallback::class.java)
}

@Test
@Throws(JSONException::class)
fun testDerivedCallbackProtectInitialize() {
val callback = MetadataCallback(JSONObject(getJson("/protect_initialize.json"))
.getJSONArray("callbacks").getJSONObject(0), 0)
Assertions.assertThat(AbstractProtectCallback.isPingOneProtectInitializeCallback(callback.value)).isTrue()
}

@Test
@Throws(JSONException::class)
fun testDerivedCallbackProtectRiskEvaluation() {
val callback = MetadataCallback(JSONObject(getJson("/protect_risk_evaluation.json"))
.getJSONArray("callbacks").getJSONObject(0), 0)
Assertions.assertThat(AbstractProtectCallback.isPingOneProtectEvaluationCallback(callback.value)).isTrue()
}

@Test
@Throws(JSONException::class)
fun testDerivedCallbackUndefined() {
Expand Down
46 changes: 46 additions & 0 deletions forgerock-auth/src/test/resources/protect_initialize.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"authId": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdXRoSW5kZXhWYWx1ZSI6IndlYmF1dGhuX3JlZyIsIm90ayI6Ijluc2E1YWVva2E3YXBuaW84bHBycXAxc242IiwiYXV0aEluZGV4VHlwZSI6InNlcnZpY2UiLCJyZWFsbSI6Ii8iLCJzZXNzaW9uSWQiOiIqQUFKVFNRQUNNREVBQkhSNWNHVUFDRXBYVkY5QlZWUklBQUpUTVFBQSpleUowZVhBaU9pSktWMVFpTENKamRIa2lPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5LlpYbEtNR1ZZUVdsUGFVcExWakZSYVV4RFNteGliVTFwVDJsS1FrMVVTVFJSTUVwRVRGVm9WRTFxVlRKSmFYZHBXVmQ0YmtscWIybGFSMng1U1c0d0xpNWpVSHBGZG1OVFpIRk9UbHB4UVZsaExWaEhhMWhSTG1wMVVscEZkRFZ4Y2tsRlpWQTNOMDh4TUhSbk5IbGlVRTV2UW5acmIzUjBiM2RXWDA5dGFYaDVjWE5uWm1OelFXVlJTR1pWVVRrd2NsaHRiRXBFVTNkNFZVUjZibEJrZUVWWVJXSllkRUZYUlhWeVdXWjVOMnhwT0Vaa1ZHcDNaSGxDT1VjM2Nrd3ROVlJsZG1VNFlsazRUM1ZSU0VOclkweDBSbEZYT1ZkdmNtRlpNSFpvWlVWMGJWZFhTM3BaWlZsT2FXSTJPSEV0Um5SUGFVcGlVbGxFUkRCeVdEWllPRFF4ZDJaRmNGaGxXRTF5UjBORVJEaE1aMnhWV0ZkMWNIVnRaelkxU0VsM1QwMDBRMnRFWVVjM1MybEZjMWxEUlRkRlVFOTJRM1ZqWTJReFNYaGtlbWt0ZWs5d09UVlVPR1IzTTFWcVZrbE5VMUpMUzNWSGJtVkVlRjl4VWxGdlNHOVFVa1ptU1dOek9XZHNObWxtWkRKd1gwSTBPR3hDTWpKRU5XZEJhaTFJUzBKRlRuRkhOSFV0TjBoNlgyOXROWGgwWVdNNWJISTBSRnBhZUMxcVRtUm1YMVp0ZERSNU4wWkpNRXQ0Tmkwd2NsTlNVVXRIT0ZsSFExWXdWMFZMTWt0QlMzcHRaalpyWVZaaVdETTFTR3BFVHpCMUxYUlFVV1ZSU1hnMVMwaFVXRmhMTmxNelZsOXBhVTlaTmxoTlpURXllV3A0Y0VWTlJUWmlSbkozWm5wc2VYQlpjSFJhZVZreE4xcGZYMWhVY2pkWk0xcHhTVlJST0RWaWJWZHZTMFowTkRoZlgyeEVWbTFZUkVSblIydHlWR3hMZFRoTlNVazFNWGszUWxKVFFrZEdkV2N4VUhwaVVuazRVRGx3TlZKaFJtVXRORU5GVEhGdFNsOUdMV2RDZG1ob1RUWldibUZaVVdoTGJ6Y3laR05rV2s1a1pUaEllazVCYm1WTVQzTkNVRE5aTkRscVdHVkNaR2gwVmpaalNsOXBMWHBxYjFSVVowSktVVkJXVUdoSVNtTnhkamhzTFRaa05sZGhlRWgzWnpaSmJUUlNhek4zTFVNMFlVVlBNREI0U1U5Nk0zVTJRVU5HVFRGakxVaE5jMUZsZVV0c1NtOUtOVFZuT0ZSMFJubE9WVFpVVm5OemRYVmlSRzVTTlVoYWJWazVVRGhuZEZSQmJ6VlBUakk0V1c5WVgxcDRZVk5aUXpGek1UUktTV3hoYzJ4NE1uTmFOelUzVWpVMGVFNUlWM3BtVDNkTlJtazVWWHA0VjFjNVpVVnVjSGw2YUhsT1NqRnlNelJNYnk1R1UyTnNabk10YkdSalpWSmhNVjh5VmtWWGRqaEIuSElHZFprUVNwbzR4U0dUVmlabEdSN3psRExvV09uOHhNQmt3MVRVZ05qVSIsImV4cCI6MTYxMzY5NTI1MywiaWF0IjoxNjEzNjk0OTUzfQ.BOdMUcaZbRNGkYAQu-Z_sBZMMHW4hdKDrXnBCbZhrxA",
"callbacks": [
{
"type": "MetadataCallback",
"output": [
{
"name": "data",
"value": {
"_type": "PingOneProtect",
"_action": "protect_initialize",
"envId": "some_id",
"consoleLogEnabled": true,
"deviceAttributesToIgnore": [],
"customHost": "",
"lazyMetadata": true,
"behavioralDataCollection": true,
"disableHub": true,
"deviceKeyRsyncIntervals": 10,
"enableTrust": true,
"disableTags": true
}
}
]
},
{
"type": "HiddenValueCallback",
"output": [
{
"name": "value",
"value": ""
},
{
"name": "id",
"value": "clientError"
}
],
"input": [
{
"name": "IDToken2",
"value": "clientError"
}
]
}
]
}
57 changes: 57 additions & 0 deletions forgerock-auth/src/test/resources/protect_risk_evaluation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{
"authId": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdXRoSW5kZXhWYWx1ZSI6IndlYmF1dGhuX3JlZyIsIm90ayI6Ijluc2E1YWVva2E3YXBuaW84bHBycXAxc242IiwiYXV0aEluZGV4VHlwZSI6InNlcnZpY2UiLCJyZWFsbSI6Ii8iLCJzZXNzaW9uSWQiOiIqQUFKVFNRQUNNREVBQkhSNWNHVUFDRXBYVkY5QlZWUklBQUpUTVFBQSpleUowZVhBaU9pSktWMVFpTENKamRIa2lPaUpLVjFRaUxDSmhiR2NpT2lKSVV6STFOaUo5LlpYbEtNR1ZZUVdsUGFVcExWakZSYVV4RFNteGliVTFwVDJsS1FrMVVTVFJSTUVwRVRGVm9WRTFxVlRKSmFYZHBXVmQ0YmtscWIybGFSMng1U1c0d0xpNWpVSHBGZG1OVFpIRk9UbHB4UVZsaExWaEhhMWhSTG1wMVVscEZkRFZ4Y2tsRlpWQTNOMDh4TUhSbk5IbGlVRTV2UW5acmIzUjBiM2RXWDA5dGFYaDVjWE5uWm1OelFXVlJTR1pWVVRrd2NsaHRiRXBFVTNkNFZVUjZibEJrZUVWWVJXSllkRUZYUlhWeVdXWjVOMnhwT0Vaa1ZHcDNaSGxDT1VjM2Nrd3ROVlJsZG1VNFlsazRUM1ZSU0VOclkweDBSbEZYT1ZkdmNtRlpNSFpvWlVWMGJWZFhTM3BaWlZsT2FXSTJPSEV0Um5SUGFVcGlVbGxFUkRCeVdEWllPRFF4ZDJaRmNGaGxXRTF5UjBORVJEaE1aMnhWV0ZkMWNIVnRaelkxU0VsM1QwMDBRMnRFWVVjM1MybEZjMWxEUlRkRlVFOTJRM1ZqWTJReFNYaGtlbWt0ZWs5d09UVlVPR1IzTTFWcVZrbE5VMUpMUzNWSGJtVkVlRjl4VWxGdlNHOVFVa1ptU1dOek9XZHNObWxtWkRKd1gwSTBPR3hDTWpKRU5XZEJhaTFJUzBKRlRuRkhOSFV0TjBoNlgyOXROWGgwWVdNNWJISTBSRnBhZUMxcVRtUm1YMVp0ZERSNU4wWkpNRXQ0Tmkwd2NsTlNVVXRIT0ZsSFExWXdWMFZMTWt0QlMzcHRaalpyWVZaaVdETTFTR3BFVHpCMUxYUlFVV1ZSU1hnMVMwaFVXRmhMTmxNelZsOXBhVTlaTmxoTlpURXllV3A0Y0VWTlJUWmlSbkozWm5wc2VYQlpjSFJhZVZreE4xcGZYMWhVY2pkWk0xcHhTVlJST0RWaWJWZHZTMFowTkRoZlgyeEVWbTFZUkVSblIydHlWR3hMZFRoTlNVazFNWGszUWxKVFFrZEdkV2N4VUhwaVVuazRVRGx3TlZKaFJtVXRORU5GVEhGdFNsOUdMV2RDZG1ob1RUWldibUZaVVdoTGJ6Y3laR05rV2s1a1pUaEllazVCYm1WTVQzTkNVRE5aTkRscVdHVkNaR2gwVmpaalNsOXBMWHBxYjFSVVowSktVVkJXVUdoSVNtTnhkamhzTFRaa05sZGhlRWgzWnpaSmJUUlNhek4zTFVNMFlVVlBNREI0U1U5Nk0zVTJRVU5HVFRGakxVaE5jMUZsZVV0c1NtOUtOVFZuT0ZSMFJubE9WVFpVVm5OemRYVmlSRzVTTlVoYWJWazVVRGhuZEZSQmJ6VlBUakk0V1c5WVgxcDRZVk5aUXpGek1UUktTV3hoYzJ4NE1uTmFOelUzVWpVMGVFNUlWM3BtVDNkTlJtazVWWHA0VjFjNVpVVnVjSGw2YUhsT1NqRnlNelJNYnk1R1UyTnNabk10YkdSalpWSmhNVjh5VmtWWGRqaEIuSElHZFprUVNwbzR4U0dUVmlabEdSN3psRExvV09uOHhNQmt3MVRVZ05qVSIsImV4cCI6MTYxMzY5NTI1MywiaWF0IjoxNjEzNjk0OTUzfQ.BOdMUcaZbRNGkYAQu-Z_sBZMMHW4hdKDrXnBCbZhrxA",
"callbacks": [
{
"type": "MetadataCallback",
"output": [
{
"name": "data",
"value": {
"_type": "PingOneProtect",
"_action": "protect_risk_evaluation",
"envId" : "some_id",
"pauseBehavioralData" : true
}
}
]
},
{
"type": "HiddenValueCallback",
"output": [
{
"name": "value",
"value": ""
},
{
"name": "id",
"value": "pingone_risk_evaluation_signals"
}
],
"input": [
{
"name": "IDToken1",
"value": "pingone_risk_evaluation_signals"
}
]
},
{
"type": "HiddenValueCallback",
"output": [
{
"name": "value",
"value": ""
},
{
"name": "id",
"value": "clientError"
}
],
"input": [
{
"name": "IDToken1",
"value": "clientError"
}
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,24 @@ package org.forgerock.android.auth

import android.content.Context
import androidx.annotation.Keep
import org.forgerock.android.auth.callback.AbstractCallback
import org.forgerock.android.auth.callback.AbstractProtectCallback
witrisna marked this conversation as resolved.
Show resolved Hide resolved
import org.forgerock.android.auth.callback.HiddenValueCallback
import org.forgerock.android.auth.callback.PING_ONE_RISK_EVALUATION_SIGNALS
import org.json.JSONObject

private val TAG = PingOneProtectEvaluationCallback::class.java.simpleName

/**
* Callback to evaluate Ping One Protect
*/
open class PingOneProtectEvaluationCallback : AbstractCallback {
open class PingOneProtectEvaluationCallback : AbstractProtectCallback {

@Keep
Copy link
Contributor

@jeyanthanperiyasamy jeyanthanperiyasamy Aug 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here. lets keep both constructors and PingOneProtectEvaluationCallback: AbstractProtectCallback

constructor(jsonObject: JSONObject, index: Int) : super(jsonObject, index)

@Keep
constructor() : super()

/**
* The pauseBehavioralData received from server
*/
Expand All @@ -43,15 +46,26 @@ open class PingOneProtectEvaluationCallback : AbstractCallback {
* @param value The JWS value.
*/
fun setSignals(value: String) {
super.setValue(value, 0)
if (derivedCallback) {
setSignalsInHiddenCallback(value)
} else {
super.setValue(value, 0)
}
}

/**
* Input the Client Error to the server
* @param value Protect ErrorType .
* Set the signals to the [HiddenValueCallback] which associated with the callback.
*
* @param value The Value to set to the [HiddenValueCallback].
*/
fun setClientError(value: String) {
super.setValue(value, 1)
private fun setSignalsInHiddenCallback(value: String) {
for (callback in getNode().callbacks) {
if (callback is HiddenValueCallback) {
if (callback.id.contains(PING_ONE_RISK_EVALUATION_SIGNALS)) {
callback.value = value
}
}
}
}

/**
Expand All @@ -69,10 +83,11 @@ open class PingOneProtectEvaluationCallback : AbstractCallback {
}
catch (e: Exception) {
Logger.error(TAG, t = e, message = e.message)
setClientError(e.message ?: "clientError")
setClientError(e.message ?: "clientError", 1)
throw e
}
}

}

/**
Expand Down
Loading
Loading