Skip to content

Commit ceb40f6

Browse files
committed
fix(wallet): require client ID for invalid previews
1 parent b562c18 commit ceb40f6

7 files changed

Lines changed: 59 additions & 16 deletions

File tree

waltid-libraries/protocols/waltid-openid4vc-wallet-mobile/api/waltid-openid4vc-wallet-mobile.klib.api

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -820,10 +820,10 @@ final class id.walt.wallet2.mobile/MobileWalletPresentationPreview { // id.walt.
820820
}
821821

822822
final class id.walt.wallet2.mobile/MobileWalletPresentationRequestContext { // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext|null[0]
823-
constructor <init>(kotlin/String?, kotlin/String?, kotlin/String?, kotlin/String?, kotlin/String?) // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.<init>|<init>(kotlin.String?;kotlin.String?;kotlin.String?;kotlin.String?;kotlin.String?){}[0]
823+
constructor <init>(kotlin/String, kotlin/String?, kotlin/String?, kotlin/String?, kotlin/String?) // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.<init>|<init>(kotlin.String;kotlin.String?;kotlin.String?;kotlin.String?;kotlin.String?){}[0]
824824

825825
final val clientId // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.clientId|{}clientId[0]
826-
final fun <get-clientId>(): kotlin/String? // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.clientId.<get-clientId>|<get-clientId>(){}[0]
826+
final fun <get-clientId>(): kotlin/String // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.clientId.<get-clientId>|<get-clientId>(){}[0]
827827
final val nonce // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.nonce|{}nonce[0]
828828
final fun <get-nonce>(): kotlin/String? // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.nonce.<get-nonce>|<get-nonce>(){}[0]
829829
final val responseUri // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.responseUri|{}responseUri[0]
@@ -833,12 +833,12 @@ final class id.walt.wallet2.mobile/MobileWalletPresentationRequestContext { // i
833833
final val verifierName // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.verifierName|{}verifierName[0]
834834
final fun <get-verifierName>(): kotlin/String? // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.verifierName.<get-verifierName>|<get-verifierName>(){}[0]
835835

836-
final fun component1(): kotlin/String? // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.component1|component1(){}[0]
836+
final fun component1(): kotlin/String // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.component1|component1(){}[0]
837837
final fun component2(): kotlin/String? // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.component2|component2(){}[0]
838838
final fun component3(): kotlin/String? // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.component3|component3(){}[0]
839839
final fun component4(): kotlin/String? // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.component4|component4(){}[0]
840840
final fun component5(): kotlin/String? // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.component5|component5(){}[0]
841-
final fun copy(kotlin/String? = ..., kotlin/String? = ..., kotlin/String? = ..., kotlin/String? = ..., kotlin/String? = ...): id.walt.wallet2.mobile/MobileWalletPresentationRequestContext // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.copy|copy(kotlin.String?;kotlin.String?;kotlin.String?;kotlin.String?;kotlin.String?){}[0]
841+
final fun copy(kotlin/String = ..., kotlin/String? = ..., kotlin/String? = ..., kotlin/String? = ..., kotlin/String? = ...): id.walt.wallet2.mobile/MobileWalletPresentationRequestContext // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.copy|copy(kotlin.String;kotlin.String?;kotlin.String?;kotlin.String?;kotlin.String?){}[0]
842842
final fun equals(kotlin/Any?): kotlin/Boolean // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.equals|equals(kotlin.Any?){}[0]
843843
final fun hashCode(): kotlin/Int // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.hashCode|hashCode(){}[0]
844844
final fun toString(): kotlin/String // id.walt.wallet2.mobile/MobileWalletPresentationRequestContext.toString|toString(){}[0]

waltid-libraries/protocols/waltid-openid4vc-wallet-mobile/src/commonMain/kotlin/id/walt/wallet2/mobile/MobileWallet.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,9 @@ private fun AuthorizationRequest.toMobileRequestInfo(
564564

565565
private fun AuthorizationRequest.toMobileRequestContext(): MobileWalletPresentationRequestContext =
566566
MobileWalletPresentationRequestContext(
567-
clientId = clientId,
567+
clientId = requireNotNull(clientId) {
568+
"A reportable invalid presentation request must contain client_id."
569+
},
568570
verifierName = clientMetadata?.clientName,
569571
responseUri = responseUri,
570572
state = state,

waltid-libraries/protocols/waltid-openid4vc-wallet-mobile/src/commonMain/kotlin/id/walt/wallet2/mobile/MobileWalletPresentationModels.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,23 @@ public sealed interface MobileWalletPresentationPreviewResult {
3535
/**
3636
* Partial request context retained when an OpenID4VP request is invalid.
3737
*
38-
* Invalid requests may fail before required authorization parameters have been resolved, so
39-
* [clientId] and [nonce] remain nullable here. A ready preview instead exposes the validated,
40-
* non-null values through [MobileWalletPresentationRequestInfo].
38+
* A reportable invalid request has a validated, non-blank [clientId]. Its [nonce] remains nullable
39+
* because a missing nonce can itself be the validation error. A ready preview instead exposes a
40+
* validated, non-null nonce through [MobileWalletPresentationRequestInfo].
4141
*/
4242
public data class MobileWalletPresentationRequestContext(
43-
val clientId: String?,
43+
val clientId: String,
4444
val verifierName: String?,
4545
val responseUri: String?,
4646
val state: String?,
4747
val nonce: String?,
48-
)
48+
) {
49+
init {
50+
require(clientId.isNotBlank()) {
51+
"A reportable presentation request client ID must not be blank."
52+
}
53+
}
54+
}
4955

5056
/**
5157
* A required presentation credential-query combination.

waltid-libraries/protocols/waltid-openid4vc-wallet-mobile/src/commonTest/kotlin/id/walt/wallet2/mobile/MobileWalletTest.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,29 @@ class MobileWalletTest {
232232
}
233233
}
234234

235+
@Test
236+
fun presentationRequestContextRequiresClientIdButAllowsMissingNonce() {
237+
assertFailsWith<IllegalArgumentException> {
238+
MobileWalletPresentationRequestContext(
239+
clientId = " ",
240+
verifierName = null,
241+
responseUri = null,
242+
state = null,
243+
nonce = null,
244+
)
245+
}
246+
247+
val context = MobileWalletPresentationRequestContext(
248+
clientId = "https://verifier.example",
249+
verifierName = null,
250+
responseUri = null,
251+
state = null,
252+
nonce = null,
253+
)
254+
assertEquals("https://verifier.example", context.clientId)
255+
assertEquals(null, context.nonce)
256+
}
257+
235258
@Test
236259
fun presentationDisclosuresRejectImpossibleSelectableStates() {
237260
assertFailsWith<IllegalArgumentException> {

waltid-libraries/protocols/waltid-openid4vc-wallet-mobile/src/iosTest/kotlin/id/walt/wallet2/mobile/swiftinterop/WalletSdkBridgeTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ class WalletSdkBridgeTest {
198198
verifierName = "Example Verifier",
199199
responseUri = "https://verifier.example/direct-post",
200200
state = "state-1",
201-
nonce = "nonce-1",
201+
nonce = null,
202202
),
203203
errorCode = MobileWalletPresentationErrorCode.invalidTransactionData,
204204
message = "Unsupported transaction data type",

waltid-libraries/protocols/waltid-wallet-sdk-ios/Sources/WalletSDK/WalletModels.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -705,12 +705,13 @@ public struct PresentationCredentialRequirement: Equatable, Sendable {
705705

706706
/// Partial request context retained when an OpenID4VP request is invalid.
707707
///
708-
/// Invalid requests may fail before required authorization parameters have been
709-
/// resolved. A ready preview instead exposes validated, non-optional values
708+
/// A reportable invalid request has a validated, non-blank client identifier.
709+
/// Its nonce remains optional because a missing nonce can itself be the
710+
/// validation error. A ready preview exposes a validated, non-optional nonce
710711
/// through ``PresentationRequestInfo``.
711712
public struct PresentationRequestContext: Equatable, Sendable {
712-
/// OpenID4VP client identifier when available.
713-
public let clientID: String?
713+
/// Validated OpenID4VP client identifier.
714+
public let clientID: String
714715

715716
/// Human-readable verifier name from client metadata when available.
716717
public let verifierName: String?
@@ -726,18 +727,26 @@ public struct PresentationRequestContext: Equatable, Sendable {
726727

727728
/// Creates partial presentation request context.
728729
public init(
729-
clientID: String? = nil,
730+
clientID: String,
730731
verifierName: String? = nil,
731732
responseURI: URL? = nil,
732733
state: String? = nil,
733734
nonce: String? = nil
734735
) {
736+
precondition(
737+
Self.hasValidClientID(clientID),
738+
"A reportable presentation request must contain a non-blank client ID."
739+
)
735740
self.clientID = clientID
736741
self.verifierName = verifierName
737742
self.responseURI = responseURI
738743
self.state = state
739744
self.nonce = nonce
740745
}
746+
747+
static func hasValidClientID(_ clientID: String) -> Bool {
748+
isNonBlank(clientID)
749+
}
741750
}
742751

743752
/// Verifier and transaction metadata extracted from a presentation request.

waltid-libraries/protocols/waltid-wallet-sdk-ios/Tests/WalletSDKTests/WalletAPITests.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,9 @@ final class WalletAPITests: XCTestCase {
527527
}
528528

529529
func testPresentationModelValidationRejectsInvalidStates() {
530+
XCTAssertFalse(PresentationRequestContext.hasValidClientID(" "))
531+
XCTAssertTrue(PresentationRequestContext.hasValidClientID("https://verifier.example"))
532+
530533
XCTAssertFalse(PresentationCredentialRequirement.hasValidOptions([]))
531534
XCTAssertFalse(PresentationCredentialRequirement.hasValidOptions([[]]))
532535
XCTAssertFalse(PresentationCredentialRequirement.hasValidOptions([[" "]]))

0 commit comments

Comments
 (0)