diff --git a/tw2023_wallet.xcodeproj/project.pbxproj b/tw2023_wallet.xcodeproj/project.pbxproj index 334c07b..4bf94ea 100644 --- a/tw2023_wallet.xcodeproj/project.pbxproj +++ b/tw2023_wallet.xcodeproj/project.pbxproj @@ -82,7 +82,6 @@ 8B80569C2B5F7815009A87C8 /* SharingCredentialArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B80569B2B5F7815009A87C8 /* SharingCredentialArgs.swift */; }; 8B80569D2B5F7815009A87C8 /* SharingCredentialArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B80569B2B5F7815009A87C8 /* SharingCredentialArgs.swift */; }; 8B80569F2B60E2FB009A87C8 /* PreferencesDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B80569E2B60E2FB009A87C8 /* PreferencesDataStore.swift */; }; - 8B8056A12B6381BD009A87C8 /* CredentialSharingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B8056A02B6381BD009A87C8 /* CredentialSharingModel.swift */; }; 8B81E29D2B33CC4000ED3B4E /* tw2023_walletApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B81E29C2B33CC4000ED3B4E /* tw2023_walletApp.swift */; }; 8B81E29F2B33CC4000ED3B4E /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B81E29E2B33CC4000ED3B4E /* ContentView.swift */; }; 8B81E2A12B33CC4200ED3B4E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B81E2A02B33CC4200ED3B4E /* Assets.xcassets */; }; @@ -340,7 +339,6 @@ 8B774A532B7B7B5400F5ED55 /* QRReaderViewLauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRReaderViewLauncher.swift; sourceTree = ""; }; 8B80569B2B5F7815009A87C8 /* SharingCredentialArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingCredentialArgs.swift; sourceTree = ""; }; 8B80569E2B60E2FB009A87C8 /* PreferencesDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesDataStore.swift; sourceTree = ""; }; - 8B8056A02B6381BD009A87C8 /* CredentialSharingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialSharingModel.swift; sourceTree = ""; }; 8B81E2992B33CC4000ED3B4E /* tw2023_wallet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = tw2023_wallet.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8B81E29C2B33CC4000ED3B4E /* tw2023_walletApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = tw2023_walletApp.swift; sourceTree = ""; }; 8B81E29E2B33CC4000ED3B4E /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -886,7 +884,6 @@ children = ( 8B81E2F92B35AC0C00ED3B4E /* CredentialListModel.swift */, F657D5CE2B3C469500901A6A /* CredentialDetailModel.swift */, - 8B8056A02B6381BD009A87C8 /* CredentialSharingModel.swift */, ); path = Models; sourceTree = ""; @@ -1652,7 +1649,6 @@ F6CC769C2B57CDB900FA26A8 /* QRCodeGenerator.swift in Sources */, F6CC768D2B567A7B00FA26A8 /* QrCodeCameraDelegate.swift in Sources */, F6BB10352B4FA1D60063DBBF /* RecipientOrgInfo.swift in Sources */, - 8B8056A12B6381BD009A87C8 /* CredentialSharingModel.swift in Sources */, F699A8D52B3FF6B500F60B91 /* CredentialOfferPreviewModel.swift in Sources */, F621E1F52B6C6A610034BF95 /* RecipientDetailArgs.swift in Sources */, A83039C92B4E6E7E004139A7 /* credential_data.proto in Sources */, diff --git a/tw2023_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/tw2023_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8dfd73b..8d23166 100644 --- a/tw2023_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/tw2023_wallet.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -159,8 +159,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/skywinder/web3swift", "state" : { - "revision" : "74c24f4d3d5f1816616e9ebd16741ca5f7a57eb0", - "version" : "3.2.0" + "revision" : "8a026108ae5ff730ac83e9b574c8cf1c14413c94", + "version" : "3.2.2" } }, { diff --git a/tw2023_wallet/Feature/Credentials/Models/CredentialSharingModel.swift b/tw2023_wallet/Feature/Credentials/Models/CredentialSharingModel.swift deleted file mode 100644 index c9c1764..0000000 --- a/tw2023_wallet/Feature/Credentials/Models/CredentialSharingModel.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// CredentialListArgs.swift -// tw2023_wallet -// -// Created by 若葉良介 on 2024/01/26. -// - -import Foundation - -@Observable -class CredentialSharingModel { - var presentationDefinition: PresentationDefinition? = nil - init(presentationDefinition: PresentationDefinition? = nil) { - self.presentationDefinition = presentationDefinition - } - - var type: String? = nil - var data: SubmissionCredential? = nil - var metadata: CredentialIssuerMetadata? = nil - func setSelectedCredential(data: SubmissionCredential, metadata: CredentialIssuerMetadata) { - self.data = data - self.metadata = metadata - } -} diff --git a/tw2023_wallet/Feature/Credentials/ViewModels/CredentialListViewModel.swift b/tw2023_wallet/Feature/Credentials/ViewModels/CredentialListViewModel.swift index c630e97..e1961de 100644 --- a/tw2023_wallet/Feature/Credentials/ViewModels/CredentialListViewModel.swift +++ b/tw2023_wallet/Feature/Credentials/ViewModels/CredentialListViewModel.swift @@ -54,7 +54,8 @@ class CredentialListViewModel { let ret = matchVcToRequirement( sdJwt: credential.payload, presentationDefinition: presentationDefinition) if let (_, disclosures) = ret { - return 0 < disclosures.filter { it in (it.isUserSelectable || it.isSubmit) }.count + return 0 + < disclosures.filter { it in (it.isUserSelectable || it.isSubmit) }.count } return false } diff --git a/tw2023_wallet/Feature/Credentials/Views/CredentialDetail.swift b/tw2023_wallet/Feature/Credentials/Views/CredentialDetail.swift index 0c5c46e..1ec880b 100644 --- a/tw2023_wallet/Feature/Credentials/Views/CredentialDetail.swift +++ b/tw2023_wallet/Feature/Credentials/Views/CredentialDetail.swift @@ -10,7 +10,6 @@ import SwiftUI struct CredentialDetail: View { @Environment(\.presentationMode) var presentationMode @Environment(SharingRequestModel.self) var sharingRequestModel: SharingRequestModel? - // @Environment(CredentialSharingModel.self) var credentialSharingModel: CredentialSharingModel? var credential: Credential var viewModel: CredentialDetailViewModel var deleteAction: (() -> Void)? @@ -145,17 +144,16 @@ struct CredentialDetail: View { ActionButtonBlack( title: "Select This Credential", action: { - let claims = (viewModel.requiredClaims + userSelectableClaims).filter - { it in - it.isSubmit - } + let claims = (viewModel.requiredClaims + userSelectableClaims) + .filter { it in + it.isSubmit + } let submissionCredential = viewModel.createSubmissionCredential( credential: credential, discloseClaims: claims ) - sharingRequestModel?.setSelectedCredential( - data: submissionCredential, - submissionClaims: claims, + sharingRequestModel?.setSelectedCredentials( + data: [submissionCredential], metadata: credential.metaData ) path.removeLast(2) diff --git a/tw2023_wallet/Feature/ShareCredential/Models/SharingRequesModel.swift b/tw2023_wallet/Feature/ShareCredential/Models/SharingRequesModel.swift index 6739ce4..24d5d54 100644 --- a/tw2023_wallet/Feature/ShareCredential/Models/SharingRequesModel.swift +++ b/tw2023_wallet/Feature/ShareCredential/Models/SharingRequesModel.swift @@ -17,15 +17,13 @@ class SharingRequestModel { } var type: String? = nil - var data: SubmissionCredential? = nil + var data: [SubmissionCredential]? = nil var metadata: CredentialIssuerMetadata? = nil - var submissionClaims: [DisclosureWithOptionality]? = nil - func setSelectedCredential( - data: SubmissionCredential, submissionClaims: [DisclosureWithOptionality], + func setSelectedCredentials( + data: [SubmissionCredential], metadata: CredentialIssuerMetadata ) { self.data = data - self.submissionClaims = submissionClaims self.metadata = metadata } } diff --git a/tw2023_wallet/Feature/ShareCredential/Views/SharingRequest.swift b/tw2023_wallet/Feature/ShareCredential/Views/SharingRequest.swift index 0ba32ba..d2a4ae2 100644 --- a/tw2023_wallet/Feature/ShareCredential/Views/SharingRequest.swift +++ b/tw2023_wallet/Feature/ShareCredential/Views/SharingRequest.swift @@ -159,7 +159,7 @@ struct SharingRequest: View { viewModel.selectedCredential { let result = await viewModel.shareVpToken( - credentials: [sharingRequestModel.data!] + credentials: sharingRequestModel.data! ) switch result { case .success(let postResult): @@ -213,11 +213,12 @@ struct SharingRequest: View { if sharingRequestModel.data != nil { viewModel.selectedCredential = true if let submission = sharingRequestModel.data, - let metadata = sharingRequestModel.metadata + let metadata = sharingRequestModel.metadata, + let firstSubmission = submission.first // Workaround until multiple credentials are possible on the UI. { if let credentialSupported = VCIMetadataUtil.findMatchingCredentials( - format: submission.format, - types: submission.types, + format: firstSubmission.format, + types: firstSubmission.types, metadata: metadata ) { if let display = credentialSupported.display { diff --git a/tw2023_wallet/Services/OID/OpenIdProvider.swift b/tw2023_wallet/Services/OID/OpenIdProvider.swift index 107d1cb..dd37362 100644 --- a/tw2023_wallet/Services/OID/OpenIdProvider.swift +++ b/tw2023_wallet/Services/OID/OpenIdProvider.swift @@ -432,12 +432,12 @@ class OpenIdProvider { } let vpTokens = try! credentials.compactMap { - credential -> (String, (String, DescriptorMap, [DisclosedClaim], String?))? in + credential -> (String, PreparedSubmissionData)? in switch credential.format { case "vc+sd-jwt": return ( credential.id, - try createPresentationSubmissionSdJwtVc( + try createVpTokenForSdJwtVc( credential: credential, presentationDefinition: presentationDefinition, clientId: clientId, @@ -448,7 +448,7 @@ class OpenIdProvider { case "jwt_vc_json": return ( credential.id, - try createPresentationSubmissionJwtVc( + try createVpTokenForJwtVc( credential: credential, presentationDefinition: presentationDefinition, clientId: clientId, @@ -463,10 +463,10 @@ class OpenIdProvider { let vpTokenValue: String if vpTokens.count == 1 { - vpTokenValue = vpTokens[0].1.0 + vpTokenValue = vpTokens[0].1.vpToken } else if !vpTokens.isEmpty { - let tokens = vpTokens.map { $0.1.0 } + let tokens = vpTokens.map { $0.1.vpToken } let jsonEncoder = JSONEncoder() if let jsonData = try? jsonEncoder.encode(tokens), let jsonString = String(data: jsonData, encoding: .utf8) @@ -484,7 +484,7 @@ class OpenIdProvider { let presentationSubmission = PresentationSubmission( id: UUID().uuidString, definitionId: presentationDefinition.id, - descriptorMap: vpTokens.map { $0.1.1 } + descriptorMap: vpTokens.map { $0.1.descriptorMap } ) let jsonEncoder = JSONEncoder() @@ -507,20 +507,22 @@ class OpenIdProvider { convert: convertVpTokenResponseResponse, using: session ) - let sharedContents = vpTokens.map { SharedContent(id: $0.0, sharedClaims: $0.1.2) } - let purposes = vpTokens.map { $0.1.3 } + let sharedContents = vpTokens.map { + SharedContent(id: $0.0, sharedClaims: $0.1.disclosedClaims) + } + let purposes = vpTokens.map { $0.1.purpose } return .success((postResult, sharedContents, purposes)) } catch { return .failure(error) } } - func createPresentationSubmissionSdJwtVc( + func createVpTokenForSdJwtVc( credential: SubmissionCredential, presentationDefinition: PresentationDefinition, clientId: String, nonce: String - ) throws -> (String, DescriptorMap, [DisclosedClaim], String?) { + ) throws -> PreparedSubmissionData { // ここに実装を追加します let sdJwt = credential.credential @@ -529,14 +531,7 @@ class OpenIdProvider { let (inputDescriptor, _) = matchVcToRequirement( sdJwt: sdJwt, presentationDefinition: presentationDefinition) else { - // TODO: エラーハンドリングかダミーの戻り値 - return ( - "Dummy", - DescriptorMap( - id: "dummyId", format: "dummyFormat", path: "dummyPath", - pathNested: Path(format: "dummyFormat", path: "dummyPath")), [DisclosedClaim](), - "dummyPurpose" - ) + throw OpenIdProviderIllegalInputException.illegalCredentialInput } let selectedDisclosures = credential.discloseClaims.map { $0.disclosure } print(String(describing: inputDescriptor)) @@ -548,13 +543,7 @@ class OpenIdProvider { let parts = sdJwt.split(separator: "~").map(String.init) guard let issuerSignedJwt = parts.first else { - return ( - "Error", - DescriptorMap( - id: "error", format: "error", path: "error", - pathNested: Path(format: "error", path: "error")), [DisclosedClaim](), - "dummyPurpose" - ) + throw OpenIdProviderIllegalInputException.illegalCredentialInput } let hasNilValue = selectedDisclosures.contains { disclosure in @@ -585,14 +574,16 @@ class OpenIdProvider { id: credential.id, types: credential.types, name: key, value: disclosure.value) } - return (vpToken, dm, disclosedClaims, inputDescriptor.purpose) + return PreparedSubmissionData( + vpToken: vpToken, descriptorMap: dm, disclosedClaims: disclosedClaims, + purpose: inputDescriptor.purpose) } - func createPresentationSubmissionJwtVc( + func createVpTokenForJwtVc( credential: SubmissionCredential, presentationDefinition: PresentationDefinition, clientId: String, nonce: String - ) throws -> (String, DescriptorMap, [DisclosedClaim], String?) { + ) throws -> PreparedSubmissionData { do { let (_, payload, _) = try JWTUtil.decodeJwt(jwt: credential.credential) if let vcDictionary = payload["vc"] as? [String: Any], @@ -613,11 +604,11 @@ class OpenIdProvider { let descriptorMap = JwtVpJsonPresentation.genDescriptorMap( inputDescriptorId: credential.inputDescriptor.id) - return ( - vpToken, - descriptorMap, - disclosedClaims, - nil + return PreparedSubmissionData( + vpToken: vpToken, + descriptorMap: descriptorMap, + disclosedClaims: disclosedClaims, + purpose: nil ) } else { @@ -727,6 +718,13 @@ struct SubmissionCredential: Codable, Equatable { } } +struct PreparedSubmissionData { + let vpToken: String + let descriptorMap: DescriptorMap + let disclosedClaims: [DisclosedClaim] + let purpose: String? +} + struct DisclosedClaim: Codable { let id: String // credential identifier let types: [String] @@ -811,7 +809,8 @@ func matchVcToRequirement(sdJwt: String, presentationDefinition: PresentationDef // 各InputDescriptorをループ for inputDescriptor in presentationDefinition.inputDescriptors { // fieldKeysを取得 - let requiredOrOptionalKeys = filterKeysWithOptionality(from: sourcePayload, using: inputDescriptor) + let requiredOrOptionalKeys = filterKeysWithOptionality( + from: sourcePayload, using: inputDescriptor) let matchingDisclosures = createDisclosureWithOptionality( from: allDisclosures, @@ -831,7 +830,7 @@ private func filterKeysWithOptionality( /* array of (String, Bool) values filtered by `inputDescriptor.constraints.fields.path` A Bool value represents whether the field is required. - + example of input_descriptors "input_descriptors": [ { @@ -870,7 +869,8 @@ private func createDisclosureWithOptionality( disclosure: disclosure, isSubmit: !optionality, isUserSelectable: optionality) } } - return DisclosureWithOptionality(disclosure: disclosure, isSubmit: false, isUserSelectable: false) + return DisclosureWithOptionality( + disclosure: disclosure, isSubmit: false, isUserSelectable: false) } } diff --git a/tw2023_wallet/Services/OID/PresentationExchange.swift b/tw2023_wallet/Services/OID/PresentationExchange.swift index 3cd4fe3..46fe69a 100644 --- a/tw2023_wallet/Services/OID/PresentationExchange.swift +++ b/tw2023_wallet/Services/OID/PresentationExchange.swift @@ -193,7 +193,7 @@ struct PresentationSubmission: Codable { struct DisclosureWithOptionality: Codable { var disclosure: Disclosure - + // If the value of `isUserSelectable` is `true`, the value of `isSubmit` // is a mutable that can be changed by the user (via toggle operation). var isSubmit: Bool diff --git a/tw2023_walletTests/OpenIdProviderTests.swift b/tw2023_walletTests/OpenIdProviderTests.swift index dfa309d..c755dc0 100644 --- a/tw2023_walletTests/OpenIdProviderTests.swift +++ b/tw2023_walletTests/OpenIdProviderTests.swift @@ -324,6 +324,85 @@ final class OpenIdProviderTests: XCTestCase { } """ + let presentationDefinition7 = """ + { + "id": "12345", + "submission_requirements": [ + { + "name": "Citizenship Information", + "rule": "pick", + "count": 2, + "from": "A" + } + ], + "input_descriptors": [ + { + "id": "input1", + "group": [ + "A" + ], + "format": { + "vc+sd-jwt": {} + }, + "constraints": { + "limit_disclosure": "required", + "fields": [ + { + "path": [ + "$.claim1" + ], + "filter": { + "type": "string" + }, + "optional": false + }, + { + "path": [ + "$.claim2" + ], + "filter": { + "type": "string" + }, + "optional": true + } + ] + } + }, + { + "id": "input2", + "group": [ + "A" + ], + "format": { + "vc+sd-jwt": {} + }, + "constraints": { + "fields": [ + { + "path": [ + "$.claim3" + ], + "filter": { + "type": "string" + } + }, + { + "path": [ + "$.claim4" + ], + "filter": { + "type": "string" + }, + "optional": false + } + ] + } + } + ] + } + + """ + func testSelectDisclosureNoSelected() throws { // mock up decodeDisclosureFunction = mockDecodeDisclosure0 @@ -596,22 +675,21 @@ final class OpenIdProviderTests: XCTestCase { let keyBinding = KeyBindingImpl(keyAlias: Constants.Cryptography.KEY_BINDING) idProvider.setKeyBinding(keyBinding: keyBinding) - let presentationSubmission = try idProvider.createPresentationSubmissionSdJwtVc( + let preparedData = try idProvider.createVpTokenForSdJwtVc( credential: credential, presentationDefinition: presentationDefinition, clientId: "https://rp.example.com", nonce: "dummy-nonce" ) - let (vpToken, descriptorMap, disclosedClaims, _) = presentationSubmission - let parts = vpToken.split(separator: "~").map(String.init) + let parts = preparedData.vpToken.split(separator: "~").map(String.init) XCTAssertEqual(parts.count, 3) XCTAssertEqual(parts[0], "issuer-jwt") XCTAssertEqual(parts[1], "claim1-digest") - XCTAssertEqual(descriptorMap.format, "vc+sd-jwt") - XCTAssertEqual(descriptorMap.path, "$") - XCTAssertEqual(disclosedClaims.count, 1) - XCTAssertEqual(disclosedClaims[0].id, "internal-id-1") - XCTAssertEqual(disclosedClaims[0].name, "claim1") + XCTAssertEqual(preparedData.descriptorMap.format, "vc+sd-jwt") + XCTAssertEqual(preparedData.descriptorMap.path, "$") + XCTAssertEqual(preparedData.disclosedClaims.count, 1) + XCTAssertEqual(preparedData.disclosedClaims[0].id, "internal-id-1") + XCTAssertEqual(preparedData.disclosedClaims[0].name, "claim1") } func testCreatePresentationSubmissionSdJwtVcBothSubmit() throws { @@ -645,25 +723,24 @@ final class OpenIdProviderTests: XCTestCase { let keyBinding = KeyBindingImpl(keyAlias: Constants.Cryptography.KEY_BINDING) idProvider.setKeyBinding(keyBinding: keyBinding) - let presentationSubmission = try idProvider.createPresentationSubmissionSdJwtVc( + let preparedData = try idProvider.createVpTokenForSdJwtVc( credential: credential, presentationDefinition: presentationDefinition, clientId: "https://rp.example.com", nonce: "dummy-nonce" ) - let (vpToken, descriptorMap, disclosedClaims, _) = presentationSubmission - let parts = vpToken.split(separator: "~").map(String.init) + let parts = preparedData.vpToken.split(separator: "~").map(String.init) XCTAssertEqual(parts.count, 4) XCTAssertEqual(parts[0], "issuer-jwt") XCTAssertEqual(parts[1], "claim1-digest") XCTAssertEqual(parts[2], "claim2-digest") - XCTAssertEqual(descriptorMap.format, "vc+sd-jwt") - XCTAssertEqual(descriptorMap.path, "$") - XCTAssertEqual(disclosedClaims.count, 2) - XCTAssertEqual(disclosedClaims[0].id, "internal-id-1") - XCTAssertEqual(disclosedClaims[0].name, "claim1") - XCTAssertEqual(disclosedClaims[1].id, "internal-id-1") - XCTAssertEqual(disclosedClaims[1].name, "claim2") + XCTAssertEqual(preparedData.descriptorMap.format, "vc+sd-jwt") + XCTAssertEqual(preparedData.descriptorMap.path, "$") + XCTAssertEqual(preparedData.disclosedClaims.count, 2) + XCTAssertEqual(preparedData.disclosedClaims[0].id, "internal-id-1") + XCTAssertEqual(preparedData.disclosedClaims[0].name, "claim1") + XCTAssertEqual(preparedData.disclosedClaims[1].id, "internal-id-1") + XCTAssertEqual(preparedData.disclosedClaims[1].name, "claim2") } func testCreatePresentationSubmissionJwtVpJson() throws { @@ -699,19 +776,18 @@ final class OpenIdProviderTests: XCTestCase { keyAlias: Constants.Cryptography.KEY_PAIR_ALIAS_FOR_KEY_JWT_VP_JSON) idProvider.setJwtVpJsonGenerator(jwtVpJsonGenerator: jwtVpJsonGenerator) - let presentationSubmission = try idProvider.createPresentationSubmissionJwtVc( + let preparedData = try idProvider.createVpTokenForJwtVc( credential: credential, presentationDefinition: presentationDefinition, clientId: "https://rp.example.com", nonce: "dummy-nonce" ) - let (vpToken, descriptorMap, disclosedClaims, _) = presentationSubmission do { - let decodedJwt = try JWTUtil.decodeJwt(jwt: vpToken) + let decodedJwt = try JWTUtil.decodeJwt(jwt: preparedData.vpToken) let jwk = decodedJwt.0["jwk"] // let payload = decodedJwt.1 let publicKey = try! KeyPairUtil.createPublicKey(jwk: jwk as! [String: String]) - let result = JWTUtil.verifyJwt(jwt: vpToken, publicKey: publicKey) + let result = JWTUtil.verifyJwt(jwt: preparedData.vpToken, publicKey: publicKey) switch result { case .success(let verifiedJwt): let decodedPayload = verifiedJwt.body @@ -746,17 +822,17 @@ final class OpenIdProviderTests: XCTestCase { catch { XCTFail("Error generating JWT: \(error)") } - XCTAssertEqual(descriptorMap.format, "jwt_vp_json") - XCTAssertEqual(descriptorMap.path, "$") - XCTAssertEqual(descriptorMap.pathNested?.format, "jwt_vc_json") - XCTAssertEqual(descriptorMap.pathNested?.path, "$.vp.verifiableCredential[0]") - XCTAssertEqual(disclosedClaims.count, 1) - XCTAssertEqual(disclosedClaims[0].id, "internal-id-1") - XCTAssertEqual(disclosedClaims[0].name, "claim1") - XCTAssertEqual(disclosedClaims[0].value, "foo") + XCTAssertEqual(preparedData.descriptorMap.format, "jwt_vp_json") + XCTAssertEqual(preparedData.descriptorMap.path, "$") + XCTAssertEqual(preparedData.descriptorMap.pathNested?.format, "jwt_vc_json") + XCTAssertEqual(preparedData.descriptorMap.pathNested?.path, "$.vp.verifiableCredential[0]") + XCTAssertEqual(preparedData.disclosedClaims.count, 1) + XCTAssertEqual(preparedData.disclosedClaims[0].id, "internal-id-1") + XCTAssertEqual(preparedData.disclosedClaims[0].name, "claim1") + XCTAssertEqual(preparedData.disclosedClaims[0].value, "foo") } - func testRespondVPResponseDirectPost() throws { + func testDirectPostSingleVpToken() throws { // mock up decodeDisclosureFunction = mockDecodeDisclosure2Records let requestObject = RequestObjectPayloadImpl( @@ -842,6 +918,117 @@ final class OpenIdProviderTests: XCTestCase { } } + func testDirectPostMultipleVpToken() throws { + // mock up + decodeDisclosureFunction = mockDecodeDisclosure2Records + let requestObject = RequestObjectPayloadImpl( + clientId: "https://rp.example.com", + redirectUri: "https://rp.example.com/cb", + nonce: "dummy-nonce", + responseMode: ResponseMode.directPost, + responseUri: "https://rp.example.com/cb" + ) + + let configuration = URLSessionConfiguration.ephemeral + configuration.protocolClasses = [MockURLProtocol.self] + let mockSession = URLSession(configuration: configuration) + + let urlString = "https://rp.example.com/cb" + let testURL = URL(string: urlString)! + let mockData = "dummy response".data(using: .utf8) + let response = HTTPURLResponse( + url: testURL, statusCode: 200, httpVersion: nil, headerFields: nil) + MockURLProtocol.mockResponses[testURL.absoluteString] = (mockData, response) + + let sdJwt1 = "issuer-jwt~dummy-claim1-digest~dummy-claim2-digest~" + let sdJwt2 = "issuer-jwt~dummy-claim3-digest~dummy-claim4-digest~" + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + let presentationDefinition = try decoder.decode( + PresentationDefinition.self, from: presentationDefinition7.data(using: .utf8)!) + + let credential1 = SubmissionCredential( + id: "internal-id-1", format: "vc+sd-jwt", types: [], credential: sdJwt1, + inputDescriptor: presentationDefinition.inputDescriptors[0], + discloseClaims: [ + DisclosureWithOptionality( + disclosure: + Disclosure( + disclosure: "claim1-digest", key: "claim1", value: "claim1-value"), + isSubmit: true, isUserSelectable: false) + ] + ) + + let credential2 = SubmissionCredential( + id: "internal-id-2", format: "vc+sd-jwt", types: [], credential: sdJwt2, + inputDescriptor: presentationDefinition.inputDescriptors[1], + discloseClaims: [ + DisclosureWithOptionality( + disclosure: + Disclosure( + disclosure: "claim3-digest", key: "claim3", value: "claim3-value"), + isSubmit: true, isUserSelectable: false), + DisclosureWithOptionality( + disclosure: + Disclosure( + disclosure: "claim4-digest", key: "claim4", value: "claim4-value"), + isSubmit: true, isUserSelectable: false), + + ] + ) + + let authRequestProcessedData = ProcessedRequestData( + authorizationRequest: AuthorizationRequestPayloadImpl(), + requestObjectJwt: "dummy-jwt", + requestObject: requestObject, + clientMetadata: RPRegistrationMetadataPayload(), + presentationDefinition: presentationDefinition, + requestIsSigned: false + ) + + runAsyncTest { + let idProvider = OpenIdProvider(ProviderOption()) + idProvider.authRequestProcessedData = authRequestProcessedData + + let requestObj = authRequestProcessedData.requestObject + let authRequest = authRequestProcessedData.authorizationRequest + idProvider.clientId = requestObj?.clientId ?? authRequest.clientId + idProvider.responseMode = requestObj?.responseMode ?? authRequest.responseMode + idProvider.nonce = requestObj?.nonce ?? authRequest.nonce + idProvider.presentationDefinition = authRequestProcessedData.presentationDefinition + + try KeyPairUtil.generateSignVerifyKeyPair(alias: Constants.Cryptography.KEY_BINDING) + let keyBinding = KeyBindingImpl(keyAlias: Constants.Cryptography.KEY_BINDING) + idProvider.setKeyBinding(keyBinding: keyBinding) + let result = await idProvider.respondVPResponse( + credentials: [credential1, credential2], using: mockSession) + switch result { + case .success(let data): + let (_, arrayOfSharedContent, _) = data + let sharedContents = arrayOfSharedContent + XCTAssertEqual(sharedContents.count, 2) + XCTAssertEqual(sharedContents[0].id, "internal-id-1") + XCTAssertEqual(sharedContents[0].sharedClaims.count, 1) + XCTAssertEqual(sharedContents[0].sharedClaims[0].name, "claim1") + + XCTAssertEqual(sharedContents[1].id, "internal-id-2") + XCTAssertEqual(sharedContents[1].sharedClaims.count, 2) + XCTAssertEqual(sharedContents[1].sharedClaims[0].name, "claim3") + XCTAssertEqual(sharedContents[1].sharedClaims[1].name, "claim4") + + if let lastRequest = MockURLProtocol.lastRequest { + XCTAssertEqual(lastRequest.httpMethod, "POST") + XCTAssertEqual(lastRequest.url, testURL) + } + else { + XCTFail("No request was made") + } + case .failure(let error): + XCTFail() + } + } + } + func testPerformanceExample() throws { // This is an example of a performance test case. self.measure {