Skip to content
Open
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
Expand Up @@ -14,6 +14,8 @@ import io.mosip.openID4VP.constants.ResponseMode.DIRECT_POST
import io.mosip.openID4VP.constants.ResponseMode.DIRECT_POST_JWT
import io.mosip.openID4VP.constants.ResponseMode.IAR_POST
import io.mosip.openID4VP.constants.ResponseMode.IAR_POST_JWT
import io.mosip.openID4VP.constants.ResponseMode.IAE_POST
import io.mosip.openID4VP.constants.ResponseMode.IAE_POST_JWT
import io.mosip.openID4VP.constants.SpecVersion
import io.mosip.openID4VP.exceptions.OpenID4VPExceptions
import java.security.PublicKey
Expand All @@ -36,6 +38,7 @@ class RedirectUriPrefixAuthorizationRequestHandler(
setResponseUri,
walletNonce
) {

private val logger = Logger.getLogger(className)

override fun isSignedRequestSupported(): Boolean {
Expand All @@ -50,8 +53,13 @@ class RedirectUriPrefixAuthorizationRequestHandler(
return ClientIdPrefix.REDIRECT_URI.value
}

override fun extractPublicKey(algorithm: SignatureAlgorithm, kid: String?): PublicKey {
throw UnsupportedOperationException("Public key extraction is not supported for redirect_uri client_id_prefix")
override fun extractPublicKey(
algorithm: SignatureAlgorithm,
kid: String?
): PublicKey {
throw UnsupportedOperationException(
"Public key extraction is not supported for redirect_uri client_id_prefix"
)
}

override fun getWalletMetadata(walletConfig: WalletConfig): Map<String, Any> {
Expand All @@ -61,23 +69,45 @@ class RedirectUriPrefixAuthorizationRequestHandler(

override fun validateAndParseRequestFields() {
super.validateAndParseRequestFields()
val responseMode = getStringValue(authorizationRequestParameters, RESPONSE_MODE.value) ?:
throw OpenID4VPExceptions.MissingInput(listOf(RESPONSE_MODE.value), "", className)
when (responseMode) {
DIRECT_POST.value, DIRECT_POST_JWT.value -> {

val responseMode =
getStringValue(authorizationRequestParameters, RESPONSE_MODE.value)
?: throw OpenID4VPExceptions.MissingInput(
listOf(RESPONSE_MODE.value),
"",
className
)

when (responseMode) {
DIRECT_POST.value,
DIRECT_POST_JWT.value -> {
validateResponseUriMatchesClientId(authorizationRequestParameters)
}
IAR_POST.value, IAR_POST_JWT.value -> {
logger.info("IAR_POST or IAR_POST_JWT response_mode is used")
}
else -> throw OpenID4VPExceptions.InvalidData("Given response_mode is not supported", className)

IAR_POST.value,
IAR_POST_JWT.value,
IAE_POST.value,
IAE_POST_JWT.value -> {
logger.info("IAR/IAE response_mode is used")
}

else -> throw OpenID4VPExceptions.InvalidData(
"Given response_mode is not supported",
className
)
}
}

private fun validateResponseUriMatchesClientId(authRequestParam: Map<String, Any>) {
val responseUri = getStringValue(authRequestParam, RESPONSE_URI.value)

validate(RESPONSE_URI.value, responseUri, className)
if (authRequestParam[RESPONSE_URI.value] != extractClientIdPartOnly(authRequestParam))
throw OpenID4VPExceptions.InvalidData("${RESPONSE_URI.value} should be equal to client_id for given client_id_prefix", className)

if (authRequestParam[RESPONSE_URI.value] != extractClientIdPartOnly(authRequestParam)) {
throw OpenID4VPExceptions.InvalidData(
"${RESPONSE_URI.value} should be equal to client_id for given client_id_prefix",
className
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,9 @@ private fun validateResponseModeForMsoMdocFormat(presentationDefinitionObj: Pres
it.format?.containsKey("mso_mdoc") ?: false
}

if (hasMsoMdocFormat && (responseMode != ResponseMode.DIRECT_POST_JWT.value && responseMode != ResponseMode.IAR_POST_JWT.value)) {
throw OpenID4VPExceptions.InvalidData("When mso_mdoc format is present in presentation definition, response_mode must be direct_post.jwt or iar-post.jwt", className)
if (hasMsoMdocFormat && (responseMode != ResponseMode.DIRECT_POST_JWT.value && responseMode != ResponseMode.IAR_POST_JWT.value &&
responseMode != ResponseMode.IAE_POST_JWT.value)) {
throw OpenID4VPExceptions.InvalidData("When mso_mdoc format is present in presentation definition, response_mode must be direct_post.jwt or iar-post.jwt or iae_post_jwt", className)
Comment on lines +155 to +157

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the actual iae_post.jwt literal in the exception text.

The new guard accepts ResponseMode.IAE_POST_JWT.value, but the message says iae_post_jwt. That points callers to a value the library does not support.

Proposed fix
-        throw OpenID4VPExceptions.InvalidData("When mso_mdoc format is present in presentation definition, response_mode must be direct_post.jwt or iar-post.jwt or iae_post_jwt", className)
+        throw OpenID4VPExceptions.InvalidData(
+            "When mso_mdoc format is present in presentation definition, response_mode must be direct_post.jwt or iar-post.jwt or iae_post.jwt",
+            className
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (hasMsoMdocFormat && (responseMode != ResponseMode.DIRECT_POST_JWT.value && responseMode != ResponseMode.IAR_POST_JWT.value &&
responseMode != ResponseMode.IAE_POST_JWT.value)) {
throw OpenID4VPExceptions.InvalidData("When mso_mdoc format is present in presentation definition, response_mode must be direct_post.jwt or iar-post.jwt or iae_post_jwt", className)
if (hasMsoMdocFormat && (responseMode != ResponseMode.DIRECT_POST_JWT.value && responseMode != ResponseMode.IAR_POST_JWT.value &&
responseMode != ResponseMode.IAE_POST_JWT.value)) {
throw OpenID4VPExceptions.InvalidData(
"When mso_mdoc format is present in presentation definition, response_mode must be direct_post.jwt or iar-post.jwt or iae_post.jwt",
className
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@kotlin/openID4VP/src/commonMain/kotlin/io/mosip/openID4VP/authorizationRequest/presentationDefinition/PresentationDefinitionUtil.kt`
around lines 155 - 157, The validation in PresentationDefinitionUtil should use
the actual response mode literal supported by ResponseMode.IAE_POST_JWT.value,
since the current exception text mentions an unsupported `iae_post_jwt` value.
Update the message in the mso_mdoc guard to reference the exact supported
literal used by the enum, alongside the existing `direct_post.jwt` and
`iar-post.jwt` values, so callers see a consistent contract.


}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.mosip.openID4VP.constants

enum class ResponseMode(val value: String) {
enum class ResponseMode(val value: String) {
DIRECT_POST("direct_post"),
DIRECT_POST_JWT("direct_post.jwt"),

IAR_POST("iar-post"),
IAR_POST_JWT("iar-post.jwt"),
}

IAE_POST("iae_post"),
IAE_POST_JWT("iae_post.jwt"),
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.mosip.openID4VP.responseModeHandler

import io.mosip.openID4VP.constants.ResponseMode.IAE_POST
import io.mosip.openID4VP.constants.ResponseMode.IAE_POST_JWT
import io.mosip.openID4VP.constants.ResponseMode.*
import io.mosip.openID4VP.exceptions.OpenID4VPExceptions
import io.mosip.openID4VP.responseModeHandler.types.DirectPostJwtResponseModeHandler
Expand All @@ -9,10 +11,19 @@ private val className = ResponseModeBasedHandlerFactory::class.simpleName!!

object ResponseModeBasedHandlerFactory {
fun get(responseMode: String): ResponseModeBasedHandler =
when(responseMode) {
DIRECT_POST.value, IAR_POST.value -> DirectPostResponseModeHandler()
DIRECT_POST_JWT.value, IAR_POST_JWT.value -> DirectPostJwtResponseModeHandler()
when (responseMode) {
DIRECT_POST.value,
IAR_POST.value,
IAE_POST.value -> DirectPostResponseModeHandler()

DIRECT_POST_JWT.value,
IAR_POST_JWT.value,
IAE_POST_JWT.value -> DirectPostJwtResponseModeHandler()

else ->
throw OpenID4VPExceptions.InvalidData("Given response_mode is not supported", className)
throw OpenID4VPExceptions.InvalidData(
"Given response_mode is not supported",
className
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import io.mosip.openID4VP.testData.responseUrl
import org.junit.jupiter.api.Test
import kotlin.test.*

class RedirectUriSchemeAuthorizationRequestHandlerTest {
class RedirectUriPrefixAuthorizationRequestHandlerTest {

private lateinit var authorizationRequestParameters: MutableMap<String, Any>
private lateinit var walletConfig: WalletConfig
Expand Down Expand Up @@ -145,7 +145,11 @@ class RedirectUriSchemeAuthorizationRequestHandlerTest {

@Test
fun `validateAndParseRequestFields should allow v1 jwt response modes without state when holder binding is required`() {
listOf("direct_post.jwt", "iar-post.jwt").forEach { responseMode ->
listOf(
"direct_post.jwt",
"iar-post.jwt",
"iae_post.jwt"
).forEach { responseMode ->
val modifiedParams = createV1DcqlParams(responseMode, requireHolderBinding = true, includeState = false)

assertDoesNotThrow {
Expand All @@ -156,7 +160,11 @@ class RedirectUriSchemeAuthorizationRequestHandlerTest {

@Test
fun `validateAndParseRequestFields should require state for v1 jwt response modes when holder binding is disabled`() {
listOf("direct_post.jwt", "iar-post.jwt").forEach { responseMode ->
listOf(
"direct_post.jwt",
"iar-post.jwt",
"iae_post.jwt"
).forEach { responseMode ->
val modifiedParams = createV1DcqlParams(responseMode, requireHolderBinding = false, includeState = false)

val exception = assertFailsWith<OpenID4VPExceptions.MissingInput> {
Expand Down Expand Up @@ -238,65 +246,73 @@ class RedirectUriSchemeAuthorizationRequestHandlerTest {
assertTrue(exception.message?.contains("response_uri should be equal to client_id") == true)
}

@Test
fun `validateAndParseRequestFields should succeed with iar-post response mode`() {
@Test
fun `validateAndParseRequestFields should succeed with IAR and IAE response modes`() {
listOf(
"iar-post",
"iar-post.jwt",
"iae_post",
"iae_post.jwt"
).forEach { responseMode ->
val modifiedParams = authorizationRequestParameters.toMutableMap()
modifiedParams[RESPONSE_MODE.value] = "iar-post"
assertDoesNotThrow { createHandler(modifiedParams).validateAndParseRequestFields() }
}
modifiedParams[RESPONSE_MODE.value] = responseMode

@Test
fun `validateAndParseRequestFields should succeed with iar-post_jwt response mode`() {
val modifiedParams = authorizationRequestParameters.toMutableMap()
modifiedParams[RESPONSE_MODE.value] = "iar-post.jwt"
assertDoesNotThrow { createHandler(modifiedParams).validateAndParseRequestFields() }
assertDoesNotThrow {
createHandler(modifiedParams).validateAndParseRequestFields()
}
}

@Test
fun `validateAndParseRequestFields should succeed with iar-post when redirect_uri is present`() {
}
@Test
fun `validateAndParseRequestFields should succeed with IAR and IAE response modes when redirect_uri is present`() {
listOf(
"iar-post",
"iar-post.jwt",
"iae_post",
"iae_post.jwt"
).forEach { responseMode ->
val modifiedParams = authorizationRequestParameters.toMutableMap()
modifiedParams[RESPONSE_MODE.value] = "iar-post"
modifiedParams[RESPONSE_MODE.value] = responseMode
modifiedParams[REDIRECT_URI.value] = "https://example.com/redirect"
assertDoesNotThrow { createHandler(modifiedParams).validateAndParseRequestFields() }
}

@Test
fun `validateAndParseRequestFields should succeed with iar-post_jwt when redirect_uri is present`() {
val modifiedParams = authorizationRequestParameters.toMutableMap()
modifiedParams[RESPONSE_MODE.value] = "iar-post.jwt"
modifiedParams[REDIRECT_URI.value] = "https://example.com/redirect"
assertDoesNotThrow { createHandler(modifiedParams).validateAndParseRequestFields() }
assertDoesNotThrow {
createHandler(modifiedParams).validateAndParseRequestFields()
}
}
}

@Test
fun `validateAndParseRequestFields should succeed with iar-post when response_uri is missing`() {
fun `validateAndParseRequestFields should succeed with IAR and IAE response modes when response_uri is missing`() {
listOf(
"iar-post",
"iar-post.jwt",
"iae_post",
"iae_post.jwt"
).forEach { responseMode ->
val modifiedParams = authorizationRequestParameters.toMutableMap()
modifiedParams[RESPONSE_MODE.value] = "iar-post"
modifiedParams[RESPONSE_MODE.value] = responseMode
modifiedParams.remove(RESPONSE_URI.value)
assertDoesNotThrow { createHandler(modifiedParams).validateAndParseRequestFields() }
}

@Test
fun `validateAndParseRequestFields should succeed with iar-post_jwt when response_uri is missing`() {
val modifiedParams = authorizationRequestParameters.toMutableMap()
modifiedParams[RESPONSE_MODE.value] = "iar-post.jwt"
modifiedParams.remove(RESPONSE_URI.value)
assertDoesNotThrow { createHandler(modifiedParams).validateAndParseRequestFields() }
assertDoesNotThrow {
createHandler(modifiedParams).validateAndParseRequestFields()
}
}
}

@Test
fun `validateAndParseRequestFields should succeed with iar-post when response_uri doesn't match client_id`() {
@Test
fun `validateAndParseRequestFields should succeed with IAR and IAE response modes when response_uri doesn't match client_id`() {
listOf(
"iar-post",
"iar-post.jwt",
"iae_post",
"iae_post.jwt"
).forEach { responseMode ->
val modifiedParams = authorizationRequestParameters.toMutableMap()
modifiedParams[RESPONSE_MODE.value] = "iar-post"
modifiedParams[RESPONSE_MODE.value] = responseMode
modifiedParams[RESPONSE_URI.value] = "https://different-domain.com/response"
assertDoesNotThrow { createHandler(modifiedParams).validateAndParseRequestFields() }
}

@Test
fun `validateAndParseRequestFields should succeed with iar-post_jwt when response_uri doesn't match client_id`() {
val modifiedParams = authorizationRequestParameters.toMutableMap()
modifiedParams[RESPONSE_MODE.value] = "iar-post.jwt"
modifiedParams[RESPONSE_URI.value] = "https://different-domain.com/response"
assertDoesNotThrow { createHandler(modifiedParams).validateAndParseRequestFields() }
assertDoesNotThrow {
createHandler(modifiedParams).validateAndParseRequestFields()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class ResponseModeBasedHandlerFactoryTest {
assertNotNull(handler)
}


@Test
fun `get should return DirectPostJwtResponseModeHandler for direct_post_jwt mode`() {
val handler = ResponseModeBasedHandlerFactory.get(ResponseMode.DIRECT_POST_JWT.value)
Expand All @@ -40,6 +41,14 @@ class ResponseModeBasedHandlerFactoryTest {
assertNotNull(handler)
}

@Test

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Add a test case for iae_post return DirectPostResponseModeHandler

fun `get should return DirectPostJwtResponseModeHandler for iae_post_jwt mode`() {
val handler = ResponseModeBasedHandlerFactory.get(ResponseMode.IAE_POST_JWT.value)

assertTrue(handler is DirectPostJwtResponseModeHandler)
assertNotNull(handler)
}

@Test
fun `get should return new instances each time for direct_post mode`() {
val handler1 = ResponseModeBasedHandlerFactory.get(ResponseMode.DIRECT_POST.value)
Expand Down