From 4b0559c99416e70d893a14f0893b6476a3834cfc Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 16 Jul 2024 18:21:42 +0200 Subject: [PATCH 1/5] mdoc oid4vci Signed-off-by: Kevin --- .../Storage/DefaultWalletRecordService.cs | 137 ++++- .../Storage/IWalletRecordService.cs | 40 +- .../Storage/Models/RecordTagAttribute.cs | 11 + .../Storage/Records/RecordBase.cs | 2 +- src/WalletFramework.Core/Colors/Color.cs | 49 ++ .../Credentials/CredentialId.cs | 32 + .../Credentials/Errors/CredentialIdError.cs | 5 + .../Cryptography/Abstractions/IKeyStore.cs | 80 +++ .../Cryptography/Models/KeyId.cs | 31 + .../Functional/Enumerable/EnumerableFun.cs | 6 + .../Functional}/Error.cs | 4 +- .../Errors/EnumerableIsEmptyError.cs | 3 + .../Errors/NoItemsSucceededValidationError.cs | 3 + .../Errors/StringIsNullOrWhitespaceError.cs | 3 + .../Functional/OptionFun.cs | 96 +++ .../Functional}/TaskFun.cs | 3 +- .../Functional/Validation.cs | 308 ++++++++++ src/WalletFramework.Core/IsExternalInit.cs | 7 + .../Json/Converters/DictJsonConverter.cs | 28 + .../Json/Converters/OneOfJsonConverter.cs | 29 + .../Json/Converters/OptionJsonConverter.cs | 28 + .../Json/Converters/ValueTypeJsonConverter.cs | 48 ++ .../Json/Errors/InvalidJsonError.cs | 6 + .../Json/Errors/JTokenIsNotAJValueError.cs | 7 + .../Json/Errors/JTokenIsNotAnJArrayError.cs | 6 + .../Json/Errors/JTokenIsNotAnJObjectError.cs | 6 + .../Json/Errors/JValueIsNotAnIntError.cs | 14 + .../Json/Errors/JsonFieldNotFoundError.cs | 5 + .../JsonFieldValueIsNullOrWhitespaceError.cs | 6 + .../Json/Errors/JsonIsNotAMapError.cs | 7 + src/WalletFramework.Core/Json/JsonFun.cs | 174 ++++++ src/WalletFramework.Core/Json/JsonSettings.cs | 11 + .../Localization/Constants.cs | 10 + .../Localization/Errors/LocaleError.cs | 14 + .../Localization/Locale.cs | 93 +++ src/WalletFramework.Core/Uri/UriFun.cs | 6 + .../WalletFramework.Core.csproj | 12 + src/WalletFramework.Functional/OptionFun.cs | 10 - src/WalletFramework.Functional/Validation.cs | 215 ------- .../CborByteString.cs | 6 +- .../CborFun.cs | 9 +- .../Common/Constants.cs | 2 +- .../Common/Errors.cs | 4 +- .../CoseLabel.cs | 8 +- .../CoseSignature.cs | 4 +- .../DeviceSigned.cs | 5 +- .../Digest.cs | 6 +- .../DigestAlgorithm.cs | 8 +- .../DigestId.cs | 4 +- .../DocType.cs | 17 +- .../IsExternalInit.cs | 0 .../IssuerAuth.cs | 19 +- .../IssuerSigned.cs | 14 +- .../IssuerSignedItem.cs | 35 +- .../Mdoc.cs | 30 +- .../MobileSecurityObject.cs | 40 +- .../NameSpace.cs | 11 +- .../NameSpaces.cs | 11 +- .../ProtectedHeaders.cs | 10 +- .../UnprotectedHeaders.cs | 23 +- .../ValidityInfo.cs | 8 +- .../ValueDigests.cs | 10 +- .../WalletFramework.MdocLib.csproj} | 6 +- src/WalletFramework.MdocVc/ClaimDisplay.cs | 20 + src/WalletFramework.MdocVc/ClaimName.cs | 25 + src/WalletFramework.MdocVc/Common/Errors.cs | 6 + .../IsExternalInit.cs | 0 src/WalletFramework.MdocVc/MdocDisplay.cs | 202 +++++++ src/WalletFramework.MdocVc/MdocLogo.cs | 20 + src/WalletFramework.MdocVc/MdocName.cs | 25 + src/WalletFramework.MdocVc/MdocRecord.cs | 123 ++++ .../WalletFramework.MdocVc.csproj} | 5 +- .../Oid4Vci/Abstractions/IMdocStorage.cs | 22 + .../Abstractions/IOid4VciClientService.cs | 50 ++ .../Abstractions/IAuthFlowSessionStorage.cs | 45 ++ .../AuthFlow/Errors/VciSessionIdError.cs | 5 + .../Implementations/AuthFlowSessionStorage.cs | 52 ++ .../Models/AuthorizationCodeParameters.cs | 38 ++ .../AuthFlow/Models/AuthorizationData.cs | 11 + .../AuthFlow/Models/AuthorizationDetails.cs | 58 ++ .../Oid4Vci/AuthFlow/Models/ClientOptions.cs | 54 ++ .../AuthFlow/Models/IssuanceSession.cs | 44 ++ .../Models/PushedAuthorizationRequest.cs | 99 ++++ .../PushedAuthorizationRequestResponse.cs | 16 + .../Oid4Vci/AuthFlow/Models/VciSessionId.cs | 49 ++ .../AuthFlow/Records/AuthFlowSessionRecord.cs | 103 ++++ .../Abstractions/ITokenService.cs | 12 + .../DPop/Abstractions/IDPopHttpClient.cs | 11 + .../DPop/Implementations/DPopHttpClient.cs | 111 ++++ .../Oid4Vci/Authorization/DPop/Models/DPop.cs | 11 + .../Authorization/DPop/Models/DPopConfig.cs | 24 + .../DPop/Models/DPopHttpResponse.cs | 3 + .../Authorization/DPop/Models/DPopNonce.cs | 18 + .../Authorization/DPop/Models/DPopToken.cs | 16 + .../Errors/AuthorizationServerIdError.cs | 5 + .../Implementations/TokenService.cs | 63 ++ .../Models/AuthorizationServerId.cs | 36 ++ .../Models/AuthorizationServerMetadata.cs | 78 +++ .../Models/Mdoc/MdocTokenRequest.cs | 21 + .../Authorization/Models/OAuthToken.cs | 75 +++ .../Authorization/Models/TokenRequest.cs | 88 +++ .../BatchSizeIsNotAPositiveNumberError.cs | 5 + .../Errors/FormatNotSupportedError.cs | 5 + .../Errors/ProofTypeIdNotSupportedError.cs | 5 + .../Models/CredentialConfiguration.cs | 132 +++++ .../Models/CredentialDisplay.cs | 118 ++++ .../Models/CredentialLogo.cs | 65 ++ .../Models/CredentialName.cs | 32 + .../Models/CryptograhicSigningAlgValue.cs | 33 ++ .../Models/CryptographicBindingMethod.cs | 32 + .../CredConfiguration/Models/Format.cs | 35 ++ .../Models/Mdoc/ClaimsMetadata.cs | 88 +++ .../Models/Mdoc/CryptoGraphicCurve.cs | 20 + .../Models/Mdoc/CryptographicSuite.cs | 31 + .../Models/Mdoc/ElementDisplay.cs | 44 ++ .../Models/Mdoc/ElementMetadata.cs | 63 ++ .../Models/Mdoc/ElementName.cs | 31 + .../Models/Mdoc/MdocConfiguration.cs | 146 +++++ .../CredConfiguration/Models/Mdoc/Policy.cs | 85 +++ .../CredConfiguration/Models/ProofTypeId.cs | 37 ++ .../Models/ProofTypeMetadata.cs | 31 + .../Oid4Vci/CredConfiguration/Models/Scope.cs | 32 + .../Models/SdJwt/SdJwtConfiguration.cs | 97 +++ .../SupportedCredentialConfiguration.cs | 31 + .../Abstractions/ICredentialOfferService.cs | 10 + .../CouldNotFetchCredentialOfferError.cs | 7 + .../Errors/CredentialConfigurationIdError.cs | 7 + ...lConfigurationIdIsNullOrWhitespaceError.cs | 6 + .../CredOffer/Errors/CredentialIssuerError.cs | 7 + ...CredentialOfferHasNoQueryParameterError.cs | 6 + .../Errors/CredentialOfferNotFoundError.cs | 6 + .../CredOffer/GrantTypes/AuthorizationCode.cs | 55 ++ .../CredOffer/GrantTypes/PreAuthorizedCode.cs | 155 +++++ .../Implementations/CredentialOfferService.cs | 53 ++ .../Models/CredentialConfigurationId.cs | 47 ++ .../CredOffer/Models/CredentialOffer.cs | 82 +++ .../Models/CredentialOfferMetadata.cs | 5 + .../Oid4Vci/CredOffer/Models/Grants.cs | 55 ++ .../Abstractions/ICredentialRequestService.cs | 21 + .../CredentialRequestService.cs | 137 +++++ .../CredRequest/Models/CredentialRequest.cs | 27 + .../Models/Mdoc/MdocCredentialRequest.cs | 43 ++ .../CredRequest/Models/ProofOfPossession.cs | 23 + .../Models/SdJwt/SdJwtCredentialRequest.cs | 41 ++ .../CredResponse/CredentialResponse.cs | 139 +++++ .../Oid4Vci/CredResponse/Mdoc/EncodedMdoc.cs | 31 + .../CredResponse/SdJwt/EncodedSdJwt.cs | 38 ++ .../CredResponse/SdJwt/Errors/SdJwtError.cs | 8 + .../Oid4Vci/CredResponse/TransactionId.cs | 6 + .../Extensions/Oid4VciHttpClientExtensions.cs | 36 +- .../Oid4Vci/Implementations/MdocFun.cs | 41 ++ .../Oid4Vci/Implementations/MdocStorage.cs | 68 +++ .../Implementations/Oid4VciClientService.cs | 351 +++++++++++ .../Implementations/SdJwtRecordExtensions.cs | 65 ++ .../Abstractions/IIssuerMetadataService.cs | 11 + .../Issuer/Errors/CredentialEndpointError.cs | 5 + .../Issuer/Errors/CredentialIssuerIdError.cs | 5 + .../Implementations/IssuerMetadataService.cs | 42 ++ .../Issuer/Models/CredentialIssuerId.cs | 36 ++ .../Oid4Vci/Issuer/Models/IssuerMetadata.cs | 202 +++++++ .../Oid4Vci/Issuer/Models/IssuerName.cs | 26 + .../AuthorizationCodeParameters.cs | 39 -- .../Models/Authorization/AuthorizationData.cs | 27 - .../Authorization/AuthorizationDetails.cs | 41 -- .../AuthorizationServerMetadata.cs | 79 --- .../Models/Authorization/ClientOptions.cs | 55 -- .../PushedAuthorizationRequest.cs | 100 ---- .../PushedAuthorizationRequestResponse.cs | 17 - .../Models/Authorization/TokenRequest.cs | 91 --- .../Models/Authorization/TokenResponse.cs | 74 --- .../VciAuthorizationSessionRecord.cs | 64 -- .../Models/Authorization/VciSessionId.cs | 32 - .../GrantTypes/AuthorizationCode.cs | 23 - .../GrantTypes/PreAuthorizedCode.cs | 47 -- .../Oid4Vci/Models/CredentialOffer/Grants.cs | 26 - .../CredentialOffer/OidCredentialOffer.cs | 33 -- .../CredentialRequest/OidCredentialRequest.cs | 37 -- .../CredentialRequest/OidProofOfPossession.cs | 23 - .../OidCredentialResponse.cs | 39 -- .../Oid4Vci/Models/DPop/OAuthToken.cs | 33 -- .../Models/IssuanceSessionParameters.cs | 44 -- .../Credential/OidCredentialDisplay.cs | 40 -- .../Metadata/Credential/OidCredentialLogo.cs | 22 - .../Credential/OidCredentialMetadata.cs | 85 --- .../Models/Metadata/Issuer/IssuerDisplay.cs | 62 ++ .../Models/Metadata/Issuer/IssuerLogo.cs | 67 +++ .../Metadata/Issuer/OidIssuerDisplay.cs | 28 - .../Models/Metadata/Issuer/OidIssuerLogo.cs | 22 - .../Metadata/Issuer/OidIssuerMetadata.cs | 130 ---- .../Models/Metadata/IssuerMetadataSet.cs | 32 + .../Oid4Vci/Models/Metadata/MetadataSet.cs | 29 - .../Oid4Vci/Services/ISessionRecordService.cs | 41 -- .../IOid4VciClientService.cs | 65 -- .../Oid4VciClientService.cs | 559 ------------------ .../Oid4Vci/Services/SessionRecordService.cs | 67 --- .../Services/PexService.cs | 2 +- .../Oid4Vp/Services/IOid4VpHaipClient.cs | 43 +- .../Oid4Vp/Services/Oid4VpClientService.cs | 311 +++++----- .../SeviceCollectionExtensions.cs | 34 +- .../WalletFramework.Oid4Vc.csproj | 7 +- .../KeyStore/Services/IKeyStore.cs | 69 --- .../Credential/CredentialDisplayMetadata.cs | 58 -- .../Models/Credential/SdJwtDisplay.cs | 57 ++ ...CredentialMetadata.cs => SdJwtMetadata.cs} | 4 +- .../Models/Issuer/IssuerDisplay.cs | 46 -- .../Models/Issuer/IssuerMetadata.cs | 113 ---- .../Models/Records/SdJwtRecord.cs | 391 ++++++------ src/WalletFramework.SdJwtVc/Models/Vct.cs | 30 + .../ServiceCollectionExtensions.cs | 12 +- .../DefaultSdJwtVcHolderService.cs | 140 ----- .../ISdJwtVcHolderService.cs | 135 ++--- .../SdJwtVcHolderService.cs | 113 ++++ .../WalletFramework.SdJwtVc.csproj | 2 +- src/WalletFramework.sln | 36 +- .../Routing/RoutingInboxHandlerTests.cs | 8 +- test/WalletFramework.Mdoc.Tests/Samples.cs | 68 --- .../Helpers.cs | 2 +- .../MdocTests.cs | 59 +- test/WalletFramework.MdocLib.Tests/Samples.cs | 71 +++ .../WalletFramework.MdocLib.Tests.csproj} | 5 +- .../MdocRecordTests.cs | 36 ++ .../MdocVcSamples.cs | 12 + .../WalletFramework.MdocVc.Tests.csproj | 25 + .../Extensions/ObjectExtensions.cs | 19 +- .../AuthFlow/AuthFlowSessionRecordTests.cs | 78 +++ .../AuthFlow/Samples/AuthFlowSamples.cs | 38 ++ .../MdocConfigurationTests.cs | 78 +++ .../SdJwtConfigurationTests.cs | 10 + .../Oid4Vci/CredOffer/CredentialOfferTests.cs | 81 +++ .../Oid4Vci/Issuer/IssuerMetadataTests.cs | 84 +++ .../Oid4Vci/IssuerMetadataTests.cs | 108 ++-- .../Oid4Vci/Localization/LocaleTests.cs | 75 +++ .../Localization/Samples/LocaleSample.cs | 15 + .../Oid4Vci/Samples/CredentialOfferSample.cs | 43 ++ .../Oid4Vci/Samples/IssuerMetadataSample.cs | 60 ++ .../Samples/Mdoc/MdocConfigurationSample.cs | 99 ++++ .../Samples/SdJwt/SdJwtConfigurationSample.cs | 95 +++ .../Services/Oid4VciClientServiceTests.cs | 216 ------- .../Services/Oid4VpClientServiceTests.cs | 293 +++++---- .../Services/PexServiceTests.cs | 35 +- test/WalletFramework.Oid4Vc.Tests/Samples.cs | 166 ------ .../WalletFramework.Oid4Vc.Tests.csproj | 7 +- .../SdJwtRecordTests.cs | 12 +- .../SdJwtVcHolderServiceTests.cs | 10 +- .../WalletFramework.SdJwtVc.Tests.csproj | 2 +- 245 files changed, 8212 insertions(+), 4064 deletions(-) create mode 100644 src/Hyperledger.Aries/Storage/Models/RecordTagAttribute.cs create mode 100644 src/WalletFramework.Core/Colors/Color.cs create mode 100644 src/WalletFramework.Core/Credentials/CredentialId.cs create mode 100644 src/WalletFramework.Core/Credentials/Errors/CredentialIdError.cs create mode 100644 src/WalletFramework.Core/Cryptography/Abstractions/IKeyStore.cs create mode 100644 src/WalletFramework.Core/Cryptography/Models/KeyId.cs create mode 100644 src/WalletFramework.Core/Functional/Enumerable/EnumerableFun.cs rename src/{WalletFramework.Functional => WalletFramework.Core/Functional}/Error.cs (89%) create mode 100644 src/WalletFramework.Core/Functional/Errors/EnumerableIsEmptyError.cs create mode 100644 src/WalletFramework.Core/Functional/Errors/NoItemsSucceededValidationError.cs create mode 100644 src/WalletFramework.Core/Functional/Errors/StringIsNullOrWhitespaceError.cs create mode 100644 src/WalletFramework.Core/Functional/OptionFun.cs rename src/{WalletFramework.Functional => WalletFramework.Core/Functional}/TaskFun.cs (96%) create mode 100644 src/WalletFramework.Core/Functional/Validation.cs create mode 100644 src/WalletFramework.Core/IsExternalInit.cs create mode 100644 src/WalletFramework.Core/Json/Converters/DictJsonConverter.cs create mode 100644 src/WalletFramework.Core/Json/Converters/OneOfJsonConverter.cs create mode 100644 src/WalletFramework.Core/Json/Converters/OptionJsonConverter.cs create mode 100644 src/WalletFramework.Core/Json/Converters/ValueTypeJsonConverter.cs create mode 100644 src/WalletFramework.Core/Json/Errors/InvalidJsonError.cs create mode 100644 src/WalletFramework.Core/Json/Errors/JTokenIsNotAJValueError.cs create mode 100644 src/WalletFramework.Core/Json/Errors/JTokenIsNotAnJArrayError.cs create mode 100644 src/WalletFramework.Core/Json/Errors/JTokenIsNotAnJObjectError.cs create mode 100644 src/WalletFramework.Core/Json/Errors/JValueIsNotAnIntError.cs create mode 100644 src/WalletFramework.Core/Json/Errors/JsonFieldNotFoundError.cs create mode 100644 src/WalletFramework.Core/Json/Errors/JsonFieldValueIsNullOrWhitespaceError.cs create mode 100644 src/WalletFramework.Core/Json/Errors/JsonIsNotAMapError.cs create mode 100644 src/WalletFramework.Core/Json/JsonFun.cs create mode 100644 src/WalletFramework.Core/Json/JsonSettings.cs create mode 100644 src/WalletFramework.Core/Localization/Constants.cs create mode 100644 src/WalletFramework.Core/Localization/Errors/LocaleError.cs create mode 100644 src/WalletFramework.Core/Localization/Locale.cs create mode 100644 src/WalletFramework.Core/Uri/UriFun.cs create mode 100644 src/WalletFramework.Core/WalletFramework.Core.csproj delete mode 100644 src/WalletFramework.Functional/OptionFun.cs delete mode 100644 src/WalletFramework.Functional/Validation.cs rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/CborByteString.cs (89%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/CborFun.cs (91%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/Common/Constants.cs (90%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/Common/Errors.cs (96%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/CoseLabel.cs (86%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/CoseSignature.cs (83%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/DeviceSigned.cs (83%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/Digest.cs (81%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/DigestAlgorithm.cs (91%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/DigestId.cs (90%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/DocType.cs (68%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/IsExternalInit.cs (100%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/IssuerAuth.cs (78%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/IssuerSigned.cs (74%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/IssuerSignedItem.cs (87%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/Mdoc.cs (90%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/MobileSecurityObject.cs (66%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/NameSpace.cs (75%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/NameSpaces.cs (85%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/ProtectedHeaders.cs (93%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/UnprotectedHeaders.cs (85%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/ValidityInfo.cs (93%) rename src/{WalletFramework.Mdoc => WalletFramework.MdocLib}/ValueDigests.cs (76%) rename src/{WalletFramework.Mdoc/WalletFramework.Mdoc.csproj => WalletFramework.MdocLib/WalletFramework.MdocLib.csproj} (71%) create mode 100644 src/WalletFramework.MdocVc/ClaimDisplay.cs create mode 100644 src/WalletFramework.MdocVc/ClaimName.cs create mode 100644 src/WalletFramework.MdocVc/Common/Errors.cs rename src/{WalletFramework.Functional => WalletFramework.MdocVc}/IsExternalInit.cs (100%) create mode 100644 src/WalletFramework.MdocVc/MdocDisplay.cs create mode 100644 src/WalletFramework.MdocVc/MdocLogo.cs create mode 100644 src/WalletFramework.MdocVc/MdocName.cs create mode 100644 src/WalletFramework.MdocVc/MdocRecord.cs rename src/{WalletFramework.Functional/WalletFramework.Functional.csproj => WalletFramework.MdocVc/WalletFramework.MdocVc.csproj} (59%) create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Abstractions/IMdocStorage.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Abstractions/IOid4VciClientService.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Abstractions/IAuthFlowSessionStorage.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Errors/VciSessionIdError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Implementations/AuthFlowSessionStorage.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationCodeParameters.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationData.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationDetails.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/ClientOptions.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/IssuanceSession.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/PushedAuthorizationRequest.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/PushedAuthorizationRequestResponse.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/VciSessionId.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Records/AuthFlowSessionRecord.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Abstractions/ITokenService.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Abstractions/IDPopHttpClient.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Implementations/DPopHttpClient.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPop.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopConfig.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopHttpResponse.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopNonce.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopToken.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Errors/AuthorizationServerIdError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Implementations/TokenService.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/AuthorizationServerId.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/AuthorizationServerMetadata.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/Mdoc/MdocTokenRequest.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/OAuthToken.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/TokenRequest.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Errors/BatchSizeIsNotAPositiveNumberError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Errors/FormatNotSupportedError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Errors/ProofTypeIdNotSupportedError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialConfiguration.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialDisplay.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialLogo.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialName.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptograhicSigningAlgValue.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptographicBindingMethod.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Format.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ClaimsMetadata.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptoGraphicCurve.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptographicSuite.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementDisplay.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementMetadata.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementName.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/MdocConfiguration.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/Policy.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeId.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeMetadata.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Scope.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SdJwt/SdJwtConfiguration.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SupportedCredentialConfiguration.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Abstractions/ICredentialOfferService.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CouldNotFetchCredentialOfferError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialConfigurationIdError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialConfigurationIdIsNullOrWhitespaceError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialIssuerError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialOfferHasNoQueryParameterError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialOfferNotFoundError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/GrantTypes/AuthorizationCode.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/GrantTypes/PreAuthorizedCode.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Implementations/CredentialOfferService.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialConfigurationId.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialOffer.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialOfferMetadata.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/Grants.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Abstractions/ICredentialRequestService.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Implementations/CredentialRequestService.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/Mdoc/MdocCredentialRequest.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/ProofOfPossession.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/SdJwt/SdJwtCredentialRequest.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/CredentialResponse.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/Mdoc/EncodedMdoc.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/SdJwt/EncodedSdJwt.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/SdJwt/Errors/SdJwtError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/TransactionId.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/MdocFun.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/MdocStorage.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/Oid4VciClientService.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/SdJwtRecordExtensions.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Abstractions/IIssuerMetadataService.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Errors/CredentialEndpointError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Errors/CredentialIssuerIdError.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Implementations/IssuerMetadataService.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/CredentialIssuerId.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerMetadata.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerName.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationCodeParameters.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationData.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationDetails.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationServerMetadata.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/ClientOptions.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/PushedAuthorizationRequest.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/PushedAuthorizationRequestResponse.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/TokenRequest.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/TokenResponse.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/VciAuthorizationSessionRecord.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/VciSessionId.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/GrantTypes/AuthorizationCode.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/GrantTypes/PreAuthorizedCode.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/Grants.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/OidCredentialOffer.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialRequest/OidCredentialRequest.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialRequest/OidProofOfPossession.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialResponse/OidCredentialResponse.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/DPop/OAuthToken.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/IssuanceSessionParameters.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialDisplay.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialLogo.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialMetadata.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerDisplay.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerLogo.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/OidIssuerDisplay.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/OidIssuerLogo.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/OidIssuerMetadata.cs create mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/IssuerMetadataSet.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/MetadataSet.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Services/ISessionRecordService.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Services/Oid4VciClientService/IOid4VciClientService.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Services/Oid4VciClientService/Oid4VciClientService.cs delete mode 100644 src/WalletFramework.Oid4Vc/Oid4Vci/Services/SessionRecordService.cs delete mode 100644 src/WalletFramework.SdJwtVc/KeyStore/Services/IKeyStore.cs delete mode 100644 src/WalletFramework.SdJwtVc/Models/Credential/CredentialDisplayMetadata.cs create mode 100644 src/WalletFramework.SdJwtVc/Models/Credential/SdJwtDisplay.cs rename src/WalletFramework.SdJwtVc/Models/Credential/{CredentialMetadata.cs => SdJwtMetadata.cs} (97%) delete mode 100644 src/WalletFramework.SdJwtVc/Models/Issuer/IssuerDisplay.cs delete mode 100644 src/WalletFramework.SdJwtVc/Models/Issuer/IssuerMetadata.cs create mode 100644 src/WalletFramework.SdJwtVc/Models/Vct.cs delete mode 100644 src/WalletFramework.SdJwtVc/Services/SdJwtVcHolderService/DefaultSdJwtVcHolderService.cs create mode 100644 src/WalletFramework.SdJwtVc/Services/SdJwtVcHolderService/SdJwtVcHolderService.cs delete mode 100644 test/WalletFramework.Mdoc.Tests/Samples.cs rename test/{WalletFramework.Mdoc.Tests => WalletFramework.MdocLib.Tests}/Helpers.cs (87%) rename test/{WalletFramework.Mdoc.Tests => WalletFramework.MdocLib.Tests}/MdocTests.cs (80%) create mode 100644 test/WalletFramework.MdocLib.Tests/Samples.cs rename test/{WalletFramework.Mdoc.Tests/WalletFramework.Mdoc.Tests.csproj => WalletFramework.MdocLib.Tests/WalletFramework.MdocLib.Tests.csproj} (84%) create mode 100644 test/WalletFramework.MdocVc.Tests/MdocRecordTests.cs create mode 100644 test/WalletFramework.MdocVc.Tests/MdocVcSamples.cs create mode 100644 test/WalletFramework.MdocVc.Tests/WalletFramework.MdocVc.Tests.csproj create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/MdocConfigurationTests.cs create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwtConfigurationTests.cs create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredOffer/CredentialOfferTests.cs create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Localization/LocaleTests.cs create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Localization/Samples/LocaleSample.cs create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/CredentialOfferSample.cs create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/IssuerMetadataSample.cs create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/Mdoc/MdocConfigurationSample.cs create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/SdJwt/SdJwtConfigurationSample.cs delete mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Services/Oid4VciClientServiceTests.cs delete mode 100644 test/WalletFramework.Oid4Vc.Tests/Samples.cs diff --git a/src/Hyperledger.Aries/Storage/DefaultWalletRecordService.cs b/src/Hyperledger.Aries/Storage/DefaultWalletRecordService.cs index a6c5918c..f9b3af8e 100644 --- a/src/Hyperledger.Aries/Storage/DefaultWalletRecordService.cs +++ b/src/Hyperledger.Aries/Storage/DefaultWalletRecordService.cs @@ -6,9 +6,11 @@ using Hyperledger.Aries.Agents; using Hyperledger.Aries.Extensions; using Hyperledger.Aries.Features.PresentProof; +using Hyperledger.Aries.Storage.Models; using Hyperledger.Indy.NonSecretsApi; using Hyperledger.Indy.WalletApi; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Hyperledger.Aries.Storage { @@ -32,47 +34,80 @@ public DefaultWalletRecordService() } /// - public virtual Task AddAsync(Wallet wallet, T record) - where T : RecordBase, new() + public virtual Task AddAsync(Wallet wallet, T record, Func? encode = null) where T : RecordBase, new() { record.CreatedAtUtc = DateTime.UtcNow; + + var properties = record + .GetType() + .GetProperties() + .Where(info => Attribute.IsDefined(info, typeof(RecordTagAttribute))); + + foreach (var property in properties) + { + var value = property.GetValue(record); + record.SetTag(property.Name, value.ToString(), false); + } + + var recordJson = encode is null + ? record.ToJson(_jsonSettings) + : encode(record).ToString(); return NonSecrets.AddRecordAsync(wallet, record.TypeName, record.Id, - record.ToJson(_jsonSettings), + recordJson, record.Tags.ToJson()); } /// - public virtual async Task> SearchAsync(Wallet wallet, ISearchQuery query, SearchOptions options, int count, int skip) - where T : RecordBase, new() + public virtual async Task> SearchAsync( + Wallet wallet, + ISearchQuery? query = null, + SearchOptions? options = null, + int count = 10, + int skip = 0, + Func? decode = null) where T : RecordBase, new() { using var search = await NonSecrets.OpenSearchAsync( wallet, new T().TypeName, (query ?? SearchQuery.Empty).ToJson(), (options ?? new SearchOptions()).ToJson() - ); + ); if(skip > 0) { await search.NextAsync(wallet, skip); } - var result = JsonConvert.DeserializeObject(await search.NextAsync(wallet, count), _jsonSettings); - - return result.Records? - .Select(x => - { - var record = JsonConvert.DeserializeObject(x.Value, _jsonSettings); - - foreach (var tag in x.Tags) - record.Tags[tag.Key] = tag.Value; - - return record; - }) - .ToList() - ?? new List(); + var searchResultStr = await search.NextAsync(wallet, count); + var searchResult = JsonConvert.DeserializeObject(searchResultStr, _jsonSettings); + + if (searchResult?.Records is null) + { + return new List(); + } + + var records = searchResult.Records.Select(searchItem => + { + T record; + if (decode is null) + { + record = JsonConvert.DeserializeObject(searchItem.Value, _jsonSettings)!; + } + else + { + var json = JObject.Parse(searchItem.Value); + record = decode(json); + } + + foreach (var tag in searchItem.Tags) + record.Tags[tag.Key] = tag.Value; + + return record; + }); + + return records.ToList(); } /// @@ -91,8 +126,27 @@ await NonSecrets.UpdateRecordTagsAsync(wallet, record.Tags.ToJson(_jsonSettings)); } + public async Task Update(Wallet wallet, T record, Func? encode = null) where T : RecordBase + { + record.UpdatedAtUtc = DateTime.UtcNow; + + var recordJson = encode is null + ? record.ToJson(_jsonSettings) + : encode(record).ToString(); + + await NonSecrets.UpdateRecordValueAsync(wallet, + record.TypeName, + record.Id, + recordJson); + + await NonSecrets.UpdateRecordTagsAsync(wallet, + record.TypeName, + record.Id, + record.Tags.ToJson(_jsonSettings)); + } + /// - public virtual async Task GetAsync(Wallet wallet, string id) where T : RecordBase, new() + public async Task GetAsync(Wallet wallet, string id, Func? decode = null) where T : RecordBase, new() { try { @@ -106,9 +160,18 @@ await NonSecrets.UpdateRecordTagsAsync(wallet, return null; } - var item = JsonConvert.DeserializeObject(searchItemJson, _jsonSettings); + var item = JsonConvert.DeserializeObject(searchItemJson, _jsonSettings)!; - var record = JsonConvert.DeserializeObject(item.Value, _jsonSettings); + T record; + if (decode is null) + { + record = JsonConvert.DeserializeObject(item.Value, _jsonSettings)!; + } + else + { + var json = JObject.Parse(item.Value); + record = decode(json); + } foreach (var tag in item.Tags) record.Tags[tag.Key] = tag.Value; @@ -127,7 +190,7 @@ await NonSecrets.UpdateRecordTagsAsync(wallet, try { var record = await GetAsync(wallet, id); - var typeName = new T().TypeName; + var typeName = record.TypeName; await NonSecrets.DeleteRecordTagsAsync( wallet: wallet, @@ -147,5 +210,31 @@ await NonSecrets.DeleteRecordAsync( return false; } } + + /// + public async Task Delete(Wallet wallet, RecordBase record) + { + try + { + var typeName = record.TypeName; + + await NonSecrets.DeleteRecordTagsAsync( + wallet: wallet, + type: typeName, + id: record.Id, + tagsJson: record.Tags.Select(x => x.Key).ToArray().ToJson()); + await NonSecrets.DeleteRecordAsync( + wallet: wallet, + type: typeName, + id: record.Id); + + return true; + } + catch (Exception e) + { + Debug.WriteLine($"Couldn't delete record: {e}"); + return false; + } + } } } diff --git a/src/Hyperledger.Aries/Storage/IWalletRecordService.cs b/src/Hyperledger.Aries/Storage/IWalletRecordService.cs index dd9d6d5d..3adde782 100644 --- a/src/Hyperledger.Aries/Storage/IWalletRecordService.cs +++ b/src/Hyperledger.Aries/Storage/IWalletRecordService.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using Hyperledger.Indy.WalletApi; +using Newtonsoft.Json.Linq; namespace Hyperledger.Aries.Storage { @@ -15,11 +17,12 @@ public interface IWalletRecordService /// The record async. /// Wallet. /// Record. + /// The func for encoding the record to JSON format /// The 1st type parameter. - Task AddAsync(Wallet wallet, T record) where T : RecordBase, new(); - + Task AddAsync(Wallet wallet, T record, Func? encode = null) where T : RecordBase, new(); + /// - /// Searchs the records async. + /// Searches the records async. /// /// The records async. /// Wallet. @@ -27,8 +30,15 @@ public interface IWalletRecordService /// Options. /// The number of items to return /// The number of items to skip + /// Func for decoding the JSON to the record /// The 1st type parameter. - Task> SearchAsync(Wallet wallet, ISearchQuery query = null, SearchOptions options = null, int count = 10, int skip = 0) where T : RecordBase, new(); + Task> SearchAsync( + Wallet wallet, + ISearchQuery? query = null, + SearchOptions? options = null, + int count = 10, + int skip = 0, + Func? decode = null) where T : RecordBase, new(); /// /// Updates the record async. @@ -37,6 +47,15 @@ public interface IWalletRecordService /// Wallet. /// Credential record. Task UpdateAsync(Wallet wallet, RecordBase record); + + /// + /// Updates the record async. + /// + /// The record async. + /// Wallet. + /// Credential record. + /// The func for encoding the record to JSON format + Task Update(Wallet wallet, T record, Func? encode = null) where T : RecordBase; /// /// Gets the record async. @@ -44,8 +63,9 @@ public interface IWalletRecordService /// The record async. /// Wallet. /// Identifier. + /// Func for decoding the JSON to the record /// The 1st type parameter. - Task GetAsync(Wallet wallet, string id) where T : RecordBase, new(); + Task GetAsync(Wallet wallet, string id, Func? decode = null) where T : RecordBase, new(); /// /// Deletes the record async. @@ -55,5 +75,13 @@ public interface IWalletRecordService /// Record Identifier. /// Boolean status indicating if the removal succeed Task DeleteAsync(Wallet wallet, string id) where T : RecordBase, new(); + + /// + /// Deletes the record async. + /// + /// Wallet. + /// + /// Boolean status indicating if the removal succeed + Task Delete(Wallet wallet, RecordBase record); } } diff --git a/src/Hyperledger.Aries/Storage/Models/RecordTagAttribute.cs b/src/Hyperledger.Aries/Storage/Models/RecordTagAttribute.cs new file mode 100644 index 00000000..b4891474 --- /dev/null +++ b/src/Hyperledger.Aries/Storage/Models/RecordTagAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace Hyperledger.Aries.Storage.Models +{ + /// + /// Defines an attribute to be also saved as a tag in the record + /// + public class RecordTagAttribute : Attribute + { + } +} diff --git a/src/Hyperledger.Aries/Storage/Records/RecordBase.cs b/src/Hyperledger.Aries/Storage/Records/RecordBase.cs index b8b718f7..a4ca5550 100644 --- a/src/Hyperledger.Aries/Storage/Records/RecordBase.cs +++ b/src/Hyperledger.Aries/Storage/Records/RecordBase.cs @@ -34,7 +34,7 @@ public DateTime? UpdatedAtUtc /// Gets or sets the tags. /// The tags. [JsonIgnore] - protected internal Dictionary Tags { get; set; } = new(); + public Dictionary Tags { get; set; } = new(); /// /// Get and set the schema version of a wallet record diff --git a/src/WalletFramework.Core/Colors/Color.cs b/src/WalletFramework.Core/Colors/Color.cs new file mode 100644 index 00000000..6d644a6c --- /dev/null +++ b/src/WalletFramework.Core/Colors/Color.cs @@ -0,0 +1,49 @@ +using System.Drawing; +using LanguageExt; +using Newtonsoft.Json; +using WalletFramework.Core.Json.Converters; + +namespace WalletFramework.Core.Colors; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct Color +{ + private System.Drawing.Color Value { get; } + + private Color(System.Drawing.Color value) + { + Value = value; + } + + public override string ToString() => Value.ToHex(); + + public System.Drawing.Color ToSystemColor() => Value; + + public static implicit operator System.Drawing.Color(Color color) => color.Value; + + public static implicit operator Color(System.Drawing.Color systemColor) => new(systemColor); + + public static implicit operator string(Color color) => color.ToString(); + + public static Option OptionColor(string hexStr) + { + try + { + var colorConverter = new ColorConverter(); + var systemColor = (System.Drawing.Color)colorConverter.ConvertFromString(hexStr); + return systemColor.ToFrameworkColor(); + } + catch (Exception) + { + return Option.None; + } + } +} + +public static class ColorFun +{ + public static string ToHex(this System.Drawing.Color systemColor) => + $"#{systemColor.R:X2}{systemColor.G:X2}{systemColor.B:X2}"; + + public static Color ToFrameworkColor(this System.Drawing.Color systemColor) => systemColor; +} diff --git a/src/WalletFramework.Core/Credentials/CredentialId.cs b/src/WalletFramework.Core/Credentials/CredentialId.cs new file mode 100644 index 00000000..732ed46b --- /dev/null +++ b/src/WalletFramework.Core/Credentials/CredentialId.cs @@ -0,0 +1,32 @@ +using WalletFramework.Core.Credentials.Errors; +using WalletFramework.Core.Functional; + +namespace WalletFramework.Core.Credentials; + +public readonly struct CredentialId +{ + private string Value { get; } + + private CredentialId(string value) + { + Value = value; + } + + public override string ToString() => Value; + + public static implicit operator string(CredentialId credentialId) => credentialId.Value; + + public static CredentialId CreateCredentialId() + { + var id = Guid.NewGuid().ToString(); + return new CredentialId(id); + } + + public static Validation ValidCredentialId(string id) + { + var isValid = Guid.TryParse(id, out _); + return isValid + ? new CredentialId(id) + : new CredentialIdError(id); + } +} diff --git a/src/WalletFramework.Core/Credentials/Errors/CredentialIdError.cs b/src/WalletFramework.Core/Credentials/Errors/CredentialIdError.cs new file mode 100644 index 00000000..6529716d --- /dev/null +++ b/src/WalletFramework.Core/Credentials/Errors/CredentialIdError.cs @@ -0,0 +1,5 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Core.Credentials.Errors; + +public record CredentialIdError(string Value) : Error($"The CredentialId is not a valid GUID, value is: {Value}"); diff --git a/src/WalletFramework.Core/Cryptography/Abstractions/IKeyStore.cs b/src/WalletFramework.Core/Cryptography/Abstractions/IKeyStore.cs new file mode 100644 index 00000000..7459d6d2 --- /dev/null +++ b/src/WalletFramework.Core/Cryptography/Abstractions/IKeyStore.cs @@ -0,0 +1,80 @@ +using WalletFramework.Core.Cryptography.Models; + +namespace WalletFramework.Core.Cryptography.Abstractions; + +/// +/// Represents a store for managing keys. +/// This interface is intended to be implemented outside of the framework on the device side, +/// allowing flexibility in key generation or retrieval mechanisms. +/// +public interface IKeyStore +{ + /// + /// Asynchronously generates a key for the specified algorithm and returns the key identifier. + /// + /// The algorithm for key generation (default is "ES256"). + /// A representing the generated key's identifier as a string. + Task GenerateKey(string alg = "ES256"); + + /// + /// Asynchronously creates a proof of possession for a specific key, based on the provided audience and nonce. + /// + /// The identifier of the key to be used in creating the proof of possession. + /// The intended recipient of the proof. Typically represents the entity that will verify it. + /// + /// A unique token, typically used to prevent replay attacks by ensuring that the proof is only used once. + /// + /// The type of the proof. (For example "openid4vci-proof+jwt") + /// Base64url-encoded hash digest over the Issuer-signed JWT and the selected Disclosures for integrity protection + /// + /// A representing the asynchronous operation. When evaluated, the task's result contains + /// the proof. + /// + Task GenerateKbProofOfPossessionAsync( + KeyId keyId, + string audience, + string nonce, + string type, + string? sdHash = null, + string? clientId = null); + + /// + /// Asynchronously creates a DPoP Proof JWT for a specific key, based on the provided audience, nonce and access token. + /// + /// The identifier of the key to be used in creating the proof of possession. + /// The intended recipient of the proof. Typically represents the entity that will verify it. + /// A unique token, typically used to prevent replay attacks by ensuring that the proof is only used once. + /// The access token, that the DPoP Proof JWT is bound to + /// + /// A representing the asynchronous operation. When evaluated, the task's result contains + /// the DPoP Proof JWT. + /// + Task GenerateDPopProofOfPossessionAsync( + KeyId keyId, + string audience, + string? nonce, + string? accessToken); + + /// + /// Asynchronously loads a key by its identifier and returns it as a JSON Web Key (JWK) containing the public key + /// information. + /// + /// The identifier of the key to load. + /// A representing the loaded key as a JWK string. + Task LoadKey(KeyId keyId); + + /// + /// Asynchronously signs the given payload using the key identified by the provided key ID. + /// + /// The identifier of the key to use for signing. + /// The payload to sign. + /// A representing the signed payload as a byte array. + Task Sign(KeyId keyId, byte[] payload); + + /// + /// Asynchronously deletes the key associated with the provided key ID. + /// + /// The identifier of the key that should be deleted + /// A representing the asynchronous operation. + Task DeleteKey(KeyId keyId); +} diff --git a/src/WalletFramework.Core/Cryptography/Models/KeyId.cs b/src/WalletFramework.Core/Cryptography/Models/KeyId.cs new file mode 100644 index 00000000..8b56acb2 --- /dev/null +++ b/src/WalletFramework.Core/Cryptography/Models/KeyId.cs @@ -0,0 +1,31 @@ +using WalletFramework.Core.Functional; +using WalletFramework.Core.Functional.Errors; + +namespace WalletFramework.Core.Cryptography.Models; + +public readonly struct KeyId +{ + private string Value { get; } + + private KeyId(string value) => Value = value; + + public override string ToString() => Value; + + public static implicit operator string(KeyId keyId) => keyId.Value; + + public static Validation ValidKeyId(string keyId) + { + if (string.IsNullOrWhiteSpace(keyId)) + { + return new StringIsNullOrWhitespaceError(); + } + + return new KeyId(keyId); + } + + public static KeyId CreateKeyId() + { + var id = Guid.NewGuid().ToString(); + return new KeyId(id); + } +} diff --git a/src/WalletFramework.Core/Functional/Enumerable/EnumerableFun.cs b/src/WalletFramework.Core/Functional/Enumerable/EnumerableFun.cs new file mode 100644 index 00000000..d606203a --- /dev/null +++ b/src/WalletFramework.Core/Functional/Enumerable/EnumerableFun.cs @@ -0,0 +1,6 @@ +namespace WalletFramework.Core.Functional.Enumerable; + +public static class EnumerableFun +{ + public static bool IsEmpty(this IEnumerable enumerable) => !enumerable.Any(); +} diff --git a/src/WalletFramework.Functional/Error.cs b/src/WalletFramework.Core/Functional/Error.cs similarity index 89% rename from src/WalletFramework.Functional/Error.cs rename to src/WalletFramework.Core/Functional/Error.cs index 5a4ccebe..9638f6e7 100644 --- a/src/WalletFramework.Functional/Error.cs +++ b/src/WalletFramework.Core/Functional/Error.cs @@ -1,7 +1,7 @@ using LanguageExt; -using static WalletFramework.Functional.ValidationFun; +using static WalletFramework.Core.Functional.ValidationFun; -namespace WalletFramework.Functional; +namespace WalletFramework.Core.Functional; public abstract record Error(string Message, Option Exception) { diff --git a/src/WalletFramework.Core/Functional/Errors/EnumerableIsEmptyError.cs b/src/WalletFramework.Core/Functional/Errors/EnumerableIsEmptyError.cs new file mode 100644 index 00000000..7fbd8e12 --- /dev/null +++ b/src/WalletFramework.Core/Functional/Errors/EnumerableIsEmptyError.cs @@ -0,0 +1,3 @@ +namespace WalletFramework.Core.Functional.Errors; + +public record EnumerableIsEmptyError() : Error($"The enumerable with items of type `{typeof(T).Name}` is empty"); diff --git a/src/WalletFramework.Core/Functional/Errors/NoItemsSucceededValidationError.cs b/src/WalletFramework.Core/Functional/Errors/NoItemsSucceededValidationError.cs new file mode 100644 index 00000000..0fc80a36 --- /dev/null +++ b/src/WalletFramework.Core/Functional/Errors/NoItemsSucceededValidationError.cs @@ -0,0 +1,3 @@ +namespace WalletFramework.Core.Functional.Errors; + +public record NoItemsSucceededValidationError() : Error($"No Validations of Type `{typeof(T).Name}` were successful"); diff --git a/src/WalletFramework.Core/Functional/Errors/StringIsNullOrWhitespaceError.cs b/src/WalletFramework.Core/Functional/Errors/StringIsNullOrWhitespaceError.cs new file mode 100644 index 00000000..87cc5806 --- /dev/null +++ b/src/WalletFramework.Core/Functional/Errors/StringIsNullOrWhitespaceError.cs @@ -0,0 +1,3 @@ +namespace WalletFramework.Core.Functional.Errors; + +public record StringIsNullOrWhitespaceError() : Error($"The string is null or whitespace for Type: `{nameof(T)}`"); diff --git a/src/WalletFramework.Core/Functional/OptionFun.cs b/src/WalletFramework.Core/Functional/OptionFun.cs new file mode 100644 index 00000000..a937a96f --- /dev/null +++ b/src/WalletFramework.Core/Functional/OptionFun.cs @@ -0,0 +1,96 @@ +using LanguageExt; +using WalletFramework.Core.Functional.Enumerable; + +namespace WalletFramework.Core.Functional; + +public static class OptionFun +{ + public static Option Some(T value) => value; + + public static Option None() => Option.None; + + public static Option ParseOption(T? value) => value ?? Option.None; + + public static Option OnSome(this Option option, Func> t2Func) => + from t1 in option + from t2 in t2Func(t1) + select t2; + + public static Option OnSome(this Option option, Func t2Func) => + from t1 in option + let t2 = t2Func(t1) + select t2; + + public static T? ToNullable(this Option option) => + option.MatchUnsafe( + Some: value => value, + None: () => default); + + public static T Fallback(this Option option, Func fallbackFunc) + { + var f = fallbackFunc(); + return option.Fallback(f); + } + + public static T Fallback(this Option option, T fallback) => + option.Match( + t => t, + () => fallback + ); + + /// + /// Traverses an enumerable for option + /// + /// + /// A option of an enumerable of every item. + /// In case the enumerable is empty, the traverse will return None + /// + /// The traverse only succeeds when every item of the enumerable returns Some. If you want to + /// ignore all the None items and only keep the Some items use + public static Option> TraverseAll( + this IEnumerable enumerable, + Func> optionFunc) + { + var list = enumerable.ToList(); + if (list.IsEmpty()) + return Option>.None; + + return list + .Select(optionFunc) + .Traverse(t => t); + } + + /// + /// Traverses an enumerable for option + /// + /// + /// A option of an enumerable which contains the items where the optionFunc returned Some + /// + /// + /// The traverse will ignore the None items and only keep Some items. If you want the traverse to + /// only return Some when everything is Some use + public static Option> TraverseAny( + this IEnumerable enumerable, + Func> optionFunc) + { + var items = enumerable.ToList(); + if (items.IsEmpty()) + return Option>.None; + + var traverse = items + .Select(optionFunc) + .Where(validation => validation.IsSome) + .Traverse(validation => validation); + + return traverse.OnSome(traversedItems => + { + var list = traversedItems.ToList(); + return list.Any() ? list : Option>.None; + }); + } + + public static T UnwrapOrThrow(this Option option, Exception e) => + option.Match( + t => t, + () => throw e); +} diff --git a/src/WalletFramework.Functional/TaskFun.cs b/src/WalletFramework.Core/Functional/TaskFun.cs similarity index 96% rename from src/WalletFramework.Functional/TaskFun.cs rename to src/WalletFramework.Core/Functional/TaskFun.cs index 965035f3..b9f00943 100644 --- a/src/WalletFramework.Functional/TaskFun.cs +++ b/src/WalletFramework.Core/Functional/TaskFun.cs @@ -1,8 +1,9 @@ -namespace WalletFramework.Functional; +namespace WalletFramework.Core.Functional; public enum TaskCompletionResult { Successful, + Error, Exceptional } diff --git a/src/WalletFramework.Core/Functional/Validation.cs b/src/WalletFramework.Core/Functional/Validation.cs new file mode 100644 index 00000000..e77dbae6 --- /dev/null +++ b/src/WalletFramework.Core/Functional/Validation.cs @@ -0,0 +1,308 @@ +using LanguageExt; +using WalletFramework.Core.Functional.Enumerable; +using WalletFramework.Core.Functional.Errors; + +namespace WalletFramework.Core.Functional; + +public readonly struct Validation +{ + public Validation(Validation value) + { + Value = value; + } + + public Validation Value { get; } + + public bool IsSuccess => Value.IsSuccess; + + public bool IsFail => Value.IsFail; + + public static implicit operator Validation(Validation value) => value.Value; + + public static implicit operator Validation(Validation value) => new(value); + + public static implicit operator Validation(Error error) => new(Validation.Fail(Seq.create(error))); + + public static implicit operator Validation(Seq errors) => new(Validation.Fail(errors)); + + public static implicit operator Validation(T value) => new(Validation.Success(value)); +} + +public delegate Validation Validator(T value); + +public delegate Validation Validator(T1 value); + +public static class ValidationFun +{ + public static Validation>>>>> Apply( + this Validation> valF, + Validation valT) => + Apply(valF.Select(Prelude.curry), valT); + + public static Validation>>>> Apply( + this Validation> valF, + Validation valT) => + Apply(valF.Select(Prelude.curry), valT); + + public static Validation>>> Apply( + this Validation> valF, + Validation valT) => + Apply(valF.Select(Prelude.curry), valT); + + public static Validation>> Apply( + this Validation> valF, + Validation valT) => + Apply(valF.Select(Prelude.curry), valT); + + public static Validation> Apply( + this Validation> valF, + Validation valT) => + Apply(valF.Select(Prelude.curry), valT); + + public static Validation Apply( + this Validation> valF, + Validation valT) => + valF.Value.Match( + f => + valT.Value.Match( + t => Valid(f(t)), + errors => errors + ), + errors => + valT.Value.Match( + _ => errors, + errorsT => errors + errorsT + ) + ); + + public static T2 Match( + this Validation validation, + Func valid, + Func, T2> invalid) => + validation.Value.Match(valid, invalid); + + public static async Task Match( + this Task> validation, + Func> valid, + Func, Task> invalid) => + await (await validation).Value.MatchAsync(valid, invalid); + + public static Unit Match( + this Validation validation, + Action valid, + Action> invalid) => + validation.Value.Match(valid, invalid); + + public static async Task> Select( + this Task> validation, + Func> task) => + await (await validation).Value.MatchAsync( + async value => Valid(await task(value)), + error => error + ); + + public static Validation Select( + this Validation validation, + Func func) => + validation.Value.Select(func); + + public static async Task> SelectMany( + this Validation validation, + Func>> bind, + Func project) + { + var bindResult = + await validation.Value.MatchAsync( + async t => (await bind(t)).Value, + error => error); + + return validation.Value.SelectMany(_ => bindResult, project); + } + + public static async Task> SelectMany( + this Task> validation, + Func>> bind, + Func project) + { + var validationValue = await validation; + return await validationValue.SelectMany(bind, project); + } + + public static Validation SelectMany( + this Validation validation, + Func> bind, + Func project) => + new(validation.Value.SelectMany(t => bind(t).Value, project)); + + public static Option ToOption(this Validation validation) => validation.Value.ToOption(); + + public static T Fallback(this Validation validation, Func fallbackFunc) => + validation.ToOption().Fallback(fallbackFunc); + + public static T Fallback(this Validation validation, T fallback) => + validation.ToOption().Fallback(fallback); + + /// + /// Traverses an enumerable for validation + /// + /// + /// A validation of an enumerable of every item. + /// In case the enumerable is empty, the validation will result in a + /// + /// The traverse only succeeds when every item of the enumerable is valid. If you want to + /// ignore all the invalid items and only keep the valid items use + public static Validation> TraverseAll( + this IEnumerable enumerable, + Func> validationFunc) + { + var list = enumerable.ToList(); + if (list.IsEmpty()) + return new EnumerableIsEmptyError(); + + return list + .Select(t => validationFunc(t).Value) + .Traverse(t => t); + } + + /// + /// Traverses an enumerable for validation + /// + /// + /// A validation of an enumerable which contains the items where the validation succeeded + /// The validation will result in a , in case + /// no items succeeded the validation + /// + /// + /// The traverse will ignore the invalid items and only keep the valid items. If you want the validation to + /// only succeed when everything is valid use + public static Validation> TraverseAny( + this IEnumerable enumerable, + Func> validationFunc) + { + var items = enumerable.ToList(); + if (items.IsEmpty()) + return new EnumerableIsEmptyError(); + + Validation> traverse = items + .Select(t => validationFunc(t).Value) + .Where(validation => validation.IsSuccess) + .Traverse(validation => validation); + + return traverse.OnSuccess(traversedItems => + { + Validation> result; + var list = traversedItems.ToList(); + if (list.Any()) + result = list; + else + result = new NoItemsSucceededValidationError(); + + return result; + }); + } + + public static Validation OnSuccess(this Validation validation, Func> onSucc) => + from t1 in validation + from t2 in onSucc(t1) + select t2; + + public static Validation OnSuccess(this Validation validation, Func onSucc) => + from t1 in validation + let t2 = onSucc(t1) + select t2; + + public static Task> OnSuccess(this Validation validation, Func onSucc) + { + var adapter = new Func>(async arg => + { + await onSucc(arg); + return Unit.Default; + }); + + return validation.OnSuccess(async t1 => await adapter(t1)); + } + + public static Task> OnSuccess( + this Task> validation, + Func>> onSucc) => + from t1 in validation + from t2 in onSucc(t1) + select t2; + + public static Task> OnSuccess( + this Validation validation, + Func>> onSucc) => + from t1 in validation + from t2 in onSucc(t1) + select t2; + + public static Task> OnSuccess( + this Validation validation, + Func> onSucc) => + validation.OnSuccess(async t1 => + { + var taskT2 = onSucc(t1); + var t2 = await taskT2; + return Valid(t2); + }); + + public static async Task> OnSuccess( + this Task> validation, + Func onSucc) + { + var validationT1 = await validation; + return + from t1 in validationT1 + let t2 = onSucc(t1) + select t2; + } + + public static Validation Invalid(Seq errors) => Validation.Fail(errors); + + public static Validation Valid(T value) => value; + + public static Validation Flatten(this Validation> stackedValidation) => + from outer in stackedValidation + from inner in outer + select inner; + + public static T UnwrapOrThrow(this Validation validation, Exception e) => + validation.Match( + t => t, + _ => throw e); + + public static Validator AggregateValidators(this IEnumerable> validators) => t => + { + var errors = validators + .Select(validate => validate(t)) + .SelectMany(validation => validation.Match( + _ => Option>.None, + errors => errors) + ) + .SelectMany(seq => seq) + .ToSeq(); + + return errors.Any() + ? Invalid(errors) + : t; + }; + + /// + /// Iterates through all validators and returns the first validator that succeeded + /// + /// The first successful validator or when nothing was sucessful + /// This is early out, which means after the first validator was successful it will not compute the other ones + public static Validator FirstValid(this IEnumerable> validators) => t => + { + foreach (var validator in validators) + { + var x = validator(t); + if (x.IsSuccess) + { + return x; + } + } + + return new NoItemsSucceededValidationError(); + }; +} diff --git a/src/WalletFramework.Core/IsExternalInit.cs b/src/WalletFramework.Core/IsExternalInit.cs new file mode 100644 index 00000000..eaa352a3 --- /dev/null +++ b/src/WalletFramework.Core/IsExternalInit.cs @@ -0,0 +1,7 @@ +namespace System.Runtime.CompilerServices; + +// This is needed for the init property setter. This can be removed when updating to a newer C# Version. +// https://stackoverflow.com/questions/64749385/predefined-type-system-runtime-compilerservices-isexternalinit-is-not-defined +internal static class IsExternalInit +{ +} diff --git a/src/WalletFramework.Core/Json/Converters/DictJsonConverter.cs b/src/WalletFramework.Core/Json/Converters/DictJsonConverter.cs new file mode 100644 index 00000000..699ac9b6 --- /dev/null +++ b/src/WalletFramework.Core/Json/Converters/DictJsonConverter.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace WalletFramework.Core.Json.Converters; + +public sealed class DictJsonConverter : JsonConverter> +{ + public override bool CanRead => false; + + public override void WriteJson(JsonWriter writer, Dictionary? dict, JsonSerializer serializer) + { + var dictJson = new JObject(); + foreach (var (key, config) in dict!) + { + var x = JObject.FromObject(config!); + dictJson.Add(key!.ToString(), x); + } + serializer.Serialize(writer, dictJson); + } + + public override Dictionary ReadJson( + JsonReader reader, + Type objectType, + Dictionary? existingValue, + bool hasExistingValue, + JsonSerializer serializer) => + throw new NotImplementedException(); +} diff --git a/src/WalletFramework.Core/Json/Converters/OneOfJsonConverter.cs b/src/WalletFramework.Core/Json/Converters/OneOfJsonConverter.cs new file mode 100644 index 00000000..6ec2cba8 --- /dev/null +++ b/src/WalletFramework.Core/Json/Converters/OneOfJsonConverter.cs @@ -0,0 +1,29 @@ +using LanguageExt; +using Newtonsoft.Json; +using OneOf; + +namespace WalletFramework.Core.Json.Converters; + +public class OneOfJsonConverter : JsonConverter where TOneOf : OneOfBase +{ + public override void WriteJson(JsonWriter writer, TOneOf? oneOf, JsonSerializer serializer) + { + oneOf!.Match( + t1 => + { + serializer.Serialize(writer, t1); + return Unit.Default; + }, + t2 => + { + serializer.Serialize(writer, t2); + return Unit.Default; + }); + } + + public override TOneOf ReadJson(JsonReader reader, Type objectType, TOneOf? existingValue, bool hasExistingValue, + JsonSerializer serializer) => + throw new NotImplementedException(); + + public override bool CanRead => false; +} diff --git a/src/WalletFramework.Core/Json/Converters/OptionJsonConverter.cs b/src/WalletFramework.Core/Json/Converters/OptionJsonConverter.cs new file mode 100644 index 00000000..0f5c50ac --- /dev/null +++ b/src/WalletFramework.Core/Json/Converters/OptionJsonConverter.cs @@ -0,0 +1,28 @@ +using LanguageExt; +using Newtonsoft.Json; + +namespace WalletFramework.Core.Json.Converters; + +public sealed class OptionJsonConverter : JsonConverter> +{ + public override void WriteJson(JsonWriter writer, Option option, JsonSerializer serializer) + { + option.Match( + t => + { + serializer.Serialize(writer, t); + }, + () => serializer.Serialize(writer, null) + ); + } + + public override Option ReadJson( + JsonReader reader, + Type objectType, + Option existingValue, + bool hasExistingValue, + JsonSerializer serializer) => + throw new NotImplementedException(); + + public override bool CanRead => false; +} diff --git a/src/WalletFramework.Core/Json/Converters/ValueTypeJsonConverter.cs b/src/WalletFramework.Core/Json/Converters/ValueTypeJsonConverter.cs new file mode 100644 index 00000000..2289d809 --- /dev/null +++ b/src/WalletFramework.Core/Json/Converters/ValueTypeJsonConverter.cs @@ -0,0 +1,48 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace WalletFramework.Core.Json.Converters; + +public interface IValueTypeDecoder +{ + public T Decode(JToken token); +} + +public sealed class ValueTypeJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer) + { + var str = value!.ToString(); + writer.WriteValue(str); + } + + public override T ReadJson( + JsonReader reader, + Type objectType, + T? existingValue, + bool hasExistingValue, + JsonSerializer serializer) => throw new NotImplementedException(); +} + +public sealed class ValueTypeJsonConverter : JsonConverter where TDecoder : IValueTypeDecoder +{ + public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer) + { + var str = value!.ToString(); + writer.WriteValue(str); + } + + public override T ReadJson( + JsonReader reader, + Type objectType, + T? existingValue, + bool hasExistingValue, + JsonSerializer serializer) + { + var token = JToken.Load(reader); + var decoder = (TDecoder)Activator.CreateInstance(typeof(TDecoder)); + return decoder.Decode(token); + } +} diff --git a/src/WalletFramework.Core/Json/Errors/InvalidJsonError.cs b/src/WalletFramework.Core/Json/Errors/InvalidJsonError.cs new file mode 100644 index 00000000..dc243765 --- /dev/null +++ b/src/WalletFramework.Core/Json/Errors/InvalidJsonError.cs @@ -0,0 +1,6 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Core.Json.Errors; + +public record InvalidJsonError(string Json, Exception E) + : Error($"The JSON could not be parsed. JSON Value is `{Json}`", E); diff --git a/src/WalletFramework.Core/Json/Errors/JTokenIsNotAJValueError.cs b/src/WalletFramework.Core/Json/Errors/JTokenIsNotAJValueError.cs new file mode 100644 index 00000000..32f66850 --- /dev/null +++ b/src/WalletFramework.Core/Json/Errors/JTokenIsNotAJValueError.cs @@ -0,0 +1,7 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Core.Json.Errors; + +// ReSharper disable once InconsistentNaming +public record JTokenIsNotAJValueError(string Token, Exception E) + : Error($"The token `{Token}` could not be transformed into a JValue", E); diff --git a/src/WalletFramework.Core/Json/Errors/JTokenIsNotAnJArrayError.cs b/src/WalletFramework.Core/Json/Errors/JTokenIsNotAnJArrayError.cs new file mode 100644 index 00000000..a1769011 --- /dev/null +++ b/src/WalletFramework.Core/Json/Errors/JTokenIsNotAnJArrayError.cs @@ -0,0 +1,6 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Core.Json.Errors; + +public record JTokenIsNotAnJArrayError(string Name, Exception E) + : Error($"The field '{Name}' is not an JArray.", E); diff --git a/src/WalletFramework.Core/Json/Errors/JTokenIsNotAnJObjectError.cs b/src/WalletFramework.Core/Json/Errors/JTokenIsNotAnJObjectError.cs new file mode 100644 index 00000000..3e7b9f44 --- /dev/null +++ b/src/WalletFramework.Core/Json/Errors/JTokenIsNotAnJObjectError.cs @@ -0,0 +1,6 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Core.Json.Errors; + +public record JTokenIsNotAnJObjectError(string Name, Exception E) + : Error($"The field '{Name}' is not an JObject.", E); diff --git a/src/WalletFramework.Core/Json/Errors/JValueIsNotAnIntError.cs b/src/WalletFramework.Core/Json/Errors/JValueIsNotAnIntError.cs new file mode 100644 index 00000000..508d7b1d --- /dev/null +++ b/src/WalletFramework.Core/Json/Errors/JValueIsNotAnIntError.cs @@ -0,0 +1,14 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Core.Json.Errors; + +public record JValueIsNotAnIntError : Error +{ + public JValueIsNotAnIntError(string value, Exception e) : base($"The JValue is not an int. Actual value is `{value}`", e) + { + } + + public JValueIsNotAnIntError(string value) : base($"The JValue is not an int. Actual value is `{value}`") + { + } +} diff --git a/src/WalletFramework.Core/Json/Errors/JsonFieldNotFoundError.cs b/src/WalletFramework.Core/Json/Errors/JsonFieldNotFoundError.cs new file mode 100644 index 00000000..291b6fae --- /dev/null +++ b/src/WalletFramework.Core/Json/Errors/JsonFieldNotFoundError.cs @@ -0,0 +1,5 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Core.Json.Errors; + +public record JsonFieldNotFoundError(string FieldName) : Error($"The field '{FieldName}' was not found."); diff --git a/src/WalletFramework.Core/Json/Errors/JsonFieldValueIsNullOrWhitespaceError.cs b/src/WalletFramework.Core/Json/Errors/JsonFieldValueIsNullOrWhitespaceError.cs new file mode 100644 index 00000000..36ad2a34 --- /dev/null +++ b/src/WalletFramework.Core/Json/Errors/JsonFieldValueIsNullOrWhitespaceError.cs @@ -0,0 +1,6 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Core.Json.Errors; + +public record JsonFieldValueIsNullOrWhitespaceError(string Name) + : Error($"The value of the field `{Name}` is null or empty"); diff --git a/src/WalletFramework.Core/Json/Errors/JsonIsNotAMapError.cs b/src/WalletFramework.Core/Json/Errors/JsonIsNotAMapError.cs new file mode 100644 index 00000000..43c77b95 --- /dev/null +++ b/src/WalletFramework.Core/Json/Errors/JsonIsNotAMapError.cs @@ -0,0 +1,7 @@ +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; + +namespace WalletFramework.Core.Json.Errors; + +public record JsonIsNotAMapError(JObject JObject, Exception E) + : Error($"The jObject `{JObject}` could not be transformed into a dictionary", E); diff --git a/src/WalletFramework.Core/Json/JsonFun.cs b/src/WalletFramework.Core/Json/JsonFun.cs new file mode 100644 index 00000000..06be57c4 --- /dev/null +++ b/src/WalletFramework.Core/Json/JsonFun.cs @@ -0,0 +1,174 @@ +using System.Globalization; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json.Errors; + +namespace WalletFramework.Core.Json; + +public static class JsonFun +{ + public static Validation GetByKey(this JToken token, string key) + { + try + { + var jObject = token.ToObject()!; + return jObject.GetByKey(key); + } + catch (Exception e) + { + return new JTokenIsNotAnJObjectError(key, e); + } + } + + public static Validation GetByKey(this JObject jObject, string key) + { + var success = jObject.TryGetValue(key, out var value); + if (success) + { + var str = value!.ToString(); + if (string.IsNullOrWhiteSpace(str)) + { + return new JsonFieldValueIsNullOrWhitespaceError(key); + } + else + { + return value; + } + } + else + { + return new JsonFieldNotFoundError(key); + } + } + + public static Validation ToJArray(this JToken token) + { + try + { + var array = token.ToObject()!; + return array; + } + catch (Exception e) + { + return new JTokenIsNotAnJArrayError(token.ToString(), e); + } + } + + public static Validation ToJObject(this JToken token) + { + try + { + var jObject = token.ToObject()!; + return jObject; + } + catch (Exception e) + { + return new JTokenIsNotAnJObjectError(token.ToString(), e); + } + } + + public static Validation ToJValue(this JToken token) + { + try + { + var jValue = token.ToObject()!; + return jValue; + } + catch (Exception e) + { + return new JTokenIsNotAJValueError(token.ToString(), e); + } + } + + public static Validation> ToValidDictionaryAll( + this JObject jObject, + Func> keyValidation, + Func> valueValidation) where T1 : notnull + { + try + { + return jObject + .Properties() + .TraverseAll(property => + from key in keyValidation(property.Name) + from value in valueValidation(property.Value) + select new KeyValuePair(key, value)) + .OnSuccess(pairs => pairs.ToDictionary( + pair => pair.Key, + pair => pair.Value)); + } + catch (Exception e) + { + return new JsonIsNotAMapError(jObject, e); + } + } + + public static Validation> ToValidDictionaryAny( + this JObject jObject, + Func> keyValidation, + Func> valueValidation) where T1 : notnull => jObject + .Properties() + .TraverseAny(property => + from key in keyValidation(property.Name) + from value in valueValidation(property.Value) + select new KeyValuePair(key, value)) + .OnSuccess(pairs => pairs.ToDictionary( + pair => pair.Key, + pair => pair.Value)); + + public static Validation ToInt(this JValue value) + { + try + { + return value.ToObject(); + } + catch (Exception e) + { + return new JValueIsNotAnIntError(value.ToString(CultureInfo.InvariantCulture), e); + } + } + + public static Validation ParseAsJObject(string json) + { + try + { + var jObject = JObject.Parse(json); + return jObject; + } + catch (Exception e) + { + return new InvalidJsonError(json, e); + } + } + + public static JToken RemoveNulls(this JToken token) + { + switch (token.Type) + { + case JTokenType.Object: + var obj = new JObject(); + foreach (var property in ((JObject)token).Properties()) + { + if (property.Value.Type != JTokenType.Null) + { + obj.Add(property.Name, RemoveNulls(property.Value)); + } + } + return obj; + + case JTokenType.Array: + var array = new JArray(); + foreach (var item in (JArray)token) + { + if (item.Type != JTokenType.Null) + { + array.Add(RemoveNulls(item)); + } + } + return array; + + default: + return token; + } + } +} diff --git a/src/WalletFramework.Core/Json/JsonSettings.cs b/src/WalletFramework.Core/Json/JsonSettings.cs new file mode 100644 index 00000000..71f97261 --- /dev/null +++ b/src/WalletFramework.Core/Json/JsonSettings.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json; + +namespace WalletFramework.Core.Json; + +public static class JsonSettings +{ + public static JsonSerializerSettings SerializerSettings => new() + { + NullValueHandling = NullValueHandling.Ignore + }; +} diff --git a/src/WalletFramework.Core/Localization/Constants.cs b/src/WalletFramework.Core/Localization/Constants.cs new file mode 100644 index 00000000..6c3b90d4 --- /dev/null +++ b/src/WalletFramework.Core/Localization/Constants.cs @@ -0,0 +1,10 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Core.Localization; + +public static class Constants +{ + public static Locale DefaultLocale = Locale + .ValidLocale("en-US") + .UnwrapOrThrow(new InvalidOperationException("The default locale is corrupt.")); +} diff --git a/src/WalletFramework.Core/Localization/Errors/LocaleError.cs b/src/WalletFramework.Core/Localization/Errors/LocaleError.cs new file mode 100644 index 00000000..64cc6d4d --- /dev/null +++ b/src/WalletFramework.Core/Localization/Errors/LocaleError.cs @@ -0,0 +1,14 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Errors; + +public record LocaleError : Error +{ + public LocaleError(string value, Exception e) : base($"The locale could not be processed. Value is: `{value}`", e) + { + } + + public LocaleError(string value) : base($"The locale could not be processed. Value is: `{value}`") + { + } +} diff --git a/src/WalletFramework.Core/Localization/Locale.cs b/src/WalletFramework.Core/Localization/Locale.cs new file mode 100644 index 00000000..0af15b7e --- /dev/null +++ b/src/WalletFramework.Core/Localization/Locale.cs @@ -0,0 +1,93 @@ +using System.Globalization; +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Functional.Errors; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Oid4Vc.Oid4Vci.Errors; + +namespace WalletFramework.Core.Localization; + +/// +/// A value type that represent a locale or respectively a language tag. For example +/// ("en-US"). These are based on RFC 4646: https://www.rfc-editor.org/rfc/rfc4646.html. +/// Locales are case-sensitive. +/// +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct Locale +{ + private CultureInfo Value { get; } + + private Locale(CultureInfo value) => Value = value; + + public static implicit operator string(Locale locale) => locale.Value.ToString(); + + [JsonConstructor] + private Locale(string locale) + { + var result = ValidLocale(locale).UnwrapOrThrow(new InvalidOperationException("Locale is corrupt")); + Value = result.Value; + } + + public static Validation ValidLocale(string locale) + { + try + { + if (string.IsNullOrWhiteSpace(locale)) + return new StringIsNullOrWhitespaceError(); + + var cultureInfo = CultureInfo.CreateSpecificCulture(locale); + if (cultureInfo.TwoLetterISOLanguageName == "iv") + return new LocaleError(locale); + + return new Locale(cultureInfo); + } + catch (Exception e) + { + return new LocaleError(locale, e); + } + } + + public static Validation ValidLocale(JToken locale) => + from value in locale.ToJValue() + from result in ValidLocale(value.ToString(CultureInfo.InvariantCulture)) + select result; + + public static Option OptionLocale(string locale) => ValidLocale(locale).ToOption(); + + public static Option OptionLocale(JToken locale) => ValidLocale(locale).ToOption(); + + public override string ToString() => this; +} + +public static class LocaleExtensions +{ + /// + /// Tries to find a match for a given appLocale inside a dictionary. + /// If no match is found it will try again with the DefaultLocale ("en"). + /// If no match for DefaultLocale is found, it will return the first result that is found inside the dictionary. + /// + /// Dictionary with locales as keys and display objects as values. + /// The locale that should be matched. + /// The type of the display object. + /// Dictionary must be not empty otherwise this will throw an exception. + /// + /// The TDisplay that matches the appLocale or one that matches the DefaultLocale or the first locale inside the + /// dictionary. + /// + public static TDisplay FindOrDefault(this IDictionary displays, Locale locale) + { + var matchedLocale = + displays + .Keys + .Find(x => x.ToString().Contains(locale)) + .IfNone(() => displays + .Keys + .Find(x => x.ToString().Contains(Constants.DefaultLocale)) + .IfNone(() => displays.Keys.First())); + + return displays[matchedLocale]; + } +} diff --git a/src/WalletFramework.Core/Uri/UriFun.cs b/src/WalletFramework.Core/Uri/UriFun.cs new file mode 100644 index 00000000..31ac25a7 --- /dev/null +++ b/src/WalletFramework.Core/Uri/UriFun.cs @@ -0,0 +1,6 @@ +namespace WalletFramework.Core.Uri; + +public static class UriFun +{ + public static string ToStringWithoutTrail(this System.Uri uri) => uri.ToString().TrimEnd('/'); +} diff --git a/src/WalletFramework.Core/WalletFramework.Core.csproj b/src/WalletFramework.Core/WalletFramework.Core.csproj new file mode 100644 index 00000000..517c6eb7 --- /dev/null +++ b/src/WalletFramework.Core/WalletFramework.Core.csproj @@ -0,0 +1,12 @@ + + + netstandard2.1 + enable + enable + + + + + + + diff --git a/src/WalletFramework.Functional/OptionFun.cs b/src/WalletFramework.Functional/OptionFun.cs deleted file mode 100644 index 7a2f5ce1..00000000 --- a/src/WalletFramework.Functional/OptionFun.cs +++ /dev/null @@ -1,10 +0,0 @@ -using LanguageExt; - -namespace WalletFramework.Functional; - -public static class OptionFun -{ - public static Option Some(T value) => value; - - public static Option None() => Option.None; -} diff --git a/src/WalletFramework.Functional/Validation.cs b/src/WalletFramework.Functional/Validation.cs deleted file mode 100644 index 4b357807..00000000 --- a/src/WalletFramework.Functional/Validation.cs +++ /dev/null @@ -1,215 +0,0 @@ -using LanguageExt; - -namespace WalletFramework.Functional; - -public readonly struct Validation -{ - public Validation(Validation value) - { - Value = value; - } - - public Validation Value { get; } - - public static implicit operator Validation(Validation value) => value.Value; - - public static implicit operator Validation(Validation value) => new(value); - - public static implicit operator Validation(Error error) => new(Validation.Fail(Seq.create(error))); - - public static implicit operator Validation(Seq errors) => new(Validation.Fail(errors)); - - public static implicit operator Validation(T value) => new(Validation.Success(value)); -} - -public delegate Validation Validator(T value); - -public static class ValidationFun -{ - public static Validation>>>>> Apply( - this Validation> valF, - Validation valT) => - Apply(valF.Select(Prelude.curry), valT); - - public static Validation>>>> Apply( - this Validation> valF, - Validation valT) => - Apply(valF.Select(Prelude.curry), valT); - - public static Validation>>> Apply( - this Validation> valF, - Validation valT) => - Apply(valF.Select(Prelude.curry), valT); - - public static Validation>> Apply( - this Validation> valF, - Validation valT) => - Apply(valF.Select(Prelude.curry), valT); - - public static Validation> Apply( - this Validation> valF, - Validation valT) => - Apply(valF.Select(Prelude.curry), valT); - - public static Validation Apply( - this Validation> valF, - Validation valT) => - valF.Value.Match( - f => - valT.Value.Match( - t => Valid(f(t)), - errors => errors - ), - errors => - valT.Value.Match( - _ => errors, - errorsT => errors + errorsT - ) - ); - - public static T2 Match( - this Validation validation, - Func valid, - Func, T2> invalid) => - validation.Value.Match(valid, invalid); - - public static async Task Match( - this Task> validation, - Func> valid, - Func, Task> invalid) => - await (await validation).Value.MatchAsync(valid, invalid); - - public static Unit Match( - this Validation validation, - Action valid, - Action> invalid) => - validation.Value.Match(valid, invalid); - - public static async Task> Select( - this Task> validation, - Func> task) => - await (await validation).Value.MatchAsync( - async value => Valid(await task(value)), - error => error - ); - - public static Validation Select( - this Validation validation, - Func func) => - validation.Value.Select(func); - - public static async Task> SelectMany( - this Validation validation, - Func>> bind, - Func project) - { - var bindResult = - await validation.Value.MatchAsync( - async t => (await bind(t)).Value, - error => error - ); - - return validation.Value.SelectMany(_ => bindResult, project); - } - - public static async Task> SelectMany( - this Task> validation, - Func>> bind, - Func project) - { - var validationValue = await validation; - - var bindResult = - await validationValue.Value.MatchAsync( - async t => (await bind(t)).Value, - error => error - ); - - return validationValue.Value.SelectMany(_ => bindResult, project); - } - - public static Validation SelectMany( - this Validation validation, - Func> bind, - Func project) => - new(validation.Value.SelectMany(t => bind(t).Value, project)); - - public static Option ToOption(this Validation validation) => - validation.Value.ToOption(); - - public static Validation> Traverse( - this IEnumerable> enumerable, - Func func) => - enumerable - .Select(validation => validation.Value) - .Traverse(func); - - public static Validation> Traverse( - this Dictionary, Validation> dict) where TKey : notnull - { - var createDict = new Func, IEnumerable, Dictionary>( - (keys, values) => - { - var result = new Dictionary(); - var keysArray = keys.ToArray(); - var valuesArray = values.ToArray(); - for (var i = 0; i < keysArray.Length; i++) - { - result.Add(keysArray[i], valuesArray[i]); - } - - return result; - } - ); - - var keys = - dict.Keys.Select(validation => validation).Traverse(key => key); - - var values = - dict.Values.Select(validation => validation).Traverse(value => value); - - return - Valid(createDict) - .Apply(keys) - .Apply(values); - } - - public static Validation OnSuccess(this Validation validation, Func> onSucc) => - from t in validation - from t2 in onSucc(t) - select t2; - - public static Validation OnSuccess(this Validation validation, Func onSucc) => - from t in validation - let t2 = onSucc(t) - select t2; - - public static Validation Invalid(Seq errors) => Validation.Fail(errors); - - public static Validation Valid(T value) => value; - - public static Validation Flatten(this Validation> stackedValidation) => - from outer in stackedValidation - from inner in outer - select inner; - - public static Validator HarvestErrors(IEnumerable> validators) - => t => - { - var errors = validators - .Select(validate => validate(t)) - .SelectMany(validation => validation.Match( - _ => Option>.None, - errors => errors - )) - .SelectMany(seq => seq) - .ToSeq(); - - return errors.Count() == 0 - ? t - : Invalid(errors); - }; - - public static Validator AggregateValidators(this IEnumerable> validators) - => HarvestErrors(validators); -} diff --git a/src/WalletFramework.Mdoc/CborByteString.cs b/src/WalletFramework.MdocLib/CborByteString.cs similarity index 89% rename from src/WalletFramework.Mdoc/CborByteString.cs rename to src/WalletFramework.MdocLib/CborByteString.cs index 5ad7d3a8..aa491d74 100644 --- a/src/WalletFramework.Mdoc/CborByteString.cs +++ b/src/WalletFramework.MdocLib/CborByteString.cs @@ -1,8 +1,8 @@ using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib.Common; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; /// /// A CBOR object which is a byte string which is either CBOR or hex encoded. diff --git a/src/WalletFramework.Mdoc/CborFun.cs b/src/WalletFramework.MdocLib/CborFun.cs similarity index 91% rename from src/WalletFramework.Mdoc/CborFun.cs rename to src/WalletFramework.MdocLib/CborFun.cs index 41f1bd5d..e17a2514 100644 --- a/src/WalletFramework.Mdoc/CborFun.cs +++ b/src/WalletFramework.MdocLib/CborFun.cs @@ -1,8 +1,8 @@ using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib.Common; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; internal static class CborFun { @@ -48,6 +48,7 @@ public static Validation GetByIndex(this CBORObject cbor, uint index return value; } + // TODO: Refactor or check with any public static Validation> ToDictionary( this CBORObject cborMap, Func> keyValidation, @@ -61,7 +62,7 @@ public static Validation> ToDictionary( from key in keyValidation(pair.Key) from value in valueValidation(pair.Value) select new KeyValuePair(key, value)) - .Traverse(pair => pair) + .TraverseAll(pair => pair) .OnSuccess(pairs => pairs.ToDictionary( pair => pair.Key, pair => pair.Value diff --git a/src/WalletFramework.Mdoc/Common/Constants.cs b/src/WalletFramework.MdocLib/Common/Constants.cs similarity index 90% rename from src/WalletFramework.Mdoc/Common/Constants.cs rename to src/WalletFramework.MdocLib/Common/Constants.cs index 8bd6a8be..fad70d31 100644 --- a/src/WalletFramework.Mdoc/Common/Constants.cs +++ b/src/WalletFramework.MdocLib/Common/Constants.cs @@ -1,4 +1,4 @@ -namespace WalletFramework.Mdoc.Common; +namespace WalletFramework.MdocLib.Common; internal static class Constants { diff --git a/src/WalletFramework.Mdoc/Common/Errors.cs b/src/WalletFramework.MdocLib/Common/Errors.cs similarity index 96% rename from src/WalletFramework.Mdoc/Common/Errors.cs rename to src/WalletFramework.MdocLib/Common/Errors.cs index 81eb1eed..1a55a467 100644 --- a/src/WalletFramework.Mdoc/Common/Errors.cs +++ b/src/WalletFramework.MdocLib/Common/Errors.cs @@ -1,6 +1,6 @@ -using WalletFramework.Functional; +using WalletFramework.Core.Functional; -namespace WalletFramework.Mdoc.Common; +namespace WalletFramework.MdocLib.Common; public record InvalidCborByteStringError(string Name, Exception E) : Error($"The value of *{Name}* is not a valid CBOR object encoded as a byte string", E); diff --git a/src/WalletFramework.Mdoc/CoseLabel.cs b/src/WalletFramework.MdocLib/CoseLabel.cs similarity index 86% rename from src/WalletFramework.Mdoc/CoseLabel.cs rename to src/WalletFramework.MdocLib/CoseLabel.cs index b7eafab6..3ef182a1 100644 --- a/src/WalletFramework.Mdoc/CoseLabel.cs +++ b/src/WalletFramework.MdocLib/CoseLabel.cs @@ -1,8 +1,9 @@ using Newtonsoft.Json.Linq; using PeterO.Cbor; -using WalletFramework.Functional; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib.Common; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; public readonly struct CoseLabel { @@ -16,7 +17,8 @@ private CoseLabel(int id) } public static implicit operator string(CoseLabel label) => label.Value; - + public override string ToString() => Value; + internal static Validation ValidCoseLabel(CBORObject cbor) { int id; diff --git a/src/WalletFramework.Mdoc/CoseSignature.cs b/src/WalletFramework.MdocLib/CoseSignature.cs similarity index 83% rename from src/WalletFramework.Mdoc/CoseSignature.cs rename to src/WalletFramework.MdocLib/CoseSignature.cs index 500edbae..934af800 100644 --- a/src/WalletFramework.Mdoc/CoseSignature.cs +++ b/src/WalletFramework.MdocLib/CoseSignature.cs @@ -1,7 +1,7 @@ using PeterO.Cbor; -using WalletFramework.Functional; +using WalletFramework.Core.Functional; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; public readonly struct CoseSignature { diff --git a/src/WalletFramework.Mdoc/DeviceSigned.cs b/src/WalletFramework.MdocLib/DeviceSigned.cs similarity index 83% rename from src/WalletFramework.Mdoc/DeviceSigned.cs rename to src/WalletFramework.MdocLib/DeviceSigned.cs index df06e9be..8807f83f 100644 --- a/src/WalletFramework.Mdoc/DeviceSigned.cs +++ b/src/WalletFramework.MdocLib/DeviceSigned.cs @@ -1,4 +1,4 @@ -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; // TODO: mdoc authentication public readonly struct DeviceSigned @@ -10,9 +10,6 @@ public readonly struct DeviceSigned public readonly struct DeviceAuth { - // TODO: Do we support this? - // public DeviceMac DeviceMac { get; } - public DeviceSignature DeviceSignature { get; } } diff --git a/src/WalletFramework.Mdoc/Digest.cs b/src/WalletFramework.MdocLib/Digest.cs similarity index 81% rename from src/WalletFramework.Mdoc/Digest.cs rename to src/WalletFramework.MdocLib/Digest.cs index 8a1791ee..29f3f1d6 100644 --- a/src/WalletFramework.Mdoc/Digest.cs +++ b/src/WalletFramework.MdocLib/Digest.cs @@ -1,8 +1,8 @@ using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib.Common; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; public readonly struct Digest { diff --git a/src/WalletFramework.Mdoc/DigestAlgorithm.cs b/src/WalletFramework.MdocLib/DigestAlgorithm.cs similarity index 91% rename from src/WalletFramework.Mdoc/DigestAlgorithm.cs rename to src/WalletFramework.MdocLib/DigestAlgorithm.cs index 88fb9ceb..fbb1c07d 100644 --- a/src/WalletFramework.Mdoc/DigestAlgorithm.cs +++ b/src/WalletFramework.MdocLib/DigestAlgorithm.cs @@ -1,9 +1,9 @@ using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; -using static WalletFramework.Mdoc.Common.Constants; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib.Common; +using static WalletFramework.MdocLib.Common.Constants; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; public readonly struct DigestAlgorithm { diff --git a/src/WalletFramework.Mdoc/DigestId.cs b/src/WalletFramework.MdocLib/DigestId.cs similarity index 90% rename from src/WalletFramework.Mdoc/DigestId.cs rename to src/WalletFramework.MdocLib/DigestId.cs index fedf2f4f..31c29eef 100644 --- a/src/WalletFramework.Mdoc/DigestId.cs +++ b/src/WalletFramework.MdocLib/DigestId.cs @@ -1,7 +1,7 @@ using PeterO.Cbor; -using WalletFramework.Functional; +using WalletFramework.Core.Functional; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; public readonly struct DigestId { diff --git a/src/WalletFramework.Mdoc/DocType.cs b/src/WalletFramework.MdocLib/DocType.cs similarity index 68% rename from src/WalletFramework.Mdoc/DocType.cs rename to src/WalletFramework.MdocLib/DocType.cs index b5817b6e..9d58f9e4 100644 --- a/src/WalletFramework.Mdoc/DocType.cs +++ b/src/WalletFramework.MdocLib/DocType.cs @@ -1,17 +1,24 @@ +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; -using static WalletFramework.Mdoc.Common.Constants; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json.Converters; +using WalletFramework.MdocLib.Common; +using static WalletFramework.MdocLib.Common.Constants; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; +[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct DocType { - public string Value { get; } + private string Value { get; } private DocType(string docType) => Value = docType; + public override string ToString() => Value; + + public static implicit operator string(DocType docType) => docType.Value; + internal static Validation ValidDoctype(CBORObject cborObject) => cborObject.GetByLabel(DocTypeLabel).OnSuccess(docType => { diff --git a/src/WalletFramework.Mdoc/IsExternalInit.cs b/src/WalletFramework.MdocLib/IsExternalInit.cs similarity index 100% rename from src/WalletFramework.Mdoc/IsExternalInit.cs rename to src/WalletFramework.MdocLib/IsExternalInit.cs diff --git a/src/WalletFramework.Mdoc/IssuerAuth.cs b/src/WalletFramework.MdocLib/IssuerAuth.cs similarity index 78% rename from src/WalletFramework.Mdoc/IssuerAuth.cs rename to src/WalletFramework.MdocLib/IssuerAuth.cs index 251f964a..1e1c4240 100644 --- a/src/WalletFramework.Mdoc/IssuerAuth.cs +++ b/src/WalletFramework.MdocLib/IssuerAuth.cs @@ -1,15 +1,15 @@ using PeterO.Cbor; -using WalletFramework.Functional; -using static WalletFramework.Mdoc.Common.Constants; -using static WalletFramework.Mdoc.ProtectedHeaders; -using static WalletFramework.Mdoc.UnprotectedHeaders; -using static WalletFramework.Mdoc.MobileSecurityObject; -using static WalletFramework.Mdoc.CoseSignature; -using static WalletFramework.Functional.ValidationFun; +using WalletFramework.Core.Functional; +using static WalletFramework.MdocLib.Common.Constants; +using static WalletFramework.MdocLib.ProtectedHeaders; +using static WalletFramework.MdocLib.UnprotectedHeaders; +using static WalletFramework.MdocLib.MobileSecurityObject; +using static WalletFramework.MdocLib.CoseSignature; +using static WalletFramework.Core.Functional.ValidationFun; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; -public readonly struct IssuerAuth +public record IssuerAuth { public ProtectedHeaders ProtectedHeaders { get; } @@ -49,6 +49,7 @@ internal static Validation ValidIssuerAuth(CBORObject issuerSigned) public CBORObject Encode() { + var cbor = CBORObject.NewArray(); cbor.Add(ProtectedHeaders.ByteString); cbor.Add(UnprotectedHeaders.Encode()); diff --git a/src/WalletFramework.Mdoc/IssuerSigned.cs b/src/WalletFramework.MdocLib/IssuerSigned.cs similarity index 74% rename from src/WalletFramework.Mdoc/IssuerSigned.cs rename to src/WalletFramework.MdocLib/IssuerSigned.cs index 35e24df3..0157f184 100644 --- a/src/WalletFramework.Mdoc/IssuerSigned.cs +++ b/src/WalletFramework.MdocLib/IssuerSigned.cs @@ -1,13 +1,13 @@ using PeterO.Cbor; -using WalletFramework.Functional; -using static WalletFramework.Mdoc.Common.Constants; -using static WalletFramework.Mdoc.NameSpaces; -using static WalletFramework.Mdoc.IssuerAuth; -using static WalletFramework.Functional.ValidationFun; +using WalletFramework.Core.Functional; +using static WalletFramework.MdocLib.Common.Constants; +using static WalletFramework.MdocLib.NameSpaces; +using static WalletFramework.MdocLib.IssuerAuth; +using static WalletFramework.Core.Functional.ValidationFun; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; -public readonly struct IssuerSigned +public record IssuerSigned { public NameSpaces NameSpaces { get; init; } diff --git a/src/WalletFramework.Mdoc/IssuerSignedItem.cs b/src/WalletFramework.MdocLib/IssuerSignedItem.cs similarity index 87% rename from src/WalletFramework.Mdoc/IssuerSignedItem.cs rename to src/WalletFramework.MdocLib/IssuerSignedItem.cs index 205c6c1f..c28d9e7a 100644 --- a/src/WalletFramework.Mdoc/IssuerSignedItem.cs +++ b/src/WalletFramework.MdocLib/IssuerSignedItem.cs @@ -1,20 +1,22 @@ +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OneOf; using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; -using static WalletFramework.Mdoc.ElementArray; -using static WalletFramework.Mdoc.ElementMap; -using static WalletFramework.Mdoc.ElementIdentifier; -using static WalletFramework.Mdoc.ElementValue; -using static WalletFramework.Mdoc.CborByteString; -using static WalletFramework.Mdoc.DigestId; -using static WalletFramework.Mdoc.Random; -using static WalletFramework.Functional.ValidationFun; - -namespace WalletFramework.Mdoc; - -public readonly struct IssuerSignedItem +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json.Converters; +using WalletFramework.MdocLib.Common; +using static WalletFramework.MdocLib.ElementArray; +using static WalletFramework.MdocLib.ElementMap; +using static WalletFramework.MdocLib.ElementIdentifier; +using static WalletFramework.MdocLib.ElementValue; +using static WalletFramework.MdocLib.CborByteString; +using static WalletFramework.MdocLib.DigestId; +using static WalletFramework.MdocLib.Random; +using static WalletFramework.Core.Functional.ValidationFun; + +namespace WalletFramework.MdocLib; + +public record IssuerSignedItem { public CborByteString ByteString { get; } @@ -63,6 +65,7 @@ internal static Validation ValidIssuerSignedItem(CBORObject is }); } +[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct ElementIdentifier { public string Value { get; } @@ -71,6 +74,8 @@ public readonly struct ElementIdentifier public static implicit operator string(ElementIdentifier elementIdentifier) => elementIdentifier.Value; + public override string ToString() => Value; + internal static Validation ValidElementIdentifier(CBORObject cbor) { try @@ -134,7 +139,7 @@ public readonly struct ElementArray internal static Validation ValidElementArray(CBORObject cbor) => cbor.Values .Select(ValidElementValue) - .Traverse(value => value) + .TraverseAll(value => value) .OnSuccess(values => new ElementArray(values.ToList())); } diff --git a/src/WalletFramework.Mdoc/Mdoc.cs b/src/WalletFramework.MdocLib/Mdoc.cs similarity index 90% rename from src/WalletFramework.Mdoc/Mdoc.cs rename to src/WalletFramework.MdocLib/Mdoc.cs index 80efbb67..767e2dcb 100644 --- a/src/WalletFramework.Mdoc/Mdoc.cs +++ b/src/WalletFramework.MdocLib/Mdoc.cs @@ -2,16 +2,16 @@ using LanguageExt; using Microsoft.IdentityModel.Tokens; using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; -using static WalletFramework.Mdoc.DocType; -using static WalletFramework.Mdoc.IssuerSigned; -using static WalletFramework.Functional.ValidationFun; -using static WalletFramework.Mdoc.Common.Constants; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib.Common; +using static WalletFramework.MdocLib.DocType; +using static WalletFramework.MdocLib.IssuerSigned; +using static WalletFramework.Core.Functional.ValidationFun; +using static WalletFramework.MdocLib.Common.Constants; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; -public readonly struct Mdoc +public record Mdoc { public DocType DocType { get; } @@ -20,9 +20,7 @@ public readonly struct Mdoc // TODO: mdoc authentication // public DeviceSigned DeviceSigned { get; } - private Mdoc( - DocType docType, - IssuerSigned issuerSigned) + private Mdoc(DocType docType, IssuerSigned issuerSigned) { DocType = docType; IssuerSigned = issuerSigned; @@ -67,13 +65,9 @@ public static Validation ValidMdoc(string base64UrlencodedCborByteString) } .AggregateValidators(); - var validCbor = - from bytes in decodeBase64Url(base64UrlencodedCborByteString) - from cborObject in parseCborByteString(bytes) - select cborObject; - return - from cbor in validCbor + from bytes in decodeBase64Url(base64UrlencodedCborByteString) + from cbor in parseCborByteString(bytes) from mdoc in Valid(Create) .Apply(ValidDoctype(cbor)) .Apply(ValidIssuerSigned(cbor)) @@ -161,7 +155,7 @@ public static Validation DocTypeMatches(this Mdoc mdoc) var mdocDocType = mdoc.DocType; var msoDocType = mdoc.IssuerSigned.IssuerAuth.Payload.DocType; - if (mdocDocType.Value == msoDocType.Value) + if (mdocDocType.ToString() == msoDocType.ToString()) { return mdoc; } diff --git a/src/WalletFramework.Mdoc/MobileSecurityObject.cs b/src/WalletFramework.MdocLib/MobileSecurityObject.cs similarity index 66% rename from src/WalletFramework.Mdoc/MobileSecurityObject.cs rename to src/WalletFramework.MdocLib/MobileSecurityObject.cs index 7f83d6f5..a24aa49d 100644 --- a/src/WalletFramework.Mdoc/MobileSecurityObject.cs +++ b/src/WalletFramework.MdocLib/MobileSecurityObject.cs @@ -1,16 +1,16 @@ using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; -using static WalletFramework.Mdoc.DigestAlgorithm; -using static WalletFramework.Mdoc.DocType; -using static WalletFramework.Mdoc.ValidityInfo; -using static WalletFramework.Mdoc.CborByteString; -using static WalletFramework.Mdoc.ValueDigests; -using static WalletFramework.Functional.ValidationFun; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib.Common; +using static WalletFramework.MdocLib.DigestAlgorithm; +using static WalletFramework.MdocLib.DocType; +using static WalletFramework.MdocLib.ValidityInfo; +using static WalletFramework.MdocLib.CborByteString; +using static WalletFramework.MdocLib.ValueDigests; +using static WalletFramework.Core.Functional.ValidationFun; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; -public readonly struct MobileSecurityObject +public record MobileSecurityObject { public CborByteString ByteString { get; } @@ -53,16 +53,18 @@ private static MobileSecurityObject Create( new(byteString, version, digestAlgorithm, valueDigests, docType, validityInfo); internal static Validation ValidMobileSecurityObject(CBORObject issuerAuth) => - from mso in issuerAuth.GetByIndex(2) - from byteString in ValidCborByteString(mso) - let decoded = byteString.Decode() + from payloadEncoded in issuerAuth.GetByIndex(2) + from payloadEncodedByteString in ValidCborByteString(payloadEncoded) + let payloadDecoded = payloadEncodedByteString.Decode() + from taggedByteString in ValidCborByteString(payloadDecoded) + let mso = taggedByteString.Decode() from result in Valid(Create) - .Apply(byteString) - .Apply(ValidMsoVersion(decoded)) - .Apply(ValidDigestAlgorithm(decoded)) - .Apply(decoded.GetByLabel("valueDigests").OnSuccess(ValidValueDigests)) - .Apply(ValidDoctype(decoded)) - .Apply(ValidValidityInfo(decoded)) + .Apply(payloadEncodedByteString) + .Apply(ValidMsoVersion(mso)) + .Apply(ValidDigestAlgorithm(mso)) + .Apply(mso.GetByLabel("valueDigests").OnSuccess(ValidValueDigests)) + .Apply(ValidDoctype(mso)) + .Apply(ValidValidityInfo(mso)) select result; private static Validation ValidMsoVersion(CBORObject issuerAuth) => diff --git a/src/WalletFramework.Mdoc/NameSpace.cs b/src/WalletFramework.MdocLib/NameSpace.cs similarity index 75% rename from src/WalletFramework.Mdoc/NameSpace.cs rename to src/WalletFramework.MdocLib/NameSpace.cs index fd09f563..3ceeb3c4 100644 --- a/src/WalletFramework.Mdoc/NameSpace.cs +++ b/src/WalletFramework.MdocLib/NameSpace.cs @@ -1,15 +1,20 @@ +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json.Converters; +using WalletFramework.MdocLib.Common; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; +[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct NameSpace { public string Value { get; } private NameSpace(string value) => Value = value; + + public static implicit operator string(NameSpace nameSpace) => nameSpace.ToString(); internal static Validation ValidNameSpace(CBORObject nameSpace) { diff --git a/src/WalletFramework.Mdoc/NameSpaces.cs b/src/WalletFramework.MdocLib/NameSpaces.cs similarity index 85% rename from src/WalletFramework.Mdoc/NameSpaces.cs rename to src/WalletFramework.MdocLib/NameSpaces.cs index a8dc5829..2bf20f77 100644 --- a/src/WalletFramework.Mdoc/NameSpaces.cs +++ b/src/WalletFramework.MdocLib/NameSpaces.cs @@ -1,9 +1,9 @@ using PeterO.Cbor; -using WalletFramework.Functional; -using static WalletFramework.Mdoc.Common.Constants; -using static WalletFramework.Mdoc.NameSpace; +using WalletFramework.Core.Functional; +using static WalletFramework.MdocLib.Common.Constants; +using static WalletFramework.MdocLib.NameSpace; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; public readonly struct NameSpaces { @@ -24,8 +24,7 @@ internal static Validation ValidNameSpaces(CBORObject issuerSigned) .ToDictionary(ValidNameSpace, issuerSignedItems => issuerSignedItems .Values .Select(IssuerSignedItem.ValidIssuerSignedItem) - .Traverse(item => item) - ); + .TraverseAll(item => item)); return from dict in validDict diff --git a/src/WalletFramework.Mdoc/ProtectedHeaders.cs b/src/WalletFramework.MdocLib/ProtectedHeaders.cs similarity index 93% rename from src/WalletFramework.Mdoc/ProtectedHeaders.cs rename to src/WalletFramework.MdocLib/ProtectedHeaders.cs index 84c238f3..b1282611 100644 --- a/src/WalletFramework.Mdoc/ProtectedHeaders.cs +++ b/src/WalletFramework.MdocLib/ProtectedHeaders.cs @@ -1,10 +1,10 @@ using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; -using static WalletFramework.Mdoc.ProtectedHeaders.Alg; -using static WalletFramework.Mdoc.CborByteString; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib.Common; +using static WalletFramework.MdocLib.ProtectedHeaders.Alg; +using static WalletFramework.MdocLib.CborByteString; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; public readonly struct ProtectedHeaders { diff --git a/src/WalletFramework.Mdoc/UnprotectedHeaders.cs b/src/WalletFramework.MdocLib/UnprotectedHeaders.cs similarity index 85% rename from src/WalletFramework.Mdoc/UnprotectedHeaders.cs rename to src/WalletFramework.MdocLib/UnprotectedHeaders.cs index 0919ea47..e50433cb 100644 --- a/src/WalletFramework.Mdoc/UnprotectedHeaders.cs +++ b/src/WalletFramework.MdocLib/UnprotectedHeaders.cs @@ -1,12 +1,12 @@ using System.Security.Cryptography.X509Certificates; using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; -using static WalletFramework.Mdoc.CoseLabel; -using static WalletFramework.Functional.ValidationFun; -using static WalletFramework.Mdoc.Common.Constants; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib.Common; +using static WalletFramework.MdocLib.CoseLabel; +using static WalletFramework.Core.Functional.ValidationFun; +using static WalletFramework.MdocLib.Common.Constants; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; public readonly struct UnprotectedHeaders { @@ -14,14 +14,14 @@ public readonly struct UnprotectedHeaders public X509Chain X5Chain { get; } - public byte[] CertByteString { get; } + public CBORObject CertByteString { get; } public CBORObject this[CoseLabel key] => Value[key]; private UnprotectedHeaders( Dictionary value, X509Chain x5Chain, - byte[] certByteString) + CBORObject certByteString) { Value = value; X5Chain = x5Chain; @@ -67,7 +67,7 @@ internal static Validation ValidUnprotectedHeaders(CBORObjec from byteString in byteStringCbor.TryGetByteString() select new X509Certificate2(byteString) ) - .Traverse(cert => cert) + .TraverseAll(cert => cert) .OnSuccess(certs => { var chain = new X509Chain(); @@ -102,8 +102,7 @@ from dict in toDict(headersCbor) from label in ValidCoseLabel(CBORObject.FromObject(CertificateIndex)) from chainCbor in getChainCbor(dict, label) from chain in decodeChain(chainCbor) - from chainBytes in chainCbor.TryGetByteString() - select new UnprotectedHeaders(dict, chain, chainBytes); + select new UnprotectedHeaders(dict, chain, chainCbor); } public record InvalidX509CertificateError(string Value, Exception E) @@ -112,7 +111,7 @@ public record InvalidX509CertificateError(string Value, Exception E) public CBORObject Encode() { var cbor = CBORObject.NewMap(); - cbor[CertificateIndex] = CBORObject.FromObject(CertByteString); + cbor[CertificateIndex] = CertByteString; return cbor; } } diff --git a/src/WalletFramework.Mdoc/ValidityInfo.cs b/src/WalletFramework.MdocLib/ValidityInfo.cs similarity index 93% rename from src/WalletFramework.Mdoc/ValidityInfo.cs rename to src/WalletFramework.MdocLib/ValidityInfo.cs index 01463f38..2d32f134 100644 --- a/src/WalletFramework.Mdoc/ValidityInfo.cs +++ b/src/WalletFramework.MdocLib/ValidityInfo.cs @@ -1,10 +1,10 @@ using LanguageExt; using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; -using static WalletFramework.Functional.ValidationFun; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib.Common; +using static WalletFramework.Core.Functional.ValidationFun; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; public struct ValidityInfo { diff --git a/src/WalletFramework.Mdoc/ValueDigests.cs b/src/WalletFramework.MdocLib/ValueDigests.cs similarity index 76% rename from src/WalletFramework.Mdoc/ValueDigests.cs rename to src/WalletFramework.MdocLib/ValueDigests.cs index 5db1c069..a129e0d7 100644 --- a/src/WalletFramework.Mdoc/ValueDigests.cs +++ b/src/WalletFramework.MdocLib/ValueDigests.cs @@ -1,10 +1,10 @@ using PeterO.Cbor; -using WalletFramework.Functional; -using static WalletFramework.Mdoc.Digest; -using static WalletFramework.Mdoc.DigestId; -using static WalletFramework.Mdoc.NameSpace; +using WalletFramework.Core.Functional; +using static WalletFramework.MdocLib.Digest; +using static WalletFramework.MdocLib.DigestId; +using static WalletFramework.MdocLib.NameSpace; -namespace WalletFramework.Mdoc; +namespace WalletFramework.MdocLib; public readonly struct ValueDigests { diff --git a/src/WalletFramework.Mdoc/WalletFramework.Mdoc.csproj b/src/WalletFramework.MdocLib/WalletFramework.MdocLib.csproj similarity index 71% rename from src/WalletFramework.Mdoc/WalletFramework.Mdoc.csproj rename to src/WalletFramework.MdocLib/WalletFramework.MdocLib.csproj index 9cdea1b7..2f074371 100644 --- a/src/WalletFramework.Mdoc/WalletFramework.Mdoc.csproj +++ b/src/WalletFramework.MdocLib/WalletFramework.MdocLib.csproj @@ -4,21 +4,21 @@ netstandard2.1 enable enable + WalletFramework.MdocLib - <_Parameter1>WalletFramework.Mdoc.Tests + <_Parameter1>WalletFramework.MdocLib.Tests - - + diff --git a/src/WalletFramework.MdocVc/ClaimDisplay.cs b/src/WalletFramework.MdocVc/ClaimDisplay.cs new file mode 100644 index 00000000..c63f6830 --- /dev/null +++ b/src/WalletFramework.MdocVc/ClaimDisplay.cs @@ -0,0 +1,20 @@ +using LanguageExt; +using Newtonsoft.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Core.Localization; + +namespace WalletFramework.MdocVc; + +public record ClaimDisplay( + [property: JsonProperty(ClaimDisplayJsonKeys.ClaimName)] + [property: JsonConverter(typeof(OptionJsonConverter))] + Option Name, + [property: JsonProperty(ClaimDisplayJsonKeys.Locale)] + [property: JsonConverter(typeof(OptionJsonConverter))] + Option Locale); + +public static class ClaimDisplayJsonKeys +{ + public const string ClaimName = "name"; + public const string Locale = "locale"; +} diff --git a/src/WalletFramework.MdocVc/ClaimName.cs b/src/WalletFramework.MdocVc/ClaimName.cs new file mode 100644 index 00000000..b83096d9 --- /dev/null +++ b/src/WalletFramework.MdocVc/ClaimName.cs @@ -0,0 +1,25 @@ +using LanguageExt; +using Newtonsoft.Json; +using WalletFramework.Core.Json.Converters; + +namespace WalletFramework.MdocVc; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct ClaimName +{ + private string Value { get; } + + private ClaimName(string value) => Value = value; + + public override string ToString() => Value; + + public static implicit operator string(ClaimName name) => name.Value; + + public static Option OptionClaimName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + return Option.None; + + return new ClaimName(name); + } +} diff --git a/src/WalletFramework.MdocVc/Common/Errors.cs b/src/WalletFramework.MdocVc/Common/Errors.cs new file mode 100644 index 00000000..fe622c5c --- /dev/null +++ b/src/WalletFramework.MdocVc/Common/Errors.cs @@ -0,0 +1,6 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.MdocVc.Common; + +public record OneTimeUseIsNotABooleanValueError(string Actual, Exception E) + : Error($"The field 'one_time_use' is not a boolean value. Actual value is: `{Actual}`", E); diff --git a/src/WalletFramework.Functional/IsExternalInit.cs b/src/WalletFramework.MdocVc/IsExternalInit.cs similarity index 100% rename from src/WalletFramework.Functional/IsExternalInit.cs rename to src/WalletFramework.MdocVc/IsExternalInit.cs diff --git a/src/WalletFramework.MdocVc/MdocDisplay.cs b/src/WalletFramework.MdocVc/MdocDisplay.cs new file mode 100644 index 00000000..63b97ff6 --- /dev/null +++ b/src/WalletFramework.MdocVc/MdocDisplay.cs @@ -0,0 +1,202 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Colors; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Core.Localization; +using WalletFramework.MdocLib; + +namespace WalletFramework.MdocVc; + +public record MdocDisplay( + [property: JsonProperty(MdocDisplayJsonKeys.Logo)] + [property: JsonConverter(typeof(OptionJsonConverter))] + Option Logo, + [property: JsonProperty(MdocDisplayJsonKeys.Name)] + [property: JsonConverter(typeof(OptionJsonConverter))] + Option Name, + [property: JsonProperty(MdocDisplayJsonKeys.BackgroundColor)] + [property: JsonConverter(typeof(OptionJsonConverter))] + Option BackgroundColor, + [property: JsonProperty(MdocDisplayJsonKeys.TextColor)] + [property: JsonConverter(typeof(OptionJsonConverter))] + Option TextColor, + [property: JsonProperty(MdocDisplayJsonKeys.Locale)] + [property: JsonConverter(typeof(OptionJsonConverter))] + Option Locale, + [property: JsonProperty(MdocDisplayJsonKeys.ClaimsDisplays)] + [property: JsonConverter(typeof(OptionJsonConverter>>>))] + Option>>> ClaimsDisplays); + +public static class MdocDisplayJsonKeys +{ + public const string Logo = "logo"; + public const string Name = "name"; + public const string BackgroundColor = "background_color"; + public const string TextColor = "text_color"; + public const string Locale = "locale"; + public const string ClaimsDisplays = "claims_displays"; +} + +public static class MdocDisplayFun +{ + public static Option GetByLocale(this List displays, Locale locale) + { + var dict = new Dictionary(); + foreach (var display in displays) + { + display.Locale.Match( + displayLocale => + { + dict.Add(displayLocale, display); + }, + () => + { + if (!dict.Keys.Contains(Constants.DefaultLocale)) + { + dict.Add(Constants.DefaultLocale, display); + } + } + ); + } + + if (dict.Any()) + { + return dict.FindOrDefault(locale); + } + else + { + return Option.None; + } + } + + public static Option> DecodeFromJson(JArray array) + { + var result = array.TraverseAny(token => + from jObject in token.ToJObject() + select DecodeFromJson(jObject) + ).ToOption(); + + return + from displays in result + select displays.ToList(); + } + + private static MdocDisplay DecodeFromJson(JObject display) + { + var logo = + from jToken in display.GetByKey(MdocDisplayJsonKeys.Logo).ToOption() + let uri = new Uri(jToken.ToString()) + select new MdocLogo(uri); + + var mdocName = + from jToken in display.GetByKey(MdocDisplayJsonKeys.Name).ToOption() + from name in MdocName.OptionMdocName(jToken.ToString()) + select name; + + var backgroundColor = + from jToken in display.GetByKey(MdocDisplayJsonKeys.BackgroundColor).ToOption() + from color in Color.OptionColor(jToken.ToString()) + select color; + + var textColor = + from jToken in display.GetByKey(MdocDisplayJsonKeys.TextColor).ToOption() + from color in Color.OptionColor(jToken.ToString()) + select color; + + var locale = + from jToken in display.GetByKey(MdocDisplayJsonKeys.Locale).ToOption() + from l in Locale.OptionLocale(jToken.ToString()) + select l; + + var claimsDisplays = + from jToken in display.GetByKey(MdocDisplayJsonKeys.ClaimsDisplays).ToOption() + from claimsJson in jToken.ToJObject().ToOption() + from displays in DecodeClaimsDisplaysFromJson(claimsJson) + select displays; + + return new MdocDisplay(logo, mdocName, backgroundColor, textColor, locale, claimsDisplays); + } + + private static Option>>> + DecodeClaimsDisplaysFromJson(JObject namespaceDict) + { + var result = new Dictionary>>(); + + var tuples = namespaceDict.Properties().Select(prop => + { + var claimsDict = + from jObject in prop.Value.ToJObject().ToOption() + from claimsDisplays in DecodeClaimsDisplaysDictFromJson(jObject) + select claimsDisplays; + + return ( + NameSpace: NameSpace.ValidNameSpace(prop.Name).ToOption(), + ClaimsDict: claimsDict + ); + }); + + foreach (var (nameSpace, claimsDict) in tuples) + { + nameSpace.OnSome(space => claimsDict.OnSome(dictionary => + { + result.Add(space, dictionary); + return Unit.Default; + })); + } + + return result.Any() + ? result + : Option>>>.None; + } + + private static Option>> + DecodeClaimsDisplaysDictFromJson(JObject json) + { + var result = new Dictionary>(); + + var tuples = json.Properties().Select(prop => + { + var displays = + from jArray in prop.Value.ToJArray().ToOption() + from claimDisplays in jArray.TraverseAny(token => + { + var optionName = + from jToken in token.GetByKey(ClaimDisplayJsonKeys.ClaimName).ToOption() + from name in ClaimName.OptionClaimName(jToken.ToString()) + select name; + + var optionLocale = + from jToken in token.GetByKey(ClaimDisplayJsonKeys.Locale).ToOption() + from locale in Locale.OptionLocale(jToken.ToString()) + select locale; + + return + from name in optionName + from locale in optionLocale + select new ClaimDisplay(name, locale); + }) + select claimDisplays.ToList(); + + return ( + Id: ElementIdentifier.ValidElementIdentifier(prop.Name).ToOption(), + Displays: displays + ); + }); + + foreach (var (elementId, claimDisplays) in tuples) + { + elementId.OnSome(id => claimDisplays.OnSome(displays => + { + result.Add(id, displays); + return Unit.Default; + })); + } + + return result.Any() + ? result + : Option>>.None; + } +} diff --git a/src/WalletFramework.MdocVc/MdocLogo.cs b/src/WalletFramework.MdocVc/MdocLogo.cs new file mode 100644 index 00000000..e17cb727 --- /dev/null +++ b/src/WalletFramework.MdocVc/MdocLogo.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Core.Uri; + +namespace WalletFramework.MdocVc; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct MdocLogo +{ + public MdocLogo(Uri value) + { + Value = value; + } + + private Uri Value { get; } + + public override string ToString() => Value.ToStringWithoutTrail(); + + public static implicit operator string(MdocLogo logo) => logo.ToString(); +} diff --git a/src/WalletFramework.MdocVc/MdocName.cs b/src/WalletFramework.MdocVc/MdocName.cs new file mode 100644 index 00000000..f11ca172 --- /dev/null +++ b/src/WalletFramework.MdocVc/MdocName.cs @@ -0,0 +1,25 @@ +using LanguageExt; +using Newtonsoft.Json; +using WalletFramework.Core.Json.Converters; + +namespace WalletFramework.MdocVc; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct MdocName +{ + private string Value { get; } + + private MdocName(string value) => Value = value; + + public override string ToString() => Value; + + public static implicit operator string(MdocName mdocName) => mdocName.Value; + + public static Option OptionMdocName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + return Option.None; + + return new MdocName(name); + } +} diff --git a/src/WalletFramework.MdocVc/MdocRecord.cs b/src/WalletFramework.MdocVc/MdocRecord.cs new file mode 100644 index 00000000..0a24c47e --- /dev/null +++ b/src/WalletFramework.MdocVc/MdocRecord.cs @@ -0,0 +1,123 @@ +using Hyperledger.Aries.Storage; +using Hyperledger.Aries.Storage.Models; +using Hyperledger.Aries.Storage.Models.Interfaces; +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Credentials; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.MdocLib; + +namespace WalletFramework.MdocVc; + +[JsonConverter(typeof(MdocRecordJsonConverter))] +public sealed class MdocRecord : RecordBase, ICredential +{ + public CredentialId CredentialId + { + get => CredentialId + .ValidCredentialId(Id) + .UnwrapOrThrow(new InvalidOperationException("The Id is corrupt")); + private set => Id = value; + } + + public Mdoc Mdoc { get; } + + [RecordTag] + public DocType DocType => Mdoc.DocType; + + public Option> Displays { get; } + + public override string TypeName => "WF.MdocRecord"; + + public MdocRecord(Mdoc mdoc, Option> displays) + { + CredentialId = CredentialId.CreateCredentialId(); + Mdoc = mdoc; + Displays = displays; + } + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + public MdocRecord() + { + } +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + + public static implicit operator Mdoc(MdocRecord record) => record.Mdoc; +} + +public static class MdocRecordJsonKeys +{ + public const string MdocJsonKey = "mdoc"; + public const string MdocDisplaysKey = "displays"; +} + +public static class MdocRecordFun +{ + public static MdocRecord DecodeFromJson(JObject json) + { + var id = json[nameof(RecordBase.Id)]!.ToString(); + + var mdocStr = json[MdocRecordJsonKeys.MdocJsonKey]!.ToString(); + var mdoc = Mdoc + .ValidMdoc(mdocStr) + .UnwrapOrThrow(new InvalidOperationException($"The MdocRecord with ID: {id} is corrupt")); + + var displays = + from jToken in json.GetByKey(MdocRecordJsonKeys.MdocDisplaysKey).ToOption() + from jArray in jToken.ToJArray().ToOption() + from mdocDisplays in MdocDisplayFun.DecodeFromJson(jArray) + select mdocDisplays; + + var result = new MdocRecord(mdoc, displays) + { + Id = id + }; + + return result; + } + + public static MdocRecord ToRecord(this Mdoc mdoc, Option> displays) => new(mdoc, displays); +} + +public sealed class MdocRecordJsonConverter : JsonConverter +{ + public override void WriteJson(JsonWriter writer, MdocRecord? record, JsonSerializer serializer) + { + writer.WriteStartObject(); + + writer.WritePropertyName(nameof(RecordBase.Id)); + writer.WriteValue(record!.Id); + + writer.WritePropertyName(MdocRecordJsonKeys.MdocJsonKey); + writer.WriteValue(record.Mdoc.Encode()); + + writer.WritePropertyName(MdocRecordJsonKeys.MdocDisplaysKey); + record.Displays.Match( + list => + { + writer.WriteStartArray(); + foreach (var display in list) + { + serializer.Serialize(writer, display); + } + writer.WriteEndArray(); + }, + () => {} + ); + + writer.WriteEndObject(); + } + + public override MdocRecord ReadJson( + JsonReader reader, + Type objectType, + MdocRecord? existingValue, + bool hasExistingValue, + JsonSerializer serializer) + { + var json = JObject.Load(reader); + return MdocRecordFun.DecodeFromJson(json); + } +} diff --git a/src/WalletFramework.Functional/WalletFramework.Functional.csproj b/src/WalletFramework.MdocVc/WalletFramework.MdocVc.csproj similarity index 59% rename from src/WalletFramework.Functional/WalletFramework.Functional.csproj rename to src/WalletFramework.MdocVc/WalletFramework.MdocVc.csproj index 7c5bdb54..d2bdd42a 100644 --- a/src/WalletFramework.Functional/WalletFramework.Functional.csproj +++ b/src/WalletFramework.MdocVc/WalletFramework.MdocVc.csproj @@ -1,5 +1,4 @@ - netstandard2.1 enable @@ -7,7 +6,7 @@ - - + + diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Abstractions/IMdocStorage.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Abstractions/IMdocStorage.cs new file mode 100644 index 00000000..436f38ff --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Abstractions/IMdocStorage.cs @@ -0,0 +1,22 @@ +using Hyperledger.Aries.Storage; +using LanguageExt; +using WalletFramework.Core.Credentials; +using WalletFramework.MdocVc; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Abstractions; + +public interface IMdocStorage +{ + public Task Add(MdocRecord record); + + public Task> Get(CredentialId credentialId); + + public Task>> List( + Option query, + int count = 100, + int skip = 0); + + public Task Update(MdocRecord record); + + public Task Delete(MdocRecord record); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Abstractions/IOid4VciClientService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Abstractions/IOid4VciClientService.cs new file mode 100644 index 00000000..8865cad2 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Abstractions/IOid4VciClientService.cs @@ -0,0 +1,50 @@ +using LanguageExt; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; +using OneOf; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Localization; +using WalletFramework.MdocVc; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; +using WalletFramework.SdJwtVc.Models.Records; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Abstractions; + +/// +/// Provides an interface for services related to OpenID for Verifiable Credential Issuance. +/// +public interface IOid4VciClientService +{ + /// + /// Initiates the authorization process of the VCI authorization code flow. + /// + /// The offer metadata + /// The client options + /// + Task InitiateAuthFlow(CredentialOfferMetadata offer, ClientOptions clientOptions); + + /// + /// Requests a verifiable credential using the authorization code flow. + /// + /// Holds authorization session relevant information. + /// + /// A list of credentials. + /// + Task>> RequestCredential(IssuanceSession issuanceSession); + + /// + /// Processes a credential offer + /// + /// The credential offer uri + /// Optional language tag + Task> ProcessOffer(Uri credentialOffer, Option language); + + /// + /// Requests a verifiable credential using the pre-authorized code flow. + /// + /// /// + /// Credential offer and Issuer Metadata + /// The Transaction Code. + Task>> AcceptOffer( + CredentialOfferMetadata credentialOfferMetadata, + string? transactionCode); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Abstractions/IAuthFlowSessionStorage.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Abstractions/IAuthFlowSessionStorage.cs new file mode 100644 index 00000000..835886e1 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Abstractions/IAuthFlowSessionStorage.cs @@ -0,0 +1,45 @@ +using Hyperledger.Aries.Agents; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Records; + +namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Abstractions; + +/// +/// Service for managing authorization records. They are used during the VCI Authorization Code Flow to hold session +/// relevant inforation. +/// +public interface IAuthFlowSessionStorage +{ + /// + /// Deletes the authorization session record by the session identifier. + /// + /// Agent Context + /// Session Identifier of a Authorization Code Flow session + /// + Task DeleteAsync(IAgentContext context, VciSessionId sessionId); + + /// + /// Retrieves the authorization session record by the session identifier. + /// + /// Agent Context + /// Session Identifier of a Authorization Code Flow session + /// + Task GetAsync(IAgentContext context, VciSessionId sessionId); + + /// + /// Stores the authorization session record. + /// + /// The Agent Context + /// Options specified by the Client (Wallet) + /// + /// Parameters required for the authorization during the VCI authorization code + /// flow. + /// + /// + /// + Task StoreAsync( + IAgentContext agentContext, + AuthorizationData authorizationData, + AuthorizationCodeParameters authorizationCodeParameters, + VciSessionId sessionId); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Errors/VciSessionIdError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Errors/VciSessionIdError.cs new file mode 100644 index 00000000..589f75e4 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Errors/VciSessionIdError.cs @@ -0,0 +1,5 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Errors; + +public record VciSessionIdError(string Value) : Error($"Invalid VciSessionId: {Value}"); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Implementations/AuthFlowSessionStorage.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Implementations/AuthFlowSessionStorage.cs new file mode 100644 index 00000000..ca00ebf8 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Implementations/AuthFlowSessionStorage.cs @@ -0,0 +1,52 @@ +using Hyperledger.Aries.Agents; +using Hyperledger.Aries.Storage; +using LanguageExt; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Records; +using WalletFramework.Oid4Vc.Oid4Vp.Services; + +namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Implementations; + +/// +public class AuthFlowSessionStorage : IAuthFlowSessionStorage +{ + /// + /// Initializes a new instance of the class. + /// + /// The service responsible for wallet record operations. + public AuthFlowSessionStorage(IWalletRecordService recordService) + { + _recordService = recordService; + } + + private readonly IWalletRecordService _recordService; + + /// + public async Task StoreAsync( + IAgentContext agentContext, + AuthorizationData authorizationData, + AuthorizationCodeParameters authorizationCodeParameters, + VciSessionId sessionId) + { + var record = new AuthFlowSessionRecord( + authorizationData, + authorizationCodeParameters, + sessionId); + + await _recordService.AddAsync(agentContext.Wallet, record); + + return record.Id; + } + + /// + public async Task GetAsync(IAgentContext context, VciSessionId sessionId) + { + var record = await _recordService.GetAsync(context.Wallet, sessionId); + return record!; + } + + /// + public async Task DeleteAsync(IAgentContext context, VciSessionId sessionId) => + await _recordService.DeleteAsync(context.Wallet, sessionId); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationCodeParameters.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationCodeParameters.cs new file mode 100644 index 00000000..5cc1a381 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationCodeParameters.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json; + +namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; + +/// +/// Represents the parameters required for the authorization during the VCI authorization code flow. +/// The code itself will be part of the Client redirect uri that is created by the authorization server upon +/// successful authorization. +/// +public record AuthorizationCodeParameters +{ + /// + /// Gets the code challenge. + /// + public string Challenge { get; } + + /// + /// Gets the code challenge method. SHA-256 is the only supported method. + /// + public string CodeChallengeMethod => "S256"; + + /// + /// Gets the code verifier. + /// + public string Verifier { get; } + + [JsonConstructor] + internal AuthorizationCodeParameters(string challenge, string verifier) + { + if (string.IsNullOrWhiteSpace(challenge) || string.IsNullOrWhiteSpace(verifier)) + { + throw new ArgumentException("Authorization Code Parameters cannot be null or empty."); + } + + Challenge = challenge; + Verifier = verifier; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationData.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationData.cs new file mode 100644 index 00000000..86017ddb --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationData.cs @@ -0,0 +1,11 @@ +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; + +public record AuthorizationData( + ClientOptions ClientOptions, + IssuerMetadata IssuerMetadata, + AuthorizationServerMetadata AuthorizationServerMetadata, + List CredentialConfigurationIds); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationDetails.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationDetails.cs new file mode 100644 index 00000000..6e728596 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationDetails.cs @@ -0,0 +1,58 @@ +using Newtonsoft.Json; + +namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; + +/// +/// Represents the authorization details. +/// +public record AuthorizationDetails +{ + /// + /// Gets the type of the credential. + /// + [JsonProperty("type")] + public string Type { get; } = "openid_credential"; + + /// + /// Gets the format of the credential. + /// + [JsonProperty("format", NullValueHandling = NullValueHandling.Ignore)] + public string? Format { get; } + + /// + /// Gets the verifiable credential type (vct). + /// + [JsonProperty("vct", NullValueHandling = NullValueHandling.Ignore)] + public string? Vct { get; } + + [JsonProperty("doctype", NullValueHandling = NullValueHandling.Ignore)] + public string? DocType { get; } + + /// + /// Gets the credential configuration id. + /// + [JsonProperty("credential_configuration_id", NullValueHandling = NullValueHandling.Ignore)] + public string? CredentialConfigurationId { get; } + + [JsonProperty("locations", NullValueHandling = NullValueHandling.Ignore)] + public string[]? Locations { get; } + + internal AuthorizationDetails( + string? format, + string? vct, + string? credentialConfigurationId, + string[]? locations, + string? docType) + { + if (!string.IsNullOrWhiteSpace(format) && !string.IsNullOrWhiteSpace(credentialConfigurationId)) + { + throw new ArgumentException("Both format and credentialConfigurationId cannot be present at the same time."); + } + + Format = format; + Vct = vct; + CredentialConfigurationId = credentialConfigurationId; + Locations = locations; + DocType = docType; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/ClientOptions.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/ClientOptions.cs new file mode 100644 index 00000000..fa2afa87 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/ClientOptions.cs @@ -0,0 +1,54 @@ +using Newtonsoft.Json; + +namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; + +/// +/// Represents the client options that are used during the VCI Authorization Code Flow. Here the wallet acts as the client. +/// +public record ClientOptions +{ + /// + /// Identifier of the client (wallet) + /// + public string ClientId { get; init; } + + /// + /// Identifier of the wallet issuer + /// + public string WalletIssuer { get; init; } + + /// + /// Redirect URI that the Authorization Server will use after the authorization was successful. + /// + public string RedirectUri { get; init; } + +#pragma warning disable CS8618 + /// + /// Parameterless Default Constructor. + /// + public ClientOptions() + { + } +#pragma warning restore CS8618 + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + [JsonConstructor] + public ClientOptions(string clientId, string walletIssuer, string redirectUri) + { + if (string.IsNullOrWhiteSpace(clientId) + || string.IsNullOrWhiteSpace(walletIssuer) + || !Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute)) + { + throw new ArgumentException("Invalid Client Options"); + } + + ClientId = clientId; + WalletIssuer = walletIssuer; + RedirectUri = redirectUri; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/IssuanceSession.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/IssuanceSession.cs new file mode 100644 index 00000000..f40ce17f --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/IssuanceSession.cs @@ -0,0 +1,44 @@ +using WalletFramework.Core.Functional; +using static System.Web.HttpUtility; + +namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; + +/// +/// Represents the parameters of an VCI Authorization Code Flow issuance session. +/// +public record IssuanceSession +{ + /// + /// Gets the session identifier. + /// + public VciSessionId SessionId { get; } + + /// + /// Gets the actual authorization code that is received from the authorization server upon successful authorization. + /// + public string Code { get; } + + private IssuanceSession(VciSessionId sessionId, string code) => (SessionId, Code) = (sessionId, code); + + /// + /// Creates a new instance of from the given . + /// + /// + /// + /// + public static IssuanceSession FromUri(Uri uri) + { + var queryParams = ParseQueryString(uri.Query); + + var code = queryParams.Get("code"); + if (string.IsNullOrWhiteSpace(code)) + { + throw new InvalidOperationException("Query parameter 'code' is missing"); + } + + var sessionIdParam = queryParams.Get("session"); + var sessionId = VciSessionId.ValidSessionId(sessionIdParam).Fallback(VciSessionId.CreateSessionId()); + + return new IssuanceSession(sessionId, code); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/PushedAuthorizationRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/PushedAuthorizationRequest.cs new file mode 100644 index 00000000..7432187a --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/PushedAuthorizationRequest.cs @@ -0,0 +1,99 @@ +using Newtonsoft.Json; +using static Newtonsoft.Json.JsonConvert; + +namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; + +internal record PushedAuthorizationRequest +{ + [JsonProperty("client_id")] + public string ClientId { get; } + + [JsonProperty("response_type")] + public string ResponseType { get; } = "code"; + + [JsonProperty("redirect_uri")] + public string RedirectUri { get; } + + [JsonProperty("code_challenge")] + public string CodeChallenge { get; } + + [JsonProperty("code_challenge_method")] + public string CodeChallengeMethod { get; } + + [JsonProperty("authorization_details", NullValueHandling = NullValueHandling.Ignore)] + public AuthorizationDetails[]? AuthorizationDetails { get; } + + [JsonProperty("issuer_state", NullValueHandling = NullValueHandling.Ignore)] + public string? IssuerState { get; } + + [JsonProperty("wallet_issuer", NullValueHandling = NullValueHandling.Ignore)] + public string? WalletIssuer { get; } + + [JsonProperty("user_hint", NullValueHandling = NullValueHandling.Ignore)] + public string? UserHint { get; } + + [JsonProperty("scope", NullValueHandling = NullValueHandling.Ignore)] + public string? Scope { get; } + + [JsonProperty("resource", NullValueHandling = NullValueHandling.Ignore)] + public string? Resource { get; } + + public PushedAuthorizationRequest( + VciSessionId sessionId, + ClientOptions clientOptions, + AuthorizationCodeParameters authorizationCodeParameters, + AuthorizationDetails[]? authorizationDetails, + string? scope, + string? issuerState, + string? userHint, + string? resource) + { + ClientId = clientOptions.ClientId; + RedirectUri = clientOptions.RedirectUri + "?session=" + sessionId; + WalletIssuer = clientOptions.WalletIssuer; + CodeChallenge = authorizationCodeParameters.Challenge; + CodeChallengeMethod = authorizationCodeParameters.CodeChallengeMethod; + AuthorizationDetails = authorizationDetails; + IssuerState = issuerState; + UserHint = userHint; + Scope = scope; + Resource = resource; + } + + public FormUrlEncodedContent ToFormUrlEncoded() + { + var keyValuePairs = new List>(); + + if (!string.IsNullOrEmpty(ClientId)) + keyValuePairs.Add(new KeyValuePair("client_id", ClientId)); + + if (!string.IsNullOrEmpty(ResponseType)) + keyValuePairs.Add(new KeyValuePair("response_type", ResponseType)); + + if (!string.IsNullOrEmpty(RedirectUri)) + keyValuePairs.Add(new KeyValuePair("redirect_uri", RedirectUri)); + + if (!string.IsNullOrEmpty(CodeChallenge)) + keyValuePairs.Add(new KeyValuePair("code_challenge", CodeChallenge)); + + if (!string.IsNullOrEmpty(CodeChallengeMethod)) + keyValuePairs.Add(new KeyValuePair("code_challenge_method", CodeChallengeMethod)); + + if (AuthorizationDetails != null) + keyValuePairs.Add(new KeyValuePair("authorization_details", SerializeObject(AuthorizationDetails))); + + if (!string.IsNullOrEmpty(IssuerState)) + keyValuePairs.Add(new KeyValuePair("issuer_state", IssuerState)); + + if (!string.IsNullOrEmpty(WalletIssuer)) + keyValuePairs.Add(new KeyValuePair("wallet_issuer", WalletIssuer)); + + if (!string.IsNullOrEmpty(UserHint)) + keyValuePairs.Add(new KeyValuePair("user_hint", UserHint)); + + if (!string.IsNullOrEmpty(Scope)) + keyValuePairs.Add(new KeyValuePair("scope", Scope)); + + return new FormUrlEncodedContent(keyValuePairs); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/PushedAuthorizationRequestResponse.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/PushedAuthorizationRequestResponse.cs new file mode 100644 index 00000000..ffa88091 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/PushedAuthorizationRequestResponse.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; + +internal record PushedAuthorizationRequestResponse +{ + [JsonProperty("request_uri")] + public Uri RequestUri { get; init; } + + [JsonProperty("expires_in")] + public string ExpiresIn { get; init; } + + [JsonConstructor] + private PushedAuthorizationRequestResponse(Uri requestUri, string expiresIn) + => (RequestUri, ExpiresIn) = (requestUri, expiresIn); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/VciSessionId.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/VciSessionId.cs new file mode 100644 index 00000000..452dad1c --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/VciSessionId.cs @@ -0,0 +1,49 @@ +using System.Globalization; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Errors; + +namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; + +/// +/// Identifier of the authorization session during the VCI Authorization Code Flow. +/// +public struct VciSessionId +{ + /// + /// Gets the value of the session identifier. + /// + private string Value { get; } + + private VciSessionId(string value) => Value = value; + + /// + /// Returns the value of the session identifier. + /// + /// + /// + public static implicit operator string(VciSessionId sessionId) => sessionId.Value; + + public static Validation ValidSessionId(string sessionId) + { + if (!Guid.TryParse(sessionId, out _)) + { + return new VciSessionIdError(sessionId); + } + + return new VciSessionId(sessionId); + } + + public static VciSessionId CreateSessionId() + { + var guid = Guid.NewGuid().ToString(); + return new VciSessionId(guid); + } +} + +public static class VciSessionIdFun +{ + public static VciSessionId DecodeFromJson(JValue json) => VciSessionId + .ValidSessionId(json.ToString(CultureInfo.InvariantCulture)) + .UnwrapOrThrow(new InvalidOperationException("SessionId is corrupt")); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Records/AuthFlowSessionRecord.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Records/AuthFlowSessionRecord.cs new file mode 100644 index 00000000..6c5c62f3 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Records/AuthFlowSessionRecord.cs @@ -0,0 +1,103 @@ +using Hyperledger.Aries.Storage; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Records; + +/// +/// Represents the authorization session record. Used during the VCI Authorization Code Flow to hold session relevant information. +/// +[JsonConverter(typeof(AuthFlowSessionRecordJsonConverter))] +public sealed class AuthFlowSessionRecord : RecordBase +{ + /// + /// The session specific id. + /// + [JsonIgnore] + public VciSessionId SessionId + { + get => VciSessionId + .ValidSessionId(Id) + .UnwrapOrThrow(new InvalidOperationException("SessionId is corrupt")); + set + { + string str = value; + Id = str; + } + } + + /// + /// The authorization data. + /// + public AuthorizationData AuthorizationData { get; } + + /// + /// The parameters for the 'authorization_code' grant type. + /// + public AuthorizationCodeParameters AuthorizationCodeParameters { get; } + + /// + /// Initializes a new instance of the class. + /// + public override string TypeName => "AF.VciAuthorizationSessionRecord"; + +#pragma warning disable CS8618 + /// + /// Initializes a new instance of the class. + /// + public AuthFlowSessionRecord() + { + } +#pragma warning restore CS8618 + + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// + public AuthFlowSessionRecord( + AuthorizationData authorizationData, + AuthorizationCodeParameters authorizationCodeParameters, + VciSessionId sessionId) + { + SessionId = sessionId; + RecordVersion = 1; + AuthorizationCodeParameters = authorizationCodeParameters; + AuthorizationData = authorizationData; + } +} + +public sealed class AuthFlowSessionRecordJsonConverter : JsonConverter +{ + public override bool CanWrite => false; + + public override void WriteJson(JsonWriter writer, AuthFlowSessionRecord? record, JsonSerializer serializer) + => throw new NotImplementedException(); + + public override AuthFlowSessionRecord ReadJson( + JsonReader reader, + Type objectType, + AuthFlowSessionRecord? existingValue, + bool hasExistingValue, + JsonSerializer serializer) + { + var json = JObject.Load(reader); + + var id = VciSessionIdFun.DecodeFromJson(json[nameof(RecordBase.Id)]!.ToObject()!); + + var authCodeParameters = JsonConvert.DeserializeObject( + json[nameof(AuthorizationCodeParameters)]!.ToString() + ); + + var authorizationData = JsonConvert.DeserializeObject( + json[nameof(AuthorizationData)]!.ToString() + )!; + + var result = new AuthFlowSessionRecord(authorizationData, authCodeParameters!, id); + + return result; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Abstractions/ITokenService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Abstractions/ITokenService.cs new file mode 100644 index 00000000..b1f0bc26 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Abstractions/ITokenService.cs @@ -0,0 +1,12 @@ +using OneOf; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.Abstractions; + +public interface ITokenService +{ + public Task> RequestToken( + TokenRequest tokenRequest, + AuthorizationServerMetadata metadata); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Abstractions/IDPopHttpClient.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Abstractions/IDPopHttpClient.cs new file mode 100644 index 00000000..901fafc8 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Abstractions/IDPopHttpClient.cs @@ -0,0 +1,11 @@ +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Abstractions; + +public interface IDPopHttpClient +{ + internal Task Post( + Uri requestUri, + HttpContent content, + DPopConfig config); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Implementations/DPopHttpClient.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Implementations/DPopHttpClient.cs new file mode 100644 index 00000000..6e747ce9 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Implementations/DPopHttpClient.cs @@ -0,0 +1,111 @@ +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Cryptography.Abstractions; +using WalletFramework.Core.Functional; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Exceptions; +using WalletFramework.Oid4Vc.Oid4Vci.Extensions; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Implementations; + +public class DPopHttpClient : IDPopHttpClient +{ + private const string ErrorCodeKey = "error"; + private const string InvalidGrantError = "invalid_grant"; + private const string UseDPopNonceError = "use_dpop_nonce"; + + public DPopHttpClient( + IHttpClientFactory httpClientFactory, + IKeyStore keyStore, + ILogger logger) + { + _keyStore = keyStore; + _httpClient = httpClientFactory.CreateClient(); + _logger = logger; + } + + private readonly IKeyStore _keyStore; + private readonly ILogger _logger; + private readonly HttpClient _httpClient; + + public async Task Post( + Uri requestUri, + HttpContent content, + DPopConfig config) + { + var dPop = await _keyStore.GenerateDPopProofOfPossessionAsync( + config.KeyId, + config.Audience, + config.Nonce.ToNullable(), + config.OAuthToken.ToNullable()?.AccessToken); + + var httpClient = config.OAuthToken.Match( + token => _httpClient.WithDPopHeader(dPop).WithAuthorizationHeader(token), + () => _httpClient.WithDPopHeader(dPop)); + + var response = await httpClient.PostAsync( + requestUri, + content); + + await ThrowIfInvalidGrantError(response); + + var nonceStr = await GetDPopNonce(response); + if (!string.IsNullOrEmpty(nonceStr)) + { + config = config with { Nonce = new DPopNonce(nonceStr) }; + + var newDpop = await _keyStore.GenerateDPopProofOfPossessionAsync( + config.KeyId, + config.Audience, + config.Nonce.ToNullable(), + config.OAuthToken.ToNullable()?.AccessToken); + + httpClient.WithDPopHeader(newDpop); + + response = await httpClient.PostAsync(requestUri, content); + } + + await ThrowIfInvalidGrantError(response); + + var message = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + throw new HttpRequestException($"Http Request with DPop failed. Status Code is {response.StatusCode} with message: {message}"); + + return new DPopHttpResponse(response, config); + } + + private async Task ThrowIfInvalidGrantError(HttpResponseMessage response) + { + var content = await response.Content.ReadAsStringAsync(); + var errorReason = string.IsNullOrEmpty(content) + ? null + : JObject.Parse(content)[ErrorCodeKey]?.ToString(); + + if (response.StatusCode is System.Net.HttpStatusCode.BadRequest && errorReason == InvalidGrantError) + { + _logger.LogError("Error while sending request: {Content}", content); + throw new Oid4VciInvalidGrantException(response.StatusCode); + } + } + + private static async Task GetDPopNonce(HttpResponseMessage response) + { + var content = await response.Content.ReadAsStringAsync(); + var errorReason = string.IsNullOrEmpty(content) + ? null + : JObject.Parse(content)[ErrorCodeKey]?.ToString(); + + if (response.StatusCode + is System.Net.HttpStatusCode.BadRequest + or System.Net.HttpStatusCode.Unauthorized + && errorReason == UseDPopNonceError + && response.Headers.TryGetValues("DPoP-Nonce", out var dPopNonce)) + { + return dPopNonce?.FirstOrDefault(); + } + + return null; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPop.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPop.cs new file mode 100644 index 00000000..c094a7ed --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPop.cs @@ -0,0 +1,11 @@ +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models; + +internal record DPop +{ + public DPopConfig Config { get; } + + internal DPop(DPopConfig config) + { + Config = config; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopConfig.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopConfig.cs new file mode 100644 index 00000000..d3367184 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopConfig.cs @@ -0,0 +1,24 @@ +using LanguageExt; +using WalletFramework.Core.Cryptography.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models; + +public record DPopConfig +{ + internal KeyId KeyId { get; } + + internal string Audience { get; init; } + + internal Option Nonce { get; init; } + + internal Option OAuthToken { get; init; } + + internal DPopConfig(KeyId keyId, string audience) + { + KeyId = keyId; + Audience = audience; + Nonce = Option.None; + OAuthToken = Option.None; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopHttpResponse.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopHttpResponse.cs new file mode 100644 index 00000000..34d67724 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopHttpResponse.cs @@ -0,0 +1,3 @@ +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models; + +public record DPopHttpResponse(HttpResponseMessage ResponseMessage, DPopConfig Config); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopNonce.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopNonce.cs new file mode 100644 index 00000000..9d5ee205 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopNonce.cs @@ -0,0 +1,18 @@ +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models; + +internal readonly struct DPopNonce +{ + private string Value { get; } + + public static implicit operator string(DPopNonce nonce) => nonce.Value; + + internal DPopNonce(string nonce) + { + if (string.IsNullOrEmpty(nonce)) + { + throw new InvalidOperationException("nonce must not be null or empty"); + } + + Value = nonce; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopToken.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopToken.cs new file mode 100644 index 00000000..bbb45b43 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/DPop/Models/DPopToken.cs @@ -0,0 +1,16 @@ +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models; + +public record DPopToken +{ + internal OAuthToken Token { get; } + + internal DPop DPop { get; } + + internal DPopToken(OAuthToken token, DPop dPop) + { + Token = token; + DPop = dPop; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Errors/AuthorizationServerIdError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Errors/AuthorizationServerIdError.cs new file mode 100644 index 00000000..e8ad0d64 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Errors/AuthorizationServerIdError.cs @@ -0,0 +1,5 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.Errors; + +public record AuthorizationServerIdError(string Value, Exception E) : Error($"The AuthorizationServerId could not be parsed. Value is `{Value}`", E); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Implementations/TokenService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Implementations/TokenService.cs new file mode 100644 index 00000000..5ecd19b0 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Implementations/TokenService.cs @@ -0,0 +1,63 @@ +using OneOf; +using WalletFramework.Core.Cryptography.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; +using static Newtonsoft.Json.JsonConvert; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.Implementations; + +internal class TokenService : ITokenService +{ + private readonly IDPopHttpClient _dPopHttpClient; + private readonly IKeyStore _keyStore; + private readonly HttpClient _httpClient; + + public TokenService( + IDPopHttpClient dPopHttpClient, + IHttpClientFactory httpClientFactory, + IKeyStore keyStore) + { + _dPopHttpClient = dPopHttpClient; + _keyStore = keyStore; + _httpClient = httpClientFactory.CreateClient(); + } + + public async Task> RequestToken( + TokenRequest tokenRequest, + AuthorizationServerMetadata metadata) + { + if (metadata.IsDPoPSupported) + { + var keyId = await _keyStore.GenerateKey(); + + var config = new DPopConfig(keyId, metadata.TokenEndpoint); + + var uri = new Uri(metadata.TokenEndpoint); + + var result = await _dPopHttpClient.Post( + uri, + tokenRequest.ToFormUrlEncoded(), + config); + + var token = DeserializeObject(await result.ResponseMessage.Content.ReadAsStringAsync()) + ?? throw new InvalidOperationException("Failed to deserialize the token response"); + + var dPop = new DPop.Models.DPop(result.Config); + + return new DPopToken(token, dPop); + } + else + { + var response = await _httpClient.PostAsync( + metadata.TokenEndpoint, + tokenRequest.ToFormUrlEncoded()); + + var token = DeserializeObject(await response.Content.ReadAsStringAsync()) + ?? throw new InvalidOperationException("Failed to deserialize the token response"); + + return token; + } + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/AuthorizationServerId.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/AuthorizationServerId.cs new file mode 100644 index 00000000..acc7b37d --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/AuthorizationServerId.cs @@ -0,0 +1,36 @@ +using System.Globalization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Core.Uri; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Errors; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct AuthorizationServerId +{ + private Uri Value { get; } + + private AuthorizationServerId(Uri value) => Value = value; + + public override string ToString() => Value.ToStringWithoutTrail(); + + public static implicit operator Uri(AuthorizationServerId authorizationServerId) => authorizationServerId.Value; + + public static Validation ValidAuthorizationServerId(JToken authorizationServerId) => authorizationServerId.ToJValue().OnSuccess(value => + { + try + { + var str = value.ToString(CultureInfo.InvariantCulture); + var uri = new Uri(str); + return new AuthorizationServerId(uri); + } + catch (Exception e) + { + return new AuthorizationServerIdError(authorizationServerId.ToString(), e).ToInvalid(); + } + }); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/AuthorizationServerMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/AuthorizationServerMetadata.cs new file mode 100644 index 00000000..c6b15430 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/AuthorizationServerMetadata.cs @@ -0,0 +1,78 @@ +using Newtonsoft.Json; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; + +/// +/// Represents the metadata associated with an OAuth 2.0 Authorization Server. +/// +public class AuthorizationServerMetadata +{ + /// + /// Gets or sets the issuer location for the OAuth 2.0 Authorization Server. + /// + [JsonProperty("issuer")] + public string Issuer { get; set; } + + /// + /// Gets or sets the URL of the OAuth 2.0 token endpoint. + /// Clients use this endpoint to obtain an access token by presenting its authorization grant or refresh token. + /// + [JsonProperty("token_endpoint")] + public string TokenEndpoint { get; set; } + + /// + /// Gets or sets the URL of the OAuth 2.0 JSON Web Key Set (JWKS) document. + /// Clients use this to verify the signatures from the Authorization Server. + /// + [JsonProperty("jwks_uri")] + public string JwksUri { get; set; } + + /// + /// Gets or sets the URL of the OAuth 2.0 authorization endpoint. + /// + [JsonProperty("authorization_endpoint")] + public string AuthorizationEndpoint { get; set; } + + /// + /// Gets or sets the response types that the OAuth 2.0 Authorization Server supports. + /// These types determine how the Authorization Server responds to client requests. + /// + [JsonProperty("response_types_supported", NullValueHandling = NullValueHandling.Ignore)] + public string[]? ResponseTypesSupported { get; set; } + + /// + /// Gets or sets the supported authentication methods the OAuth 2.0 Authorization Server supports + /// when calling the token endpoint. + /// + [JsonProperty("token_endpoint_auth_methods_supported")] + public string[] TokenEndpointAuthMethodsSupported { get; set; } + + /// + /// Gets or sets the supported token endpoint authentication signing algorithms. + /// This indicates which algorithms the Authorization Server supports when receiving requests + /// at the token endpoint. + /// + [JsonProperty("token_endpoint_auth_signing_alg_values_supported")] + public string[] TokenEndpointAuthSigningAlgValuesSupported { get; set; } + + /// + /// Gets or sets the supported DPoP signing algorithms. + /// This indicates which algorithms the Authorization Server supports for DPoP Proof JWTs. + /// + [JsonProperty("dpop_signing_alg_values_supported")] + public string[]? DPopSigningAlgValuesSupported { get; set; } + + /// + /// Gets or sets the URL of the endpoint where the wallet sends the Pushed Authorization Request (PAR) to. + /// + [JsonProperty("pushed_authorization_request_endpoint")] + public string? PushedAuthorizationRequestEndpoint { get; set; } + + /// + /// Gets or sets a value indicating whether the Authorization Server requires the use of Pushed Authorization Requests. + /// + [JsonProperty("require_pushed_authorization_requests")] + public bool? RequirePushedAuthorizationRequests { get; set; } + + internal bool IsDPoPSupported => DPopSigningAlgValuesSupported != null && DPopSigningAlgValuesSupported.Contains("ES256"); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/Mdoc/MdocTokenRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/Mdoc/MdocTokenRequest.cs new file mode 100644 index 00000000..eceac5a3 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/Mdoc/MdocTokenRequest.cs @@ -0,0 +1,21 @@ +// TODO: Add this when Client Attestation is available +// public record MdocTokenRequest +// { +// public TokenRequest VciTokenRequest { get; } +// +// public string ClientAssertionType => "urn:ietf:params:oauth:client-assertion-type:jwt-client-attestation"; +// +// public ClientAssertion ClientAssertion { get; } +// } +// +// public readonly struct ClientAssertion +// { +// public ClientAttestationJwt Jwt { get; } +// +// public ClientAttestationPop ClientAttestationPop { get; } +// } +// +// public readonly struct ClientAttestationJwt +// { +// public IEnumerable DeviceKeys { get; } +// } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/OAuthToken.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/OAuthToken.cs new file mode 100644 index 00000000..b55c1d24 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/OAuthToken.cs @@ -0,0 +1,75 @@ +using Newtonsoft.Json; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; + +/// +/// Represents a successful response from the OAuth 2.0 Authorization Server containing +/// the issued access token and related information. +/// +public class OAuthToken +{ + /// + /// Indicates if the Token Request is still pending as the Credential Issuer + /// is waiting for the End-User interaction to complete. + /// + [JsonProperty("authorization_pending")] + public bool? AuthorizationPending { get; set; } + + /// + /// Gets or sets the lifetime in seconds of the c_nonce. + /// + [JsonProperty("c_nonce_expires_in")] + public int? CNonceExpiresIn { get; set; } + + /// + /// Gets or sets the lifetime in seconds of the access token. + /// + [JsonProperty("expires_in")] + public int? ExpiresIn { get; set; } + + /// + /// Gets or sets the minimum amount of time in seconds that the client should wait + /// between polling requests to the Token Endpoint. + /// + [JsonProperty("interval")] + public int? Interval { get; set; } + + /// + /// Gets or sets the access token issued by the authorization server. + /// + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + /// + /// Gets or sets the nonce to be used to create a proof of possession of key material + /// when requesting a Credential. + /// + [JsonProperty("c_nonce")] + public string CNonce { get; set; } + + /// + /// Gets or sets the refresh token, which can be used to obtain new access tokens. + /// + [JsonProperty("refresh_token")] + public string RefreshToken { get; set; } + + /// + /// Gets or sets the scope of the access token. + /// + [JsonProperty("scope")] + public string Scope { get; set; } + + /// + /// Gets or sets the type of the token issued. + /// + [JsonProperty("token_type")] + public string TokenType { get; set; } + + /// + /// Gets or sets the credential identifier. + /// + [JsonProperty("credential_identifiers")] + public AuthorizationDetails? CredentialIdentifier { get; set; } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/TokenRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/TokenRequest.cs new file mode 100644 index 00000000..db5d74ba --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/TokenRequest.cs @@ -0,0 +1,88 @@ +namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; + +/// +/// Represents a request for an access token from an OAuth 2.0 Authorization Server. +/// +public class TokenRequest +{ + /// + /// Gets or sets the grant type of the request. Determines the type of token request being made. + /// + public string GrantType { get; set; } = null!; + + /// + /// Gets or sets the pre-authorized code. Represents the authorization to obtain specific credentials. + /// This is required if the grant type is urn:ietf:params:oauth:grant-type:pre-authorized_code. + /// + public string? PreAuthorizedCode { get; set; } + + /// + /// Gets or sets the pre-authorized code. Represents the authorization to obtain specific credentials. + /// This is required if the grant type is urn:ietf:params:oauth:grant-type:pre-authorized_code. + /// + public string? Code { get; set; } + + /// + /// Gets or sets the pre-authorized code. Represents the authorization to obtain specific credentials. + /// This is required if the grant type is urn:ietf:params:oauth:grant-type:pre-authorized_code. + /// + public string? CodeVerifier { get; set; } + + /// + /// Gets or sets the pre-authorized code. Represents the authorization to obtain specific credentials. + /// This is required if the grant type is urn:ietf:params:oauth:grant-type:pre-authorized_code. + /// + public string? ClientId { get; set; } + + /// + /// Gets or sets the pre-authorized code. Represents the authorization to obtain specific credentials. + /// This is required if the grant type is urn:ietf:params:oauth:grant-type:pre-authorized_code. + /// + public string? RedirectUri { get; set; } + + /// + /// Gets or sets the scope of the access request. Defines the permissions the client is asking for. + /// + public string? Scope { get; set; } + + /// + /// Gets or sets the transaction code. This value must be present if a transaction code was required in a previous step. + /// + public string? TransactionCode { get; set; } + + /// + /// Converts the properties of the TokenRequest instance into an FormUrlEncodedContent type suitable for HTTP POST + /// operations. + /// + /// Returns an instance of FormUrlEncodedContent containing the URL-encoded properties of the TokenRequest. + public FormUrlEncodedContent ToFormUrlEncoded() + { + var keyValuePairs = new List>(); + + if (!string.IsNullOrEmpty(GrantType)) + keyValuePairs.Add(new KeyValuePair("grant_type", GrantType)); + + if (!string.IsNullOrEmpty(PreAuthorizedCode)) + keyValuePairs.Add(new KeyValuePair("pre-authorized_code", PreAuthorizedCode)); + + if (!string.IsNullOrEmpty(Scope)) + keyValuePairs.Add(new KeyValuePair("scope", Scope)); + + if (!string.IsNullOrEmpty(TransactionCode)) + keyValuePairs.Add(new KeyValuePair("tx_code", TransactionCode)); + + if (!string.IsNullOrEmpty(Code)) + keyValuePairs.Add(new KeyValuePair("code", Code)); + + if (!string.IsNullOrEmpty(RedirectUri)) + keyValuePairs.Add(new KeyValuePair("redirect_uri", RedirectUri)); + + if (!string.IsNullOrEmpty(CodeVerifier)) + keyValuePairs.Add(new KeyValuePair("code_verifier", CodeVerifier)); + + if (!string.IsNullOrEmpty(ClientId)) + keyValuePairs.Add(new KeyValuePair("client_id", ClientId)); + + return new FormUrlEncodedContent(keyValuePairs); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Errors/BatchSizeIsNotAPositiveNumberError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Errors/BatchSizeIsNotAPositiveNumberError.cs new file mode 100644 index 00000000..34343bae --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Errors/BatchSizeIsNotAPositiveNumberError.cs @@ -0,0 +1,5 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Errors; + +public record BatchSizeIsNotAPositiveNumberError() : Error("The value of `batch_size` is not a positive number"); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Errors/FormatNotSupportedError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Errors/FormatNotSupportedError.cs new file mode 100644 index 00000000..6fd7e250 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Errors/FormatNotSupportedError.cs @@ -0,0 +1,5 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Errors; + +public record FormatNotSupportedError(string Value) : Error($"The given format `{Value}` is not supported"); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Errors/ProofTypeIdNotSupportedError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Errors/ProofTypeIdNotSupportedError.cs new file mode 100644 index 00000000..cd94a90c --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Errors/ProofTypeIdNotSupportedError.cs @@ -0,0 +1,5 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Errors; + +public record ProofTypeIdNotSupportedError(string Value) : Error($"The ProofTypeId: `{Value}` is not supported"); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialConfiguration.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialConfiguration.cs new file mode 100644 index 00000000..ea7ad271 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialConfiguration.cs @@ -0,0 +1,132 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using static WalletFramework.Core.Functional.ValidationFun; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Format; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Scope; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CryptographicBindingMethod; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CryptograhicSigningAlgValue; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.ProofTypeId; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.ProofTypeMetadata; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CredentialDisplay; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; + +/// +/// Represents the metadata of a specific type of credential that a Credential Issuer can issue. +/// +public record CredentialConfiguration +{ + /// + /// Gets the identifier for the format of the credential. + /// + [JsonProperty(FormatJsonKey)] + public Format Format { get; } + + /// + /// Gets a string indicating the credential that can be issued. + /// + [JsonProperty(ScopeJsonKey)] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Scope { get; set; } + + /// + /// Gets list of methods that identify how the Credential is bound to the identifier of the End-User who + /// possesses the Credential. + /// + [JsonProperty(CryptographicBindingMethodsSupportedJsonKey)] + [JsonConverter(typeof(OptionJsonConverter>))] + public Option> CryptographicBindingMethodsSupported { get; } + + /// + /// Gets a list of identifiers for the signing algorithms that are supported by the issuer and used + /// to sign credentials. + /// + [JsonProperty(CredentialSigningAlgValuesSupportedJsonKey)] + [JsonConverter(typeof(OptionJsonConverter>))] + public Option> CredentialSigningAlgValuesSupported { get; } + + /// + /// Gets a dictionary which maps a credential type to its supported signing algorithms for key proofs. + /// + [JsonProperty(ProofTypesSupportedJsonKey)] + [JsonConverter(typeof(OptionJsonConverter>))] + public Option> ProofTypesSupported { get; } + + /// + /// Gets a list of display properties of the supported credential for different languages. + /// + [JsonProperty(DisplayJsonKey)] + [JsonConverter(typeof(OptionJsonConverter>))] + public Option> Display { get; } + + private CredentialConfiguration( + Format format, + Option scope, + Option> cryptographicBindingMethodsSupported, + Option> credentialSigningAlgValuesSupported, + Option> proofTypesSupported, + Option> display) + { + Format = format; + Scope = scope; + CryptographicBindingMethodsSupported = cryptographicBindingMethodsSupported; + CredentialSigningAlgValuesSupported = credentialSigningAlgValuesSupported; + ProofTypesSupported = proofTypesSupported; + Display = display; + } + + private static CredentialConfiguration Create( + Format format, + Option scope, + Option> cryptographicBindingMethodsSupported, + Option> credentialSigningAlgValuesSupported, + Option> proofTypesSupported, + Option> display) => new( + format, + scope, + cryptographicBindingMethodsSupported, + credentialSigningAlgValuesSupported, + proofTypesSupported, + display); + + public static Validation ValidCredentialConfiguration(JToken credentialMetadata) + { + var validBindingMethods = new Func>>(bindingMethods => + from array in bindingMethods.ToJArray() + from methods in array.TraverseAny(ValidCryptographicBindingMethod) + select methods.ToList()); + + var validSigningAlgValues = new Func>>(signingAlgValues => + from array in signingAlgValues.ToJArray() + from values in array.TraverseAny(ValidCryptograhicSigningAlgValue) + select values.ToList()); + + var validProofTypes = new Func>>(proofTypes => proofTypes + .ToJObject() + .OnSuccess(jObject => jObject.ToValidDictionaryAny(ValidProofTypeId, ValidProofTypeMetadata))); + + var optionalCredentialDisplays = new Func>>(credentialDisplays => + from array in credentialDisplays.ToJArray().ToOption() + from displays in array.TraverseAny(OptionalCredentialDisplay) + select displays.ToList()); + + return Valid(Create) + .Apply(credentialMetadata.GetByKey(FormatJsonKey).OnSuccess(ValidFormat)) + .Apply(credentialMetadata.GetByKey(ScopeJsonKey).ToOption().OnSome(OptionalScope)) + .Apply(credentialMetadata.GetByKey(CryptographicBindingMethodsSupportedJsonKey).OnSuccess(validBindingMethods).ToOption()) + .Apply(credentialMetadata.GetByKey(CredentialSigningAlgValuesSupportedJsonKey).OnSuccess(validSigningAlgValues).ToOption()) + .Apply(credentialMetadata.GetByKey(ProofTypesSupportedJsonKey).OnSuccess(validProofTypes).ToOption()) + .Apply(credentialMetadata.GetByKey(DisplayJsonKey).ToOption().OnSome(optionalCredentialDisplays)); + } + + private const string FormatJsonKey = "format"; + private const string ScopeJsonKey = "scope"; + private const string CryptographicBindingMethodsSupportedJsonKey = "cryptographic_binding_methods_supported"; + private const string CredentialSigningAlgValuesSupportedJsonKey = "credential_signing_alg_values_supported"; + private const string ProofTypesSupportedJsonKey = "proof_types_supported"; + private const string DisplayJsonKey = "display"; +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialDisplay.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialDisplay.cs new file mode 100644 index 00000000..63580e48 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialDisplay.cs @@ -0,0 +1,118 @@ +using System.Drawing; +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Colors; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Core.Localization; +using WalletFramework.SdJwtVc.Models.Credential; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CredentialName; +using static WalletFramework.Core.Localization.Locale; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CredentialLogo; +using Color = WalletFramework.Core.Colors.Color; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; + +/// +/// Represents the visual representations for the credential. +/// +public record CredentialDisplay +{ + /// + /// Gets the logo associated with this Credential. + /// + [JsonProperty("logo")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Logo { get; } + + /// + /// Gets the name of the Credential. + /// + [JsonProperty("name")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Name { get; } + + /// + /// Gets the background color for the Credential. + /// + [JsonProperty("background_color")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option BackgroundColor { get; } + + /// + /// Gets the locale, which represents the specific culture or region. + /// + [JsonProperty("locale")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Locale { get; } + + /// + /// Gets the text color for the Credential. + /// + [JsonProperty("text_color")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option TextColor { get; } + + private CredentialDisplay( + Option logo, + Option name, + Option backgroundColor, + Option locale, + Option textColor) + { + Logo = logo; + Name = name; + BackgroundColor = backgroundColor; + Locale = locale; + TextColor = textColor; + } + + public static Option OptionalCredentialDisplay(JToken display) => display + .ToJObject() + .ToOption() + .OnSome(jObject => + { + var backgroundColor = jObject + .GetByKey("background_color") + .ToOption() + .OnSome(color => Color.OptionColor(color.ToString())); + + var textColor = jObject + .GetByKey("text_color") + .ToOption() + .OnSome(color => Color.OptionColor(color.ToString())); + + var name = jObject.GetByKey("name").ToOption().OnSome(OptionalCredentialName); + var logo = jObject.GetByKey("logo").ToOption().OnSome(OptionalCredentialLogo); + var locale = jObject.GetByKey("locale").OnSuccess(ValidLocale).ToOption(); + + if (name.IsNone && logo.IsNone && backgroundColor.IsNone && locale.IsNone && textColor.IsNone) + return Option.None; + + return new CredentialDisplay(logo, name, backgroundColor, locale, textColor); + }); +} + +public static class CredentialDisplayFun +{ + // TODO: Unpure + public static SdJwtDisplay ToSdJwtDisplay(this CredentialDisplay credentialDisplay) + { + var logo = new SdJwtDisplay.SdJwtLogo + { + Uri = credentialDisplay.Logo.ToNullable()?.Uri.ToNullable()!, + AltText = credentialDisplay.Logo.ToNullable()?.AltText.ToNullable() + }; + + return new SdJwtDisplay + { + Logo = logo, + Name = credentialDisplay.Name.ToNullable(), + BackgroundColor = credentialDisplay.BackgroundColor.ToNullable(), + Locale = credentialDisplay.Locale.ToNullable()?.ToString(), + TextColor = credentialDisplay.TextColor.ToNullable() + }; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialLogo.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialLogo.cs new file mode 100644 index 00000000..2a58ee40 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialLogo.cs @@ -0,0 +1,65 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; + +/// +/// Represents the Logo for a Credential. +/// +public record CredentialLogo +{ + /// + /// Gets the alternate text that describes the logo image. This is typically used for accessibility purposes. + /// + [JsonProperty("alt_text")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option AltText { get; } + + /// + /// Gets the URL of the logo image. + /// + [JsonProperty("uri")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Uri { get; } + + private CredentialLogo(Option altText, Option uri) + { + AltText = altText; + Uri = uri; + } + + public static Option OptionalCredentialLogo(JToken logo) + { + var altText = logo.GetByKey("alt_text").ToOption().OnSome(text => + { + var str = text.ToString(); + if (string.IsNullOrWhiteSpace(str)) + return Option.None; + + return str; + }); + + var imageUri = logo.GetByKey("uri").ToOption().OnSome(uri => + { + try + { + var str = uri.ToString(); + var result = new Uri(str); + return result; + } + catch (Exception) + { + return Option.None; + } + }); + + if (altText.IsNone && imageUri.IsNone) + return Option.None; + + return new CredentialLogo(altText, imageUri); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialName.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialName.cs new file mode 100644 index 00000000..8bfc5d0f --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialName.cs @@ -0,0 +1,32 @@ +using System.Globalization; +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct CredentialName +{ + private string Value { get; } + + private CredentialName(string value) => Value = value; + + public override string ToString() => Value; + + public static implicit operator string(CredentialName credentialName) => credentialName.ToString(); + + public static Option OptionalCredentialName(JToken name) => name.ToJValue().ToOption().OnSome(value => + { + var str = value.ToString(CultureInfo.InvariantCulture); + if (string.IsNullOrWhiteSpace(str)) + { + return Option.None; + } + + return new CredentialName(str); + }); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptograhicSigningAlgValue.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptograhicSigningAlgValue.cs new file mode 100644 index 00000000..ded1adeb --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptograhicSigningAlgValue.cs @@ -0,0 +1,33 @@ +using System.Globalization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Functional.Errors; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using static WalletFramework.Core.Functional.ValidationFun; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct CryptograhicSigningAlgValue +{ + private string Value { get; } + + private CryptograhicSigningAlgValue(string value) => Value = value; + + public override string ToString() => Value; + + public static implicit operator string(CryptograhicSigningAlgValue alg) => alg.ToString(); + + public static Validation ValidCryptograhicSigningAlgValue(JToken alg) => alg.ToJValue().OnSuccess(value => + { + var str = value.ToString(CultureInfo.InvariantCulture); + if (string.IsNullOrWhiteSpace(str)) + { + return new StringIsNullOrWhitespaceError(); + } + + return Valid(new CryptograhicSigningAlgValue(str)); + }); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptographicBindingMethod.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptographicBindingMethod.cs new file mode 100644 index 00000000..bf1b7f2c --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptographicBindingMethod.cs @@ -0,0 +1,32 @@ +using System.Globalization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Functional.Errors; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct CryptographicBindingMethod +{ + private string Value { get; } + + private CryptographicBindingMethod(string value) => Value = value; + + public override string ToString() => Value; + + public static implicit operator string(CryptographicBindingMethod method) => method.ToString(); + + public static Validation ValidCryptographicBindingMethod(JToken method) => method.ToJValue().OnSuccess(value => + { + var str = value.ToString(CultureInfo.InvariantCulture); + if (string.IsNullOrWhiteSpace(str)) + { + return new StringIsNullOrWhitespaceError().ToInvalid(); + } + + return new CryptographicBindingMethod(str); + }); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Format.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Format.cs new file mode 100644 index 00000000..2ef65059 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Format.cs @@ -0,0 +1,35 @@ +using System.Globalization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Errors; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct Format +{ + private string Value { get; } + + private Format(string value) => Value = value; + + public override string ToString() => Value; + + public static implicit operator string(Format format) => format.ToString(); + + public static Validation ValidFormat(JToken format) => format.ToJValue().OnSuccess(value => + { + var str = value.ToString(CultureInfo.InvariantCulture); + return SupportedFormats.Contains(str) + ? new Format(str) + : new FormatNotSupportedError(str).ToInvalid(); + }); + + private static List SupportedFormats => new() + { + "vc+sd-jwt", + "mso_mdoc" + }; +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ClaimsMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ClaimsMetadata.cs new file mode 100644 index 00000000..c3b30a86 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ClaimsMetadata.cs @@ -0,0 +1,88 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.MdocLib; +using WalletFramework.MdocVc; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.ElementMetadata; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; + +[JsonConverter(typeof(ClaimsMetadataJsonConverter))] +public readonly struct ClaimsMetadata +{ + public Dictionary> Value { get; } + + private ClaimsMetadata(Dictionary> value) => + Value = value; + + public static Validation ValidClaimsMetadata(JObject claims) => claims + .ToValidDictionaryAll(NameSpace.ValidNameSpace, ValidElementMetadatas) + .OnSuccess(dictionary => new ClaimsMetadata(dictionary)); +} + +public class ClaimsMetadataJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override void WriteJson(JsonWriter writer, ClaimsMetadata claimsMetadata, JsonSerializer serializer) + { + writer.WriteStartObject(); + + var value = JObject.FromObject(claimsMetadata.Value, serializer); + foreach (var property in value.Properties()) + { + property.WriteTo(writer); + } + } + + public override ClaimsMetadata ReadJson(JsonReader reader, Type objectType, ClaimsMetadata existingValue, bool hasExistingValue, + JsonSerializer serializer) => + throw new NotImplementedException(); +} + +public static class ClaimsMetadataFun +{ + public static Option>>> ToClaimsDisplays( + this ClaimsMetadata claimsMetadata) + { + var result = new Dictionary>>(); + + foreach (var nameSpacePair in claimsMetadata.Value) + { + var elementDisplays = new Dictionary>(); + foreach (var elementPair in nameSpacePair.Value) + { + elementPair.Value.Display.Match( + list => + { + var displays = list.Select(elementDisplay => + { + var name = + from elementName in elementDisplay.Name + from claimName in ClaimName.OptionClaimName(elementName) + select claimName; + + return new ClaimDisplay(name, elementDisplay.Locale); + }).ToList(); + elementDisplays.Add(elementPair.Key, displays); + }, + () => {} + ); + } + + if (elementDisplays.Any()) + result.Add(nameSpacePair.Key, elementDisplays); + } + + if (result.Any()) + { + return result; + } + else + { + return Option>>>.None; + } + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptoGraphicCurve.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptoGraphicCurve.cs new file mode 100644 index 00000000..a076ee4e --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptoGraphicCurve.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json.Converters; +using WalletFramework.MdocLib; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct CryptographicCurve +{ + public CoseLabel Value { get; } + + private CryptographicCurve(CoseLabel value) => Value = value; + + public static Validation ValidCryptographicCurve(JToken curve) => + CoseLabel.ValidCoseLabel(curve).OnSuccess(label => new CryptographicCurve(label)); + + public override string ToString() => Value; +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptographicSuite.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptographicSuite.cs new file mode 100644 index 00000000..00191e4b --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptographicSuite.cs @@ -0,0 +1,31 @@ +using System.Globalization; +using System.Text.Json.Serialization; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Functional.Errors; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct CryptographicSuite +{ + // TODO: Validate if Value is part of IANA Registry + public string Value { get; } + + private CryptographicSuite(string value) => Value = value; + + public override string ToString() => Value; + + public static Validation ValidCryptographicSuite(JToken suite) => suite.ToJValue().OnSuccess(value => + { + var str = value.ToString(CultureInfo.InvariantCulture); + if (string.IsNullOrWhiteSpace(str)) + { + return new StringIsNullOrWhitespaceError().ToInvalid(); + } + + return new CryptographicSuite(str); + }); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementDisplay.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementDisplay.cs new file mode 100644 index 00000000..40a0b7e5 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementDisplay.cs @@ -0,0 +1,44 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Core.Localization; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; + +public record ElementDisplay +{ + [JsonProperty("locale")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Locale { get; } + + [JsonProperty("name")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Name { get; } + + private ElementDisplay(Option name, Option locale) + { + Name = name; + Locale = locale; + } + + public static Option OptionalElementDisplay(JObject display) + { + var name = display.GetByKey("name").Match( + ElementName.OptionalElementName, + _ => Option.None); + + var locale = display.GetByKey("locale").Match( + Core.Localization.Locale.OptionLocale, + _ => Option.None); + + if (name.IsNone && locale.IsNone) + { + return Option.None; + } + + return new ElementDisplay(name, locale); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementMetadata.cs new file mode 100644 index 00000000..fe90bb99 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementMetadata.cs @@ -0,0 +1,63 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.MdocLib; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; + +public record ElementMetadata +{ + [JsonProperty("mandatory")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Mandatory { get; } + + [JsonProperty("display")] + [JsonConverter(typeof(OptionJsonConverter>))] + public Option> Display { get; } + + private ElementMetadata(Option mandatory, Option> display) + { + Mandatory = mandatory; + Display = display; + } + + public static ElementMetadata CreateElementMetadata(JToken metadata) + { + var mandatory = metadata.GetByKey("mandatory").Match( + jToken => + { + var str = jToken.ToString(); + return bool.Parse(str); + }, + _ => Option.None); + + var validDisplay = + from token in metadata.GetByKey("display") + from array in token.ToJArray() + select array; + + var display = + from array in validDisplay.ToOption() + from displays in array.TraverseAny(token => + { + var optJObject = token.ToJObject().ToOption(); + return + from jObject in optJObject + from elementDisplay in ElementDisplay.OptionalElementDisplay(jObject) + select elementDisplay; + }) + select displays.ToList(); + + return new ElementMetadata(mandatory, display); + } + + public static Validation> ValidElementMetadatas( + JToken metadatas) => metadatas + .ToJObject() + .OnSuccess(o => o.ToValidDictionaryAll( + ElementIdentifier.ValidElementIdentifier, + token => ValidationFun.Valid(ElementMetadata.CreateElementMetadata(token)))); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementName.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementName.cs new file mode 100644 index 00000000..2d3623a2 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementName.cs @@ -0,0 +1,31 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Json.Converters; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct ElementName +{ + private string Value { get; } + + private ElementName(string value) => Value = value; + + public override string ToString() => Value; + + public static implicit operator string(ElementName elementName) => elementName.Value; + + public static Option OptionalElementName(JToken name) + { + var str = name.ToString(); + if (string.IsNullOrWhiteSpace(str)) + { + return Option.None; + } + else + { + return new ElementName(str); + } + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/MdocConfiguration.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/MdocConfiguration.cs new file mode 100644 index 00000000..759148d4 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/MdocConfiguration.cs @@ -0,0 +1,146 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.MdocLib; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.CryptographicCurve; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.CryptographicSuite; +using static WalletFramework.MdocLib.DocType; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.Policy; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.MdocConfiguration.MdocConfigurationJsonKeys; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; + +[JsonConverter(typeof(MdocConfigurationJsonConverter))] +public record MdocConfiguration +{ + public CredentialConfiguration CredentialConfiguration { get; } + + public DocType DocType { get; } + + // TODO: This is actually required, but BDR doesnt use it + public Option Policy { get; } + + public Option> CryptographicSuitesSupported { get; } + + public Option> CryptographicCurvesSupported { get; } + + public Option Claims { get; } + + public Format Format => CredentialConfiguration.Format; + + private MdocConfiguration( + CredentialConfiguration credentialConfiguration, + DocType docType, + Option policy, + Option> cryptographicSuitesSupported, + Option> cryptographicCurvesSupported, + Option claims) + { + CredentialConfiguration = credentialConfiguration; + DocType = docType; + Policy = policy; + CryptographicSuitesSupported = cryptographicSuitesSupported; + CryptographicCurvesSupported = cryptographicCurvesSupported; + Claims = claims; + } + + private static MdocConfiguration Create( + CredentialConfiguration credentialConfiguration, + DocType docType, + Option policy, + Option> cryptographicSuitesSupported, + Option> cryptographicCurvesSupported, + Option claims) => + new(credentialConfiguration, docType, policy, cryptographicSuitesSupported, cryptographicCurvesSupported, claims); + + public static Validation ValidMdocConfiguration(JObject config) + { + var credentialConfiguration = CredentialConfiguration.ValidCredentialConfiguration(config); + + var docType = config.GetByKey(DocTypeJsonKey).OnSuccess(ValidDoctype); + var policy = config.GetByKey(PolicyJsonKey).OnSuccess(ValidPolicy).ToOption(); + + var cryptographicSuitesSupported = config + .GetByKey(CryptographicSuitesSupportedJsonKey) + .OnSuccess(token => token.ToJArray()) + .OnSuccess(array => array.Select(ValidCryptographicSuite).TraverseAll(suite => suite)) + .OnSuccess(suites => suites.ToList()) + .ToOption(); + + var cryptographicCurvesSupported = config + .GetByKey(CryptographicCurvesSupportedJsonKey) + .OnSuccess(token => token.ToJArray()) + .OnSuccess(array => array.Select(ValidCryptographicCurve).TraverseAll(curve => curve)) + .OnSuccess(curves => curves.ToList()) + .ToOption(); + + var claims = config + .GetByKey(ClaimsJsonKey) + .OnSuccess(token => token.ToJObject()) + .OnSuccess(ClaimsMetadata.ValidClaimsMetadata) + .ToOption(); + + return ValidationFun.Valid(Create) + .Apply(credentialConfiguration) + .Apply(docType) + .Apply(policy) + .Apply(cryptographicSuitesSupported) + .Apply(cryptographicCurvesSupported) + .Apply(claims); + } + + public static class MdocConfigurationJsonKeys + { + public const string DocTypeJsonKey = "doctype"; + public const string PolicyJsonKey = "policy"; + public const string CryptographicSuitesSupportedJsonKey = "cryptographic_suites_supported"; + public const string CryptographicCurvesSupportedJsonKey = "cryptographic_curves_supported"; + public const string ClaimsJsonKey = "claims"; + } +} + +public class MdocConfigurationJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override void WriteJson(JsonWriter writer, MdocConfiguration? mdocConfig, JsonSerializer serializer) + { + writer.WriteStartObject(); + + var credentialConfig = JObject.FromObject(mdocConfig!.CredentialConfiguration); + foreach (var property in credentialConfig.Properties()) + { + property.WriteTo(writer); + } + + serializer.Converters.Add(new OptionJsonConverter()); + serializer.Converters.Add(new OptionJsonConverter>()); + serializer.Converters.Add(new OptionJsonConverter>()); + serializer.Converters.Add(new OptionJsonConverter()); + serializer.Converters.Add(new ValueTypeJsonConverter()); + + writer.WritePropertyName(DocTypeJsonKey); + serializer.Serialize(writer, mdocConfig.DocType); + + writer.WritePropertyName(PolicyJsonKey); + serializer.Serialize(writer, mdocConfig.Policy); + + writer.WritePropertyName(CryptographicSuitesSupportedJsonKey); + serializer.Serialize(writer, mdocConfig.CryptographicSuitesSupported); + + writer.WritePropertyName(CryptographicCurvesSupportedJsonKey); + serializer.Serialize(writer, mdocConfig.CryptographicCurvesSupported); + + writer.WritePropertyName(ClaimsJsonKey); + serializer.Serialize(writer, mdocConfig.Claims); + + writer.WriteEndObject(); + } + + public override MdocConfiguration ReadJson(JsonReader reader, Type objectType, MdocConfiguration? existingValue, + bool hasExistingValue, JsonSerializer serializer) => + throw new NotImplementedException(); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/Policy.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/Policy.cs new file mode 100644 index 00000000..565b7dd6 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/Policy.cs @@ -0,0 +1,85 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Core.Json.Errors; +using WalletFramework.MdocLib.Common; +using WalletFramework.MdocVc.Common; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Errors; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; + +public record Policy +{ + [JsonProperty("one_time_use")] + public bool OneTimeUse { get; } + + [JsonProperty("batch_size")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option BatchSize { get; } + + private Policy(bool oneTimeUse, Option batchSize) + { + OneTimeUse = oneTimeUse; + BatchSize = batchSize; + } + + private static Policy Create(bool oneTimeUse, Option batchSize) => new(oneTimeUse, batchSize); + + public static Validation ValidPolicy(JToken policy) + { + JObject jObject; + try + { + jObject = policy.ToObject()!; + } + catch (Exception e) + { + return new JTokenIsNotAnJObjectError("policy", e); + } + + var oneTimeUse = jObject + .GetByKey("one_time_use") + .OnSuccess(token => + { + var str = token.ToString(); + if (string.IsNullOrWhiteSpace(str)) + { + return new FieldValueIsNullOrEmptyError("one_time_use").ToInvalid(); + } + else + { + try + { + return bool.Parse(str); + } + catch (Exception e) + { + return new OneTimeUseIsNotABooleanValueError(str, e); + } + } + }); + + var batchSize = jObject + .GetByKey("batch_size") + .OnSuccess(token => + { + try + { + var str = token.ToString(); + return uint.Parse(str); + } + catch (Exception) + { + return new BatchSizeIsNotAPositiveNumberError().ToInvalid(); + } + }) + .ToOption(); + + return ValidationFun.Valid(Create) + .Apply(oneTimeUse) + .Apply(batchSize); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeId.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeId.cs new file mode 100644 index 00000000..7ffb7ff0 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeId.cs @@ -0,0 +1,37 @@ +using System.Globalization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Errors; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct ProofTypeId +{ + private string Value { get; } + + private ProofTypeId(string value) + { + Value = value; + } + + public override string ToString() => Value; + + public static implicit operator string(ProofTypeId proofTypeId) => proofTypeId.ToString(); + + public static Validation ValidProofTypeId(JToken proofTypeId) => proofTypeId.ToJValue().OnSuccess(value => + { + var str = value.ToString(CultureInfo.InvariantCulture); + return SupportedProofTypes.Contains(str) + ? new ProofTypeId(str) + : new ProofTypeIdNotSupportedError(str).ToInvalid(); + }); + + private static List SupportedProofTypes => new() + { + "jwt" + }; +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeMetadata.cs new file mode 100644 index 00000000..300ef7ac --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeMetadata.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CryptograhicSigningAlgValue; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; + +/// +/// Represents proof type specific signing algorithm information. +/// +public record ProofTypeMetadata +{ + /// + /// Gets the available signing algorithms for the associated credential type. + /// + [JsonProperty("proof_signing_alg_values_supported")] + public List ProofSigningAlgValuesSupported { get; } + + private ProofTypeMetadata(List proofSigningAlgValuesSupported) + { + ProofSigningAlgValuesSupported = proofSigningAlgValuesSupported; + } + + public static Validation ValidProofTypeMetadata(JToken proofTypeMetadata) => + from jObject in proofTypeMetadata.ToJObject() + from jToken in jObject.GetByKey("proof_signing_alg_values_supported") + from jArray in jToken.ToJArray() + from algValues in jArray.TraverseAny(ValidCryptograhicSigningAlgValue) + select new ProofTypeMetadata(algValues.ToList()); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Scope.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Scope.cs new file mode 100644 index 00000000..3a6d371c --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Scope.cs @@ -0,0 +1,32 @@ +using System.Globalization; +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct Scope +{ + private string Value { get; } + + private Scope(string value) => Value = value; + + public override string ToString() => Value; + + public static implicit operator string(Scope scope) => scope.ToString(); + + public static Option OptionalScope(JToken scope) => scope.ToJValue().ToOption().OnSome(value => + { + var str = value.ToString(CultureInfo.InvariantCulture); + if (string.IsNullOrWhiteSpace(str)) + { + return Option.None; + } + + return new Scope(str); + }); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SdJwt/SdJwtConfiguration.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SdJwt/SdJwtConfiguration.cs new file mode 100644 index 00000000..8b0c0668 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SdJwt/SdJwtConfiguration.cs @@ -0,0 +1,97 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.SdJwtVc.Models; +using WalletFramework.SdJwtVc.Models.Credential.Attributes; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CredentialConfiguration; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.SdJwt.SdJwtConfiguration.SdJwtConfigurationJsonKeys; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.SdJwt; + +[JsonConverter(typeof(SdJwtConfigurationJsonConverter))] +public record SdJwtConfiguration +{ + public CredentialConfiguration CredentialConfiguration { get; } + + public Format Format => CredentialConfiguration.Format; + + public Vct Vct { get; } + + /// + /// Gets or sets the dictionary representing the attributes of the credential in different languages. + /// + public Dictionary? Claims { get; set; } + + /// + /// A list of claim display names, arranged in the order in which they should be displayed by the Wallet. + /// + public List? Order { get; set; } + + private SdJwtConfiguration(CredentialConfiguration credentialConfiguration, Vct vct) + { + CredentialConfiguration = credentialConfiguration; + Vct = vct; + } + + private static SdJwtConfiguration Create(CredentialConfiguration credentialConfiguration, Vct vct) => + new(credentialConfiguration, vct); + + public static Validation ValidSdJwtCredentialConfiguration(JToken config) + { + var credentialConfiguration = ValidCredentialConfiguration(config); + var vct = config.GetByKey(VctJsonName).OnSuccess(Vct.ValidVct); + + var claims = config[ClaimsJsonName]?.ToObject>(); + var order = config[OrderJsonName]?.ToObject>(); + + var result = ValidationFun.Valid(Create) + .Apply(credentialConfiguration) + .Apply(vct) + .OnSuccess(configuration => configuration with + { + Claims = claims, + Order = order + }); + + return result; + } + + public static class SdJwtConfigurationJsonKeys + { + public const string VctJsonName = "vct"; + public const string ClaimsJsonName = "claims"; + public const string OrderJsonName = "order"; + } +} + +public class SdJwtConfigurationJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override void WriteJson(JsonWriter writer, SdJwtConfiguration? sdJwtConfig, JsonSerializer serializer) + { + writer.WriteStartObject(); + + var credentialConfig = JObject.FromObject(sdJwtConfig!.CredentialConfiguration, serializer); + foreach (var property in credentialConfig.Properties()) + { + property.WriteTo(writer); + } + + writer.WritePropertyName(ClaimsJsonName); + serializer.Serialize(writer, sdJwtConfig.Claims); + + writer.WritePropertyName(OrderJsonName); + serializer.Serialize(writer, sdJwtConfig.Order); + + writer.WritePropertyName(VctJsonName); + serializer.Serialize(writer, sdJwtConfig.Vct); + + writer.WriteEndObject(); + } + + public override SdJwtConfiguration ReadJson(JsonReader reader, Type objectType, SdJwtConfiguration? existingValue, + bool hasExistingValue, JsonSerializer serializer) => + throw new NotImplementedException(); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SupportedCredentialConfiguration.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SupportedCredentialConfiguration.cs new file mode 100644 index 00000000..826cb54e --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SupportedCredentialConfiguration.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; +using OneOf; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.SdJwt; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; + +[JsonConverter(typeof(OneOfJsonConverter))] +public sealed class SupportedCredentialConfiguration : OneOfBase +{ + public static implicit operator OneOf( + SupportedCredentialConfiguration supportedCredentialConfiguration) => + supportedCredentialConfiguration.Match( + sdJwt => (OneOf)sdJwt, + mdoc => mdoc + ); + + public static implicit operator SupportedCredentialConfiguration(OneOf input) => + new(input); + + public static implicit operator SupportedCredentialConfiguration(SdJwtConfiguration sdJwtConfiguration) => + new(sdJwtConfiguration); + + public static implicit operator SupportedCredentialConfiguration(MdocConfiguration mdocConfiguration) => + new(mdocConfiguration); + + private SupportedCredentialConfiguration(OneOf input) : base(input) + { + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Abstractions/ICredentialOfferService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Abstractions/ICredentialOfferService.cs new file mode 100644 index 00000000..16c6e9be --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Abstractions/ICredentialOfferService.cs @@ -0,0 +1,10 @@ +using WalletFramework.Core.Functional; +using WalletFramework.Core.Localization; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Abstractions; + +public interface ICredentialOfferService +{ + public Task> ProcessCredentialOffer(Uri credentialOffer, Locale language); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CouldNotFetchCredentialOfferError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CouldNotFetchCredentialOfferError.cs new file mode 100644 index 00000000..61cbb4af --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CouldNotFetchCredentialOfferError.cs @@ -0,0 +1,7 @@ +using System.Net; +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors; + +public record CouldNotFetchCredentialOfferError(HttpStatusCode Code) + : Error($"The credential offer could not be fetched with the status code: {Code}"); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialConfigurationIdError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialConfigurationIdError.cs new file mode 100644 index 00000000..f6cf0b1b --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialConfigurationIdError.cs @@ -0,0 +1,7 @@ +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors; + +public record CredentialConfigurationIdError(JValue Value, Exception E) + : Error($"The CredentialConfigurationId could not be processed. The value is `{Value}`", E); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialConfigurationIdIsNullOrWhitespaceError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialConfigurationIdIsNullOrWhitespaceError.cs new file mode 100644 index 00000000..b18c21b8 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialConfigurationIdIsNullOrWhitespaceError.cs @@ -0,0 +1,6 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors; + +public record CredentialConfigurationIdIsNullOrWhitespaceError() + : Error("The CredentialConfigurationId is null or whitespace"); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialIssuerError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialIssuerError.cs new file mode 100644 index 00000000..3cd48ad9 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialIssuerError.cs @@ -0,0 +1,7 @@ +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors; + +public record CredentialIssuerError(JValue Value, Exception E) + : Error($"The credential issuer field could not be processed. The value is: `{Value}`", E); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialOfferHasNoQueryParameterError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialOfferHasNoQueryParameterError.cs new file mode 100644 index 00000000..3ec402c0 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialOfferHasNoQueryParameterError.cs @@ -0,0 +1,6 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors; + +public record CredentialOfferHasNoQueryParameterError(Exception E) + : Error("The credential offer contains no query parameters", E); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialOfferNotFoundError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialOfferNotFoundError.cs new file mode 100644 index 00000000..574b8cc6 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Errors/CredentialOfferNotFoundError.cs @@ -0,0 +1,6 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors; + +public record CredentialOfferNotFoundError() + : Error("Neither an embedded credential offer or a credential offer uri could be found"); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/GrantTypes/AuthorizationCode.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/GrantTypes/AuthorizationCode.cs new file mode 100644 index 00000000..03fb69e5 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/GrantTypes/AuthorizationCode.cs @@ -0,0 +1,55 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.GrantTypes; + +/// +/// Represents the parameters for the 'authorization_code' grant type. +/// +public record AuthorizationCode +{ + /// + /// String value created by the Credential Issuer and opaque to the Wallet that is used to bind the subsequent + /// Authorization Request with the Credential Issuer to a context set up during previous steps. If the Wallet decides + /// to use the Authorization Code Flow and received a value for this parameter, it MUST include it in the subsequent + /// Authorization Request to the Credential Issuer as the issuer_state parameter value + /// + [JsonProperty("issuer_state")] + public Option IssuerState { get; } + + /// + /// String that the Wallet can use to identify the Authorization Server to use with this grant type when + /// authorization_servers parameter in the Credential Issuer metadata has multiple entries. It MUST NOT be used + /// otherwise. The value of this parameter MUST match with one of the values in the authorization_servers array + /// obtained from the Credential Issuer metadata. + /// + [JsonProperty("authorization_server")] + public Option AuthorizationServer { get; } + + private AuthorizationCode(Option issuerState, Option authorizationServer) + { + IssuerState = issuerState; + AuthorizationServer = authorizationServer; + } + + public static Option OptionalAuthorizationCode(JToken authorizationCode) + { + var issuerState = authorizationCode + .GetByKey("issuer_state") + .OnSuccess(token => token.ToString()) + .ToOption(); + + var authServer = authorizationCode + .GetByKey("authorization_server") + .OnSuccess(token => token.ToString()) + .ToOption(); + + if (issuerState.IsNone && authServer.IsNone) + return Option.None; + + return new AuthorizationCode(issuerState, authServer); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/GrantTypes/PreAuthorizedCode.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/GrantTypes/PreAuthorizedCode.cs new file mode 100644 index 00000000..48e05d61 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/GrantTypes/PreAuthorizedCode.cs @@ -0,0 +1,155 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using static WalletFramework.Oid4Vc.Oid4Vci.CredOffer.GrantTypes.TransactionCode; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.GrantTypes; + +/// +/// Represents the parameters for the 'pre-authorized_code' grant type. +/// +public record PreAuthorizedCode +{ + /// + /// Gets the pre-authorized code representing the Credential Issuer's authorization for the Wallet to obtain + /// Credentials of a certain type. + /// + [JsonProperty("pre-authorized_code")] + public string Value { get; } + + /// + /// Specifying whether the user must send a Transaction Code along with the Token Request in a Pre-Authorized Code Flow. + /// + [JsonProperty("tx_code")] + public Option TransactionCode { get; } + + private PreAuthorizedCode(string value, Option transactionCode) + { + Value = value; + TransactionCode = transactionCode; + } + + public static Option OptionalPreAuthorizedCode(JToken preAuthCode) + { + var transactionCode = preAuthCode + .GetByKey("tx_code") + .ToOption() + .OnSome(OptionalTransactionCode); + + return preAuthCode + .GetByKey("pre-authorized_code") + .OnSuccess(token => + { + var value = token.ToString(); + return new PreAuthorizedCode(value, transactionCode); + }) + .ToOption(); + } + + public override string ToString() => Value; + + public static implicit operator string(PreAuthorizedCode preAuthorizedCode) => preAuthorizedCode.Value; +} + +/// +/// Represents the details of the expected Transaction Code. +/// +public record TransactionCode +{ + /// + /// Gets the length of the transaction code. + /// + [JsonProperty("length")] + public Option Length { get; } + + /// + /// Gets the description of the transaction code. + /// + [JsonProperty("description")] + public Option Description { get; } + + /// + /// Gets the input mode of the transaction code which specifies the valid character set. (Must be 'numeric' ot 'text') + /// + [JsonProperty("input_mode")] + public Option InputMode { get; } + + private TransactionCode( + Option length, + Option description, + Option inputMode) + { + Length = length; + Description = description; + InputMode = inputMode; + } + + public static Option OptionalTransactionCode(JToken transactionCode) + { + var length = transactionCode + .GetByKey("length") + .OnSuccess(token => token.ToJValue()) + .OnSuccess(value => value.ToInt()) + .ToOption(); + + var description = transactionCode + .GetByKey("description") + .OnSuccess(token => token.ToString()) + .ToOption(); + + var inputMode = transactionCode + .GetByKey("input_mode") + .ToOption() + .OnSome(GrantTypes.InputMode.OptionalInputMode); + + if (length.IsNone && description.IsNone && inputMode.IsNone) + return Option.None; + + return new TransactionCode(length, description, inputMode); + } +} + +public record InputMode +{ + private InputModeValues Value { get; } + + private InputMode(InputModeValues value) => Value = value; + + public static Option OptionalInputMode(JToken inputMode) + { + try + { + var str = inputMode.ToString(); + return str switch + { + "numeric" => new InputMode(InputModeValues.Numeric), + "text" => new InputMode(InputModeValues.Text), + _ => Option.None + }; + } + catch (Exception) + { + return Option.None; + } + } + + public static implicit operator string(InputMode inputMode) => inputMode.ToString(); + + public override string ToString() => ParseInputMode(Value); + + private enum InputModeValues + { + Numeric, + Text + } + + private static string ParseInputMode(InputModeValues values) => + values switch + { + InputModeValues.Numeric => "numeric", + InputModeValues.Text => "text", + _ => throw new ArgumentOutOfRangeException(nameof(values), values, "Invalid InputModeValues value") + }; +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Implementations/CredentialOfferService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Implementations/CredentialOfferService.cs new file mode 100644 index 00000000..6a980239 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Implementations/CredentialOfferService.cs @@ -0,0 +1,53 @@ +using System.Collections.Specialized; +using System.Net; +using System.Web; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Localization; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; +using static WalletFramework.Core.Json.JsonFun; +using static WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models.CredentialOffer; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Implementations; + +public class CredentialOfferService : ICredentialOfferService +{ + public CredentialOfferService(IHttpClientFactory httpClientFactory) + { + _httpClient = httpClientFactory.CreateClient(); + } + + private readonly HttpClient _httpClient; + + public async Task> ProcessCredentialOffer(Uri credentialOffer, Locale language) + { + NameValueCollection queryParams; + try + { + queryParams = HttpUtility.ParseQueryString(credentialOffer.Query); + } + catch (Exception e) + { + return new CredentialOfferHasNoQueryParameterError(e); + } + + if (queryParams["credential_offer"] is { } offer) + return ParseAsJObject(offer).OnSuccess(ValidCredentialOffer); + + if (queryParams["credential_offer_uri"] is { } offerUri) + { + _httpClient.DefaultRequestHeaders.Add("Accept-Language", language); + var response = await _httpClient.GetAsync(offerUri); + if (response.StatusCode == HttpStatusCode.OK) + { + var content = await response.Content.ReadAsStringAsync(); + return ParseAsJObject(content).OnSuccess(ValidCredentialOffer); + } + + return new CouldNotFetchCredentialOfferError(response.StatusCode); + } + + return new CredentialOfferNotFoundError(); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialConfigurationId.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialConfigurationId.cs new file mode 100644 index 00000000..d0010ec4 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialConfigurationId.cs @@ -0,0 +1,47 @@ +using System.Globalization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct CredentialConfigurationId +{ + private string Value { get; } + + public static implicit operator string(CredentialConfigurationId id) => id.ToString(); + + private CredentialConfigurationId(string id) => Value = id; + + public override string ToString() => Value; + + public static Validation ValidCredentialConfigurationId(JToken id) => + id.ToJValue().OnSuccess(value => + { + try + { + var str = value.ToString(CultureInfo.InvariantCulture); + if (string.IsNullOrWhiteSpace(str)) + return new CredentialConfigurationIdIsNullOrWhitespaceError(); + + return new CredentialConfigurationId(str); + } + catch (Exception e) + { + return new CredentialConfigurationIdError(value, e).ToInvalid(); + } + }); +} + +public class CredentialConfigurationIdDecoder : IValueTypeDecoder +{ + public CredentialConfigurationId Decode(JToken token) => + CredentialConfigurationId + .ValidCredentialConfigurationId(token) + .UnwrapOrThrow(new InvalidOperationException("CredentialConfigurationId is corrupt")); +} + diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialOffer.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialOffer.cs new file mode 100644 index 00000000..6671afdf --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialOffer.cs @@ -0,0 +1,82 @@ +using System.Globalization; +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors; +using static WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models.CredentialConfigurationId; +using static WalletFramework.Core.Functional.ValidationFun; +using static WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models.Grants; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; + +/// +/// Represents an OpenID4VCI Credential Offer, which is used to obtain one or more credentials from a Credential +/// Issuer. +/// +public record CredentialOffer +{ + /// + /// Gets the URL of the Credential Issuer from where the Wallet is requested to obtain one or more Credentials + /// from. + /// + [JsonProperty("credential_issuer")] + public Uri CredentialIssuer { get; } + + /// + /// Gets the list of credentials that the Wallet may request. The List contains CredentialMetadataIds + /// that must map to the keys in the credential_configurations_supported dictionary of the Issuer Metadata + /// + [JsonProperty("credential_configuration_ids")] + public List CredentialConfigurationIds { get; } + + /// + /// Gets the JSON object indicating to the Wallet the Grant Types the Credential Issuer's Authorization Server + /// is prepared to process for this credential offer. If not present or empty, the Wallet must determine the + /// Grant Types the Credential Issuer's AS supports using the respective metadata. + /// + [JsonProperty("grants")] + public Option Grants { get; } + + private CredentialOffer( + Uri credentialIssuer, + List credentialConfigurationIds, + Option grants) + { + CredentialIssuer = credentialIssuer; + CredentialConfigurationIds = credentialConfigurationIds; + Grants = grants; + } + + private static CredentialOffer Create( + Uri credentialIssuer, + List credentialConfigurationIds, + Option grants) => new(credentialIssuer, credentialConfigurationIds, grants); + + public static Validation ValidCredentialOffer(JObject json) + { + var validCredentialIssuer = new Func>(token => token.ToJValue().OnSuccess(value => + { + try + { + var str = value.ToString(CultureInfo.InvariantCulture); + return new Uri(str); + } + catch (Exception e) + { + return new CredentialIssuerError(value, e).ToInvalid(); + } + })); + + var validCredentialConfigurationsIds = new Func>>(token => + from jArray in token.ToJArray() + from configurationIds in jArray.TraverseAny(ValidCredentialConfigurationId) + select configurationIds.ToList()); + + return Valid(Create) + .Apply(json.GetByKey("credential_issuer").OnSuccess(validCredentialIssuer)) + .Apply(json.GetByKey("credential_configuration_ids").OnSuccess(validCredentialConfigurationsIds)) + .Apply(json.GetByKey("grants").OnSuccess(OptionalGrants)); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialOfferMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialOfferMetadata.cs new file mode 100644 index 00000000..4fda7240 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialOfferMetadata.cs @@ -0,0 +1,5 @@ +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; + +public record CredentialOfferMetadata(CredentialOffer CredentialOffer, IssuerMetadata IssuerMetadata); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/Grants.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/Grants.cs new file mode 100644 index 00000000..5f168592 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/Grants.cs @@ -0,0 +1,55 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.GrantTypes; +using static WalletFramework.Oid4Vc.Oid4Vci.CredOffer.GrantTypes.AuthorizationCode; +using static WalletFramework.Oid4Vc.Oid4Vci.CredOffer.GrantTypes.PreAuthorizedCode; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; + +/// +/// Represents the grant types that the Credential Issuer's AS is prepared to process for the credential offer. +/// +public record Grants +{ + /// + /// Gets the authorization_code grant type parameters. This includes an optional issuer state that is used to + /// bind the subsequent Authorization Request with the Credential Issuer to a context set up during previous steps. + /// + [JsonProperty("authorization_code")] + public Option AuthorizationCode { get; } + + /// + /// Gets the pre-authorized_code grant type parameters. This includes a required pre-authorized code + /// representing the Credential Issuer's authorization for the Wallet to obtain Credentials of a certain type, and an + /// optional boolean specifying whether a user PIN is required along with the Token Request. + /// + [JsonProperty("urn:ietf:params:oauth:grant-type:pre-authorized_code")] + public Option PreAuthorizedCode { get; } + + private Grants(Option authorizationCode, Option preAuthorizedCode) + { + AuthorizationCode = authorizationCode; + PreAuthorizedCode = preAuthorizedCode; + } + + public static Option OptionalGrants(JToken grants) + { + var authorizationCode = grants + .GetByKey("authorization_code") + .ToOption() + .OnSome(OptionalAuthorizationCode); + + var preAuthorizedCode = grants + .GetByKey("urn:ietf:params:oauth:grant-type:pre-authorized_code") + .ToOption() + .OnSome(OptionalPreAuthorizedCode); + + if (authorizationCode.IsNone && preAuthorizedCode.IsNone) + return Option.None; + + return new Grants(authorizationCode, preAuthorizedCode); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Abstractions/ICredentialRequestService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Abstractions/ICredentialRequestService.cs new file mode 100644 index 00000000..1654bce1 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Abstractions/ICredentialRequestService.cs @@ -0,0 +1,21 @@ +using LanguageExt; +using OneOf; +using WalletFramework.Core.Functional; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.SdJwt; +using WalletFramework.Oid4Vc.Oid4Vci.CredResponse; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Abstractions; + +public interface ICredentialRequestService +{ + public Task> RequestCredentials( + OneOf configuration, + IssuerMetadata issuerMetadata, + OneOf token, + Option clientOptions); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Implementations/CredentialRequestService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Implementations/CredentialRequestService.cs new file mode 100644 index 00000000..733c7738 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Implementations/CredentialRequestService.cs @@ -0,0 +1,137 @@ +using System.Text; +using LanguageExt; +using OneOf; +using WalletFramework.Core.Cryptography.Abstractions; +using WalletFramework.Core.Cryptography.Models; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Uri; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.SdJwt; +using WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models; +using WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models.Mdoc; +using WalletFramework.Oid4Vc.Oid4Vci.Extensions; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; +using WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models.SdJwt; +using WalletFramework.Oid4Vc.Oid4Vci.CredResponse; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Implementations; + +public class CredentialRequestService : ICredentialRequestService +{ + public CredentialRequestService( + HttpClient httpClient, + IDPopHttpClient dPopHttpClient, + IKeyStore keyStore) + { + _dPopHttpClient = dPopHttpClient; + _httpClient = httpClient; + _keyStore = keyStore; + } + + private readonly HttpClient _httpClient; + private readonly IDPopHttpClient _dPopHttpClient; + private readonly IKeyStore _keyStore; + + private async Task CreateCredentialRequest( + KeyId keyId, + Format format, + IssuerMetadata issuerMetadata, + OneOf token, + Option clientOptions) + { + var cNonce = token.Match( + oauthToken => oauthToken.CNonce, + dPopToken => dPopToken.Token.CNonce); + + var keyBindingJwt = await _keyStore.GenerateKbProofOfPossessionAsync( + keyId, + issuerMetadata.CredentialIssuer.ToString(), + cNonce, + "openid4vci-proof+jwt", + null, + clientOptions.ToNullable()?.ClientId); + + var proof = new ProofOfPossession + { + ProofType = "jwt", + Jwt = keyBindingJwt + }; + + return new CredentialRequest(proof, format); + } + + async Task> ICredentialRequestService.RequestCredentials( + OneOf configuration, + IssuerMetadata issuerMetadata, + OneOf token, + Option clientOptions) + { + var keyId = await _keyStore.GenerateKey(); + + var requestJson = await configuration.Match( + async sdJwt => + { + var vciRequest = await CreateCredentialRequest( + keyId, + sdJwt.Format, + issuerMetadata, + token, + clientOptions); + + var result = new SdJwtCredentialRequest(vciRequest, sdJwt.Vct); + return result.AsJson(); + }, + async mdoc => + { + var vciRequest = await CreateCredentialRequest( + keyId, + mdoc.Format, + issuerMetadata, + token, + clientOptions); + + var result = new MdocCredentialRequest(vciRequest, mdoc); + return result.AsJson(); + } + ); + + var content = new StringContent( + requestJson, + Encoding.UTF8, + "application/json"); + + var response = await token.Match( + async authToken => await _httpClient + .WithAuthorizationHeader(authToken) + .PostAsync(issuerMetadata.CredentialEndpoint, content), + async dPopToken => + { + var config = dPopToken.DPop.Config with + { + Audience = issuerMetadata.CredentialEndpoint.ToStringWithoutTrail(), + OAuthToken = dPopToken.Token + }; + + var dPopResponse = await _dPopHttpClient.Post( + issuerMetadata.CredentialEndpoint, + content, + config); + + return dPopResponse.ResponseMessage; + }); + + var responseContent = await response.Content.ReadAsStringAsync(); + + return + from jObject in JsonFun.ParseAsJObject(responseContent) + from credResponse in CredentialResponse.ValidCredentialResponse(jObject, keyId) + select credResponse; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs new file mode 100644 index 00000000..a72c0652 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs @@ -0,0 +1,27 @@ +using LanguageExt; +using Newtonsoft.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models; + +/// +/// Represents a credential request made by a client to the Credential Endpoint. +/// This request contains the format of the credential, the type of credential, +/// and a proof of possession of the key material the issued credential shall be bound to. +/// +public record CredentialRequest(Option Proof, Format Format) +{ + /// + /// Gets the proof of possession of the key material the issued credential shall be bound to. + /// + [JsonProperty("proof")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Proof { get; } = Proof; + + /// + /// Gets the format of the credential to be issued. + /// + [JsonProperty("format")] + public Format Format { get; } = Format; +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/Mdoc/MdocCredentialRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/Mdoc/MdocCredentialRequest.cs new file mode 100644 index 00000000..15b9f8cc --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/Mdoc/MdocCredentialRequest.cs @@ -0,0 +1,43 @@ +using LanguageExt; +using Newtonsoft.Json.Linq; +using WalletFramework.MdocLib; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models.Mdoc; + +public record MdocCredentialRequest +{ + public CredentialRequest VciRequest { get; } + + public DocType DocType { get; } + + public Option NamespacedData { get; } + + public MdocCredentialRequest(CredentialRequest credentialRequest, MdocConfiguration configuration) + { + VciRequest = credentialRequest; + DocType = configuration.DocType; + + // TODO: Decide if this should be true or false + NamespacedData = false; + } +} + +public static class MdocCredentialRequestFun +{ + public static string AsJson(this MdocCredentialRequest request) + { + var json = new JObject(); + + var vciRequest = JObject.FromObject(request.VciRequest); + foreach (var property in vciRequest.Properties()) + { + json.Add(property); + } + + var vct = JToken.FromObject(request.DocType); + json.Add("doctype", vct); + + return json.ToString(); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/ProofOfPossession.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/ProofOfPossession.cs new file mode 100644 index 00000000..f653ac53 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/ProofOfPossession.cs @@ -0,0 +1,23 @@ +using Newtonsoft.Json; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models; + +/// +/// Represents the proof of possession of the key material that the issued credential is bound to. +/// This contains the JWT that acts as the proof of possession, with the proof type being "jwt". +/// +// TODO: Unpure +public class ProofOfPossession +{ + /// + /// Gets or sets the JWT that acts as the proof of possession of the key material the issued credential is bound to. + /// + [JsonProperty("jwt")] + public string Jwt { get; set; } + + /// + /// Gets or sets the type of proof, expected to be "jwt". + /// + [JsonProperty("proof_type")] + public string ProofType { get; set; } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/SdJwt/SdJwtCredentialRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/SdJwt/SdJwtCredentialRequest.cs new file mode 100644 index 00000000..088c48a9 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/SdJwt/SdJwtCredentialRequest.cs @@ -0,0 +1,41 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.SdJwtVc.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models.SdJwt; + +public record SdJwtCredentialRequest +{ + public CredentialRequest VciRequest { get; } + + /// + /// Gets the verifiable credential type (vct). + /// + [JsonProperty("vct")] + public Vct Vct { get; } + + internal SdJwtCredentialRequest(CredentialRequest vciRequest, Vct vct) + { + VciRequest = vciRequest; + Vct = vct; + } +} + +public static class SdJwtCredentialRequestFun +{ + public static string AsJson(this SdJwtCredentialRequest request) + { + var json = new JObject(); + + var vciRequest = JObject.FromObject(request.VciRequest); + foreach (var property in vciRequest.Properties()) + { + json.Add(property); + } + + var vct = JToken.FromObject(request.Vct); + json.Add("vct", vct); + + return json.ToString(); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/CredentialResponse.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/CredentialResponse.cs new file mode 100644 index 00000000..d817c7c1 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/CredentialResponse.cs @@ -0,0 +1,139 @@ +using System.Globalization; +using LanguageExt; +using Newtonsoft.Json.Linq; +using OneOf; +using WalletFramework.Core.Cryptography.Models; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Functional.Errors; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Errors; +using WalletFramework.Oid4Vc.Oid4Vci.CredResponse.Mdoc; +using WalletFramework.Oid4Vc.Oid4Vci.CredResponse.SdJwt; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredResponse; + +/// +/// Represents a Credential Response. The response can be either immediate or deferred. In the synchronous response, +/// the issued Credential is immediately returned to the client. In the deferred response, a transaction ID +/// is sent to the client, which will be used later to retrieve the Credential once it's ready. +/// +public record CredentialResponse +{ + /// + /// + /// Credential: Contains issued Credential. It MUST be present when transaction_id is not returned. It MAY be a + /// string or an object, depending on the Credential format + /// + /// + /// TransactionId: String identifying a Deferred Issuance transaction. This claim is contained in the response if + /// the Credential Issuer was unable to immediately issue the Credential. The value is subsequently used to obtain the + /// respective Credential with the Deferred Credential Endpoint. It MUST be present when the + /// credential parameter is not returned. It MUST be invalidated after the Credential for which it was meant + /// has been obtained by the Wallet + /// + /// + public OneOf CredentialOrTransactionId { get; } + + /// + /// OPTIONAL. JSON string containing a nonce to be used to create a proof of possession of key material + /// when requesting a Credential + /// + public Option CNonce { get; } + + /// + /// OPTIONAL. JSON integer denoting the lifetime in seconds of the c_nonce + /// + public Option CNonceExpiresIn { get; } + + /// + /// The KeyId for the key which was used for the Key-Binding Proof + /// + public KeyId KeyId { get; } + + private CredentialResponse( + OneOf credentialOrTransactionId, + Option cNonce, + Option cNonceExpiresIn, + KeyId keyId) + { + CNonceExpiresIn = cNonceExpiresIn; + CredentialOrTransactionId = credentialOrTransactionId; + CNonce = cNonce; + KeyId = keyId; + } + + private static CredentialResponse Create( + OneOf credentialOrTransactionId, + Option cNonce, + Option cNonceExpiresIn, + KeyId keyId) => + new(credentialOrTransactionId, cNonce, cNonceExpiresIn, keyId); + + public static Validation ValidCredentialResponse(JObject response, KeyId keyId) + { + // TODO: Implement transactionID + var credential = + from jToken in response.GetByKey("credential") + from jValue in jToken.ToJValue() + from cred in Credential.ValidCredential(jValue) + select (OneOf)cred; + + var cNonce = response + .GetByKey("c_nonce") + .OnSuccess(token => token.ToJValue()) + .OnSuccess(value => + { + var str = value.ToString(CultureInfo.InvariantCulture); + if (string.IsNullOrWhiteSpace(str)) + { + return new StringIsNullOrWhitespaceError(); + } + + return ValidationFun.Valid(str); + }) + .ToOption(); + + var cNonceExpiresIn = response + .GetByKey("c_nonce_expires_in") + .OnSuccess(token => token.ToJValue()) + .OnSuccess(value => + { + var str = value.ToString(CultureInfo.InvariantCulture); + if (int.TryParse(str, out var result)) + { + return ValidationFun.Valid(result); + } + + return new JValueIsNotAnIntError(str); + }) + .ToOption(); + + return ValidationFun.Valid(Create) + .Apply(credential) + .Apply(cNonce) + .Apply(cNonceExpiresIn) + .Apply(keyId); + } + + public readonly struct Credential + { + public OneOf Value { get; } + + private Credential(OneOf value) + { + Value = value; + } + + public static Validation ValidCredential(JValue credential) + { + var firstValid = new List> + { + value => EncodedSdJwt.ValidEncodedSdJwt(value).OnSuccess(sdJwt => new Credential(sdJwt)), + value => EncodedMdoc.ValidEncodedMdoc(value).OnSuccess(mdoc => new Credential(mdoc)) + } + .FirstValid(); + + return firstValid(credential); + } + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/Mdoc/EncodedMdoc.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/Mdoc/EncodedMdoc.cs new file mode 100644 index 00000000..4b6daf84 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/Mdoc/EncodedMdoc.cs @@ -0,0 +1,31 @@ +using System.Globalization; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredResponse.Mdoc; + +public record EncodedMdoc +{ + private string Value { get; } + + public MdocLib.Mdoc Decoded { get; } + + private EncodedMdoc(string value, MdocLib.Mdoc decoded) + { + Value = value; + Decoded = decoded; + } + + public override string ToString() => Value; + + public static implicit operator string(EncodedMdoc encodedMdoc) => encodedMdoc.Value; + + public static Validation ValidEncodedMdoc(JValue mdoc) + { + var str = mdoc.ToString(CultureInfo.InvariantCulture); + + return MdocLib.Mdoc + .ValidMdoc(str) + .OnSuccess(mdoc1 => new EncodedMdoc(str, mdoc1)); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/SdJwt/EncodedSdJwt.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/SdJwt/EncodedSdJwt.cs new file mode 100644 index 00000000..6b0364d3 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/SdJwt/EncodedSdJwt.cs @@ -0,0 +1,38 @@ +using System.Globalization; +using Newtonsoft.Json.Linq; +using SD_JWT.Models; +using WalletFramework.Core.Functional; +using WalletFramework.Oid4Vc.Oid4Vci.CredResponse.SdJwt.Errors; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredResponse.SdJwt; + +public record EncodedSdJwt +{ + private string Value { get; } + + public SdJwtDoc Decoded { get; } + + private EncodedSdJwt(string value, SdJwtDoc decoded) + { + Value = value; + Decoded = decoded; + } + + public override string ToString() => Value; + + public static implicit operator string(EncodedSdJwt encodedSdJwt) => encodedSdJwt.Value; + + public static Validation ValidEncodedSdJwt(JValue sdJwt) + { + var str = sdJwt.ToString(CultureInfo.InvariantCulture); + try + { + var sdJwtDoc = new SdJwtDoc(str); + return new EncodedSdJwt(str, sdJwtDoc); + } + catch (Exception e) + { + return new SdJwtParsingError(sdJwt, e); + } + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/SdJwt/Errors/SdJwtError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/SdJwt/Errors/SdJwtError.cs new file mode 100644 index 00000000..b7be1940 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/SdJwt/Errors/SdJwtError.cs @@ -0,0 +1,8 @@ +using System.Globalization; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.CredResponse.SdJwt.Errors; + +public record SdJwtParsingError(JValue Value, Exception E) + : Error($"The encoded SD-JWT could not be parsed. Value is: {Value.ToString(CultureInfo.InvariantCulture)}", E); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/TransactionId.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/TransactionId.cs new file mode 100644 index 00000000..49cc8002 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredResponse/TransactionId.cs @@ -0,0 +1,6 @@ +namespace WalletFramework.Oid4Vc.Oid4Vci.CredResponse; + +public struct TransactionId +{ + +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Extensions/Oid4VciHttpClientExtensions.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Extensions/Oid4VciHttpClientExtensions.cs index 3b2bb5df..2f044579 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Extensions/Oid4VciHttpClientExtensions.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Extensions/Oid4VciHttpClientExtensions.cs @@ -1,22 +1,24 @@ -using WalletFramework.Oid4Vc.Oid4Vci.Models.DPop; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; -namespace WalletFramework.Oid4Vc.Oid4Vci.Extensions +namespace WalletFramework.Oid4Vc.Oid4Vci.Extensions; + +internal static class Oid4VciHttpClientExtensions { - internal static class Oid4VciHttpClientExtensions + public static HttpClient WithDPopHeader(this HttpClient httpClient, string dPopProofJwt) { - public static void AddDPopHeader(this HttpClient httpClient, string dPopProofJwt) - { - httpClient.DefaultRequestHeaders.Remove("DPoP"); - httpClient.DefaultRequestHeaders.Add("DPoP", dPopProofJwt); - } - - public static void AddAuthorizationHeader(this HttpClient httpClient, OAuthToken oAuthToken) - { - httpClient.DefaultRequestHeaders.Remove("Authorization"); - httpClient.DefaultRequestHeaders.Add( - "Authorization", - $"{oAuthToken.TokenResponse.TokenType} {oAuthToken.TokenResponse.AccessToken}" - ); - } + httpClient.DefaultRequestHeaders.Remove("DPoP"); + httpClient.DefaultRequestHeaders.Add("DPoP", dPopProofJwt); + + return httpClient; + } + + public static HttpClient WithAuthorizationHeader(this HttpClient httpClient, OAuthToken oAuthToken) + { + httpClient.DefaultRequestHeaders.Remove("Authorization"); + httpClient.DefaultRequestHeaders.Add( + "Authorization", + $"{oAuthToken.TokenType} {oAuthToken.AccessToken}"); + + return httpClient; } } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/MdocFun.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/MdocFun.cs new file mode 100644 index 00000000..5ed02a39 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/MdocFun.cs @@ -0,0 +1,41 @@ +using LanguageExt; +using WalletFramework.MdocVc; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Implementations; + +public static class MdocFun +{ + public static Option> CreateMdocDisplays(MdocConfiguration configuration) + { + var claimsDisplays = + from claimsMetadata in configuration.Claims + from dict in claimsMetadata.ToClaimsDisplays() + select dict; + + var credentialConfigurationDisplay = configuration.CredentialConfiguration.Display; + var result = + from credentialDisplays in credentialConfigurationDisplay + let mdocDisplays = credentialDisplays.Select(credentialDisplay => + { + var logo = + from credentialLogo in credentialDisplay.Logo + from uri in credentialLogo.Uri + select new MdocLogo(uri); + + var mdocName = + from credentialName in credentialDisplay.Name + from name in MdocName.OptionMdocName(credentialName.ToString()) + select name; + + var backgroundColor = credentialDisplay.BackgroundColor; + var textColor = credentialDisplay.TextColor; + var locale = credentialDisplay.Locale; + + return new MdocDisplay(logo, mdocName, backgroundColor, textColor, locale, claimsDisplays); + }).ToList() + select mdocDisplays; + + return result; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/MdocStorage.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/MdocStorage.cs new file mode 100644 index 00000000..95cfc647 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/MdocStorage.cs @@ -0,0 +1,68 @@ +using Hyperledger.Aries.Agents; +using Hyperledger.Aries.Storage; +using LanguageExt; +using WalletFramework.Core.Credentials; +using WalletFramework.Core.Functional; +using WalletFramework.MdocVc; +using WalletFramework.Oid4Vc.Oid4Vci.Abstractions; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Implementations; + +public class MdocStorage : IMdocStorage +{ + public MdocStorage(IAgentProvider agentProvider, IWalletRecordService recordService) + { + _agentProvider = agentProvider; + _recordService = recordService; + } + + private readonly IAgentProvider _agentProvider; + private readonly IWalletRecordService _recordService; + + public async Task Add(MdocRecord record) + { + var context = await _agentProvider.GetContextAsync(); + await _recordService.AddAsync(context.Wallet, record); + return Unit.Default; + } + + public async Task> Get(CredentialId id) + { + var context = await _agentProvider.GetContextAsync(); + return await _recordService.GetAsync(context.Wallet, id, MdocRecordFun.DecodeFromJson); + } + + public async Task>> List( + Option query, + int count = 100, + int skip = 0) + { + var context = await _agentProvider.GetContextAsync(); + var list = await _recordService.SearchAsync( + context.Wallet, + query.ToNullable(), + null, + count, + skip, + MdocRecordFun.DecodeFromJson); + + if (list.Count == 0) + return Option>.None; + + return list; + } + + public async Task Update(MdocRecord record) + { + var context = await _agentProvider.GetContextAsync(); + await _recordService.Update(context.Wallet, record); + return Unit.Default; + } + + public async Task Delete(MdocRecord record) + { + var context = await _agentProvider.GetContextAsync(); + await _recordService.Delete(context.Wallet, record); + return Unit.Default; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/Oid4VciClientService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/Oid4VciClientService.cs new file mode 100644 index 00000000..834ecd54 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/Oid4VciClientService.cs @@ -0,0 +1,351 @@ +using System.Security.Cryptography; +using System.Text; +using Hyperledger.Aries.Agents; +using LanguageExt; +using Microsoft.IdentityModel.Tokens; +using WalletFramework.Oid4Vc.Oid4Vci.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; +using WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; +using OneOf; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Localization; +using WalletFramework.MdocVc; +using WalletFramework.SdJwtVc.Models.Records; +using WalletFramework.SdJwtVc.Services.SdJwtVcHolderService; +using static Newtonsoft.Json.JsonConvert; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Implementations; + +/// +public class Oid4VciClientService : IOid4VciClientService +{ + private const string AuthorizationCodeGrantTypeIdentifier = "authorization_code"; + private const string PreAuthorizedCodeGrantTypeIdentifier = "urn:ietf:params:oauth:grant-type:pre-authorized_code"; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The credential request service. + /// + /// + /// The factory to create instances of . Used for making HTTP + /// requests. + /// + /// The authorization record service + /// + /// The token service. + /// + /// + public Oid4VciClientService( + IAgentProvider agentProvider, + ICredentialOfferService credentialOfferService, + ICredentialRequestService credentialRequestService, + IMdocStorage mdocStorage, + IIssuerMetadataService issuerMetadataService, + IHttpClientFactory httpClientFactory, + IAuthFlowSessionStorage authFlowSessionAuthFlowSessionStorage, + ISdJwtVcHolderService sdJwtService, + ITokenService tokenService) + { + _agentProvider = agentProvider; + _credentialOfferService = credentialOfferService; + _credentialRequestService = credentialRequestService; + _httpClient = httpClientFactory.CreateClient(); + _issuerMetadataService = issuerMetadataService; + _mdocStorage = mdocStorage; + _authFlowSessionStorage = authFlowSessionAuthFlowSessionStorage; + _sdJwtService = sdJwtService; + _tokenService = tokenService; + } + + private readonly HttpClient _httpClient; + private readonly IAgentProvider _agentProvider; + private readonly IAuthFlowSessionStorage _authFlowSessionStorage; + private readonly ICredentialOfferService _credentialOfferService; + private readonly ICredentialRequestService _credentialRequestService; + private readonly IIssuerMetadataService _issuerMetadataService; + private readonly IMdocStorage _mdocStorage; + private readonly ISdJwtVcHolderService _sdJwtService; + private readonly ITokenService _tokenService; + + /// + public async Task InitiateAuthFlow(CredentialOfferMetadata offer, ClientOptions clientOptions) + { + var authorizationCodeParameters = CreateAndStoreCodeChallenge(); + var sessionId = VciSessionId.CreateSessionId(); + var issuerMetadata = offer.IssuerMetadata; + + var scopes = offer + .CredentialOffer + .CredentialConfigurationIds + .Select(id => issuerMetadata.CredentialConfigurationsSupported[id]) + .Select(oneOf => oneOf.Match( + sdJwt => sdJwt.CredentialConfiguration.Scope.OnSome(scope => scope.ToString()), + mdoc => mdoc.CredentialConfiguration.Scope.OnSome(scope => scope.ToString()) + )) + .Where(option => option.IsSome) + .Select(option => option.Fallback(string.Empty)); + + var scope = string.Join(" ", scopes); + + var authorizationDetails = issuerMetadata + .CredentialConfigurationsSupported + .Where(config => offer.CredentialOffer.CredentialConfigurationIds.Contains(config.Key)) + .Select(pair => pair.Value.Match( + sdJwt => new AuthorizationDetails( + null, + sdJwt.Vct.ToString(), + pair.Key.ToString(), + issuerMetadata.AuthorizationServers.ToNullable()?.Select(id => id.ToString()).ToArray(), + null + ), + mdoc => new AuthorizationDetails( + null, + null, + pair.Key.ToString(), + issuerMetadata.AuthorizationServers.ToNullable()?.Select(id => id.ToString()).ToArray(), + mdoc.DocType.ToString())) + ); + + var authCode = + from grants in offer.CredentialOffer.Grants + from code in grants.AuthorizationCode + select code; + + var issuerState = + from code in authCode + from issState in code.IssuerState + select issState; + + var par = new PushedAuthorizationRequest( + sessionId, + clientOptions, + authorizationCodeParameters, + authorizationDetails.ToArray(), + scope, + issuerState.ToNullable(), + null, + null); + + var authServerMetadata = + await FetchAuthorizationServerMetadataAsync(issuerMetadata); + + _httpClient.DefaultRequestHeaders.Clear(); + var response = await _httpClient.PostAsync( + authServerMetadata.PushedAuthorizationRequestEndpoint, + par.ToFormUrlEncoded() + ); + + var parResponse = DeserializeObject(await response.Content.ReadAsStringAsync()) + ?? throw new InvalidOperationException("Failed to deserialize the PAR response."); + + var authorizationRequestUri = new Uri(authServerMetadata.AuthorizationEndpoint + + "?client_id=" + par.ClientId + + "&request_uri=" + System.Net.WebUtility.UrlEncode(parResponse.RequestUri.ToString())); + + var authorizationData = new AuthorizationData( + clientOptions, + issuerMetadata, + authServerMetadata, + offer.CredentialOffer.CredentialConfigurationIds); + + var context = await _agentProvider.GetContextAsync(); + await _authFlowSessionStorage.StoreAsync( + context, + authorizationData, + authorizationCodeParameters, + sessionId); + + return authorizationRequestUri; + } + + public async Task>> AcceptOffer(CredentialOfferMetadata credentialOfferMetadata, string? transactionCode) + { + var issuerMetadata = credentialOfferMetadata.IssuerMetadata; + // TODO: Support multiple configs + var configId = credentialOfferMetadata.CredentialOffer.CredentialConfigurationIds.First(); + var configuration = issuerMetadata.CredentialConfigurationsSupported[configId]; + var preAuthorizedCode = + from grants in credentialOfferMetadata.CredentialOffer.Grants + from preAuthCode in grants.PreAuthorizedCode + select preAuthCode.Value; + + var tokenRequest = new TokenRequest + { + GrantType = PreAuthorizedCodeGrantTypeIdentifier, + PreAuthorizedCode = preAuthorizedCode.ToNullable(), + TransactionCode = transactionCode + }; + + var authorizationServerMetadata = await FetchAuthorizationServerMetadataAsync(issuerMetadata); + + var token = await _tokenService.RequestToken( + tokenRequest, + authorizationServerMetadata); + + var validResponse = await _credentialRequestService.RequestCredentials( + configuration, + issuerMetadata, + token, + Option.None); + + var result = + from response in validResponse + let credentialOrTransactionId = response.CredentialOrTransactionId + select credentialOrTransactionId.Match( + async credential => await credential.Value.Match>>( + async sdJwt => + { + var record = sdJwt.Decoded.ToRecord(configuration.AsT0, issuerMetadata, response.KeyId); + var context = await _agentProvider.GetContextAsync(); + await _sdJwtService.SaveAsync(context, record); + return record; + }, + async mdoc => + { + var displays = MdocFun.CreateMdocDisplays(configuration.AsT1); + var record = mdoc.Decoded.ToRecord(displays); + await _mdocStorage.Add(record); + return record; + }), + // ReSharper disable once UnusedParameter.Local + transactionId => throw new NotImplementedException()); + + return await result.OnSuccess(task => task); + } + + public async Task> ProcessOffer(Uri credentialOffer, Option language) + { + var locale = language.Match( + some => some, + () => Constants.DefaultLocale); + + var result = + from offer in _credentialOfferService.ProcessCredentialOffer(credentialOffer, locale) + from metadata in _issuerMetadataService.ProcessMetadata(offer.CredentialIssuer, locale) + select new CredentialOfferMetadata(offer, metadata); + + return await result; + } + + /// + public async Task>> RequestCredential(IssuanceSession issuanceSession) + { + var context = await _agentProvider.GetContextAsync(); + + var session = await _authFlowSessionStorage.GetAsync(context, issuanceSession.SessionId); + + var credConfiguration = session + .AuthorizationData + .IssuerMetadata + .CredentialConfigurationsSupported + .Where(config => session.AuthorizationData.CredentialConfigurationIds.Contains(config.Key)) + .Select(pair => pair.Value) + .First(); + + var tokenRequest = new TokenRequest + { + GrantType = AuthorizationCodeGrantTypeIdentifier, + RedirectUri = session.AuthorizationData.ClientOptions.RedirectUri + "?session=" + session.SessionId, + CodeVerifier = session.AuthorizationCodeParameters.Verifier, + Code = issuanceSession.Code, + ClientId = session.AuthorizationData.ClientOptions.ClientId + }; + + var token = await _tokenService.RequestToken( + tokenRequest, + session.AuthorizationData.AuthorizationServerMetadata); + + var validResponse = await _credentialRequestService.RequestCredentials( + credConfiguration, + session.AuthorizationData.IssuerMetadata, + token, + session.AuthorizationData.ClientOptions); + + await _authFlowSessionStorage.DeleteAsync(context, session.SessionId); + + var result = + from response in validResponse + let credentialOrTransactionId = response.CredentialOrTransactionId + select credentialOrTransactionId.Match( + async credential => await credential.Value.Match>>( + async sdJwt => + { + var record = sdJwt.Decoded.ToRecord(credConfiguration.AsT0, session.AuthorizationData.IssuerMetadata, response.KeyId); + await _sdJwtService.SaveAsync(context, record); + return record; + }, + async mdoc => + { + var displays = MdocFun.CreateMdocDisplays(credConfiguration.AsT1); + var record = mdoc.Decoded.ToRecord(displays); + await _mdocStorage.Add(record); + return record; + }), + // ReSharper disable once UnusedParameter.Local + transactionId => throw new NotImplementedException()); + + return await result.OnSuccess(task => task); + } + + private static AuthorizationCodeParameters CreateAndStoreCodeChallenge() + { + var rng = new RNGCryptoServiceProvider(); + var randomNumber = new byte[32]; + rng.GetBytes(randomNumber); + + var codeVerifier = Base64UrlEncoder.Encode(randomNumber); + + var sha256 = SHA256.Create(); + var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier)); + + var codeChallenge = Base64UrlEncoder.Encode(bytes); + + return new AuthorizationCodeParameters(codeChallenge, codeVerifier); + } + + private async Task FetchAuthorizationServerMetadataAsync(IssuerMetadata issuerMetadata) + { + Uri credentialIssuer = issuerMetadata.CredentialIssuer; + + var authServerUrl = issuerMetadata.AuthorizationServers.Match( + servers => + { + Uri first = servers.First(); + return first; + }, + () => + { + string result; + if (string.IsNullOrWhiteSpace(credentialIssuer.AbsolutePath) || credentialIssuer.AbsolutePath == "/") + result = $"{credentialIssuer.GetLeftPart(UriPartial.Authority)}/.well-known/oauth-authorization-server"; + else + result = $"{credentialIssuer.GetLeftPart(UriPartial.Authority)}/.well-known/oauth-authorization-server" + credentialIssuer.AbsolutePath.TrimEnd('/'); + + return new Uri(result); + }); + + var getAuthServerResponse = await _httpClient.GetAsync(authServerUrl); + + if (!getAuthServerResponse.IsSuccessStatusCode) + throw new HttpRequestException( + $"Failed to get authorization server metadata. Status Code is: {getAuthServerResponse.StatusCode}" + ); + + var content = await getAuthServerResponse.Content.ReadAsStringAsync(); + + var authServer = DeserializeObject(content) + ?? throw new InvalidOperationException( + "Failed to deserialize the authorization server metadata."); + + return authServer; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/SdJwtRecordExtensions.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/SdJwtRecordExtensions.cs new file mode 100644 index 00000000..52ed4bd4 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/SdJwtRecordExtensions.cs @@ -0,0 +1,65 @@ +using System.Drawing; +using SD_JWT.Models; +using WalletFramework.Core.Cryptography.Models; +using WalletFramework.Core.Functional; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.SdJwt; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; +using WalletFramework.SdJwtVc.Models.Credential; +using WalletFramework.SdJwtVc.Models.Records; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Implementations; + +public static class SdJwtRecordExtensions +{ + public static SdJwtRecord ToRecord( + this SdJwtDoc sdJwtDoc, + SdJwtConfiguration configuration, + IssuerMetadata issuerMetadata, + KeyId keyId) + { + var claims = configuration + .Claims? + .Select(pair => (pair.Key, pair.Value)) + .ToDictionary(pair => pair.Key, pair => pair.Value); + + var display = configuration + .CredentialConfiguration + .Display + .ToNullable()? + .Select(credentialDisplay => + { + var backgroundColor = credentialDisplay.BackgroundColor.ToNullable() ?? Color.White; + var textColor = credentialDisplay.TextColor.ToNullable() ?? Color.Black; + + return new SdJwtDisplay + { + Logo = new SdJwtDisplay.SdJwtLogo + { + AltText = credentialDisplay.Logo.ToNullable()?.AltText.ToNullable(), + Uri = credentialDisplay.Logo.ToNullable()?.Uri.ToNullable()! + }, + Name = credentialDisplay.Name.ToNullable(), + BackgroundColor = backgroundColor, + Locale = credentialDisplay.Locale.ToNullable(), + TextColor = textColor + }; + }) + .ToList(); + + var issuerName = issuerMetadata + .Display + .ToNullable()? + .ToDictionary( + issuerDisplay => issuerDisplay.Locale.ToNullable()?.ToString(), + issuerDisplay => issuerDisplay.Name.ToNullable()?.ToString()); + + var record = new SdJwtRecord( + sdJwtDoc, + claims!, + display!, + issuerName!, + keyId); + + return record; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Abstractions/IIssuerMetadataService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Abstractions/IIssuerMetadataService.cs new file mode 100644 index 00000000..15dc5b60 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Abstractions/IIssuerMetadataService.cs @@ -0,0 +1,11 @@ +using WalletFramework.Core.Functional; +using WalletFramework.Core.Localization; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Models; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Issuer.Abstractions; + +public interface IIssuerMetadataService +{ + public Task> ProcessMetadata(Uri issuerEndpoint, Locale language); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Errors/CredentialEndpointError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Errors/CredentialEndpointError.cs new file mode 100644 index 00000000..a82d831e --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Errors/CredentialEndpointError.cs @@ -0,0 +1,5 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Issuer.Errors; + +public record CredentialEndpointError(Exception E) : Error("The credential enpoint could not be processed", E); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Errors/CredentialIssuerIdError.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Errors/CredentialIssuerIdError.cs new file mode 100644 index 00000000..38cf29f9 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Errors/CredentialIssuerIdError.cs @@ -0,0 +1,5 @@ +using WalletFramework.Core.Functional; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Issuer.Errors; + +public record CredentialIssuerIdError(string Value, Exception E) : Error($"The CredentialIssuerId could not be parsed. Value is {Value}", E); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Implementations/IssuerMetadataService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Implementations/IssuerMetadataService.cs new file mode 100644 index 00000000..fb79327d --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Implementations/IssuerMetadataService.cs @@ -0,0 +1,42 @@ +using WalletFramework.Core.Functional; +using WalletFramework.Core.Localization; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; +using static WalletFramework.Core.Json.JsonFun; +using static WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models.IssuerMetadata; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Issuer.Implementations; + +public class IssuerMetadataService : IIssuerMetadataService +{ + public IssuerMetadataService(IHttpClientFactory httpClientFactory) + { + _httpClient = httpClientFactory.CreateClient(); + } + + private readonly HttpClient _httpClient; + + public async Task> ProcessMetadata(Uri issuerEndpoint, Locale language) + { + var baseEndpoint = issuerEndpoint + .AbsolutePath + .EndsWith("/") + ? issuerEndpoint + : new Uri(issuerEndpoint.OriginalString + "/"); + + var metadataUrl = new Uri(baseEndpoint, ".well-known/openid-credential-issuer"); + + _httpClient.DefaultRequestHeaders.Add("Accept-Language", language); + + var response = await _httpClient.GetAsync(metadataUrl); + if (response.IsSuccessStatusCode) + { + var str = await response.Content.ReadAsStringAsync(); + return ParseAsJObject(str).OnSuccess(ValidIssuerMetadata); + } + + throw new HttpRequestException($"Failed to get Issuer metadata. Status code is {response.StatusCode}"); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/CredentialIssuerId.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/CredentialIssuerId.cs new file mode 100644 index 00000000..12e1f3a3 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/CredentialIssuerId.cs @@ -0,0 +1,36 @@ +using System.Globalization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Core.Uri; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Errors; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct CredentialIssuerId +{ + private Uri Value { get; } + + private CredentialIssuerId(Uri value) => Value = value; + + public override string ToString() => Value.ToStringWithoutTrail(); + + public static implicit operator Uri(CredentialIssuerId credentialIssuerId) => credentialIssuerId.Value; + + public static Validation ValidCredentialIssuerId(JToken credentialIssuer) => credentialIssuer.ToJValue().OnSuccess(value => + { + try + { + var str = value.ToString(CultureInfo.InvariantCulture); + var uri = new Uri(str); + return new CredentialIssuerId(uri); + } + catch (Exception e) + { + return new CredentialIssuerIdError(credentialIssuer.ToString(), e).ToInvalid(); + } + }); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerMetadata.cs new file mode 100644 index 00000000..082c0187 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerMetadata.cs @@ -0,0 +1,202 @@ +using System.Globalization; +using System.Runtime.Serialization.Formatters.Binary; +using Hyperledger.Aries.Extensions; +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.SdJwt; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Errors; +using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; +using static WalletFramework.Core.Functional.ValidationFun; +using static WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models.AuthorizationServerId; +using static WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models.CredentialIssuerId; +using static WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models.CredentialConfigurationId; +using static WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer.IssuerDisplay; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; + +/// +/// Represents the metadata of an OpenID4VCI Credential Issuer. +/// +[JsonConverter(typeof(IssuerMetadataJsonConverter))] +public record IssuerMetadata +{ + // Do not change the order of this property (must be last) otherwise the JSON serialization will not + // work properly... + /// + /// Gets a dictionary which maps a CredentialConfigurationId to its credential metadata. + /// + [JsonProperty(CredentialConfigsSupportedJsonKey)] + [JsonConverter(typeof(DictJsonConverter))] + public Dictionary CredentialConfigurationsSupported { get; } + + /// + /// Gets a list of display properties of a Credential Issuer for different languages. + /// + [JsonProperty(DisplayJsonKey)] + [JsonConverter(typeof(OptionJsonConverter>))] + public Option> Display { get; } + + /// + /// Gets the URL of the Credential Issuer's Credential Endpoint. + /// + [JsonProperty(CredentialEndpointJsonKey)] + public Uri CredentialEndpoint { get; } + + /// + /// Gets the identifier of the Credential Issuer. + /// + [JsonProperty(CredentialIssuerJsonKey)] + public CredentialIssuerId CredentialIssuer { get; } + + /// + /// Gets the identifier of the OAuth 2.0 Authorization Server that the Credential Issuer relies on for + /// authorization. If this property is omitted, it is assumed that the entity providing the Credential Issuer + /// is also acting as the Authorization Server. In such cases, the Credential Issuer's + /// identifier is used as the OAuth 2.0 Issuer value to obtain the Authorization Server + /// metadata. + /// + [JsonProperty(AuthorizationServersJsonKey)] + [JsonConverter(typeof(OptionJsonConverter>))] + public Option> AuthorizationServers { get; } + + private IssuerMetadata( + Dictionary credentialConfigurationsSupported, + Option> display, + Uri credentialEndpoint, + CredentialIssuerId credentialIssuer, + Option> authorizationServers) + { + CredentialConfigurationsSupported = credentialConfigurationsSupported; + Display = display; + CredentialEndpoint = credentialEndpoint; + CredentialIssuer = credentialIssuer; + AuthorizationServers = authorizationServers; + } + + private static IssuerMetadata Create( + Dictionary credentialConfigurationsSupported, + Option> display, + Uri credentialEndpoint, + CredentialIssuerId credentialIssuer, + Option> authorizationServers) => new( + credentialConfigurationsSupported, + display, + credentialEndpoint, + credentialIssuer, + authorizationServers); + + public static Validation ValidIssuerMetadata(JObject json) + { + var credentialConfigurations = + from jToken in json.GetByKey(CredentialConfigsSupportedJsonKey) + from jObj in jToken.ToJObject() + from dict in jObj.ToValidDictionaryAny(ValidCredentialConfigurationId, token => + { + var sdJwt = SdJwtConfiguration.ValidSdJwtCredentialConfiguration(token); + + if (sdJwt.Value.IsSuccess) + { + return sdJwt.OnSuccess(configuration => + { + SupportedCredentialConfiguration oneOf = configuration; + return oneOf; + }); + } + else + { + var mdoc = token + .ToJObject() + .OnSuccess(MdocConfiguration.ValidMdocConfiguration); + + return mdoc.OnSuccess(configuration => + { + SupportedCredentialConfiguration oneOf = configuration; + return oneOf; + }); + } + }) + select dict; + + var display = + from jToken in json.GetByKey(DisplayJsonKey).ToOption() + from jArray in jToken.ToJArray().ToOption() + from result in jArray.TraverseAny(OptionalIssuerDisplay) + select result.ToList(); + + var credentialEndpoint = + from jToken in json.GetByKey(CredentialEndpointJsonKey) + from endpoint in jToken.ToJValue().OnSuccess(value => + { + try + { + var str = value.ToString(CultureInfo.InvariantCulture); + return new Uri(str); + } + catch (Exception e) + { + return new CredentialEndpointError(e).ToInvalid(); + } + }) + select endpoint; + + var credentialIssuerId = json + .GetByKey(CredentialIssuerJsonKey) + .OnSuccess(ValidCredentialIssuerId); + + var authServers = + from jToken in json.GetByKey(AuthorizationServersJsonKey).ToOption() + from jArray in jToken.ToJArray().ToOption() + from serverIds in jArray + .TraverseAny(token => ValidAuthorizationServerId(token).ToOption()) + select serverIds; + + return Valid(Create) + .Apply(credentialConfigurations) + .Apply(display) + .Apply(credentialEndpoint) + .Apply(credentialIssuerId) + .Apply(authServers); + } + + private const string CredentialConfigsSupportedJsonKey = "credential_configurations_supported"; + private const string DisplayJsonKey = "display"; + private const string CredentialEndpointJsonKey = "credential_endpoint"; + private const string CredentialIssuerJsonKey = "credential_issuer"; + private const string AuthorizationServersJsonKey = "authorization_servers"; +} + +public sealed class IssuerMetadataJsonConverter : JsonConverter +{ + public override bool CanWrite => false; + + public override void WriteJson(JsonWriter writer, IssuerMetadata? value, JsonSerializer serializer) => + throw new NotImplementedException(); + + public override IssuerMetadata ReadJson( + JsonReader reader, + Type objectType, + IssuerMetadata? existingValue, + bool hasExistingValue, + JsonSerializer serializer) + { + var json = JObject.Load(reader); + + var result = IssuerMetadata + .ValidIssuerMetadata(json) + .Match( + metadata => metadata, + errors => + throw new InvalidOperationException($"IssuerMetadata is corrupt. Errors: {errors}") + ); + + return result; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerName.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerName.cs new file mode 100644 index 00000000..86b59bf3 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerName.cs @@ -0,0 +1,26 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Json.Converters; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct IssuerName +{ + private string Value { get; } + + private IssuerName(string value) => Value = value; + + public override string ToString() => Value; + + public static implicit operator string(IssuerName issuerName) => issuerName.Value; + + public static Option OptionIssuerName(JToken issuerName) + { + var str = issuerName.ToString(); + return string.IsNullOrWhiteSpace(str) + ? Option.None + : new IssuerName(str); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationCodeParameters.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationCodeParameters.cs deleted file mode 100644 index b6e0ffd1..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationCodeParameters.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization -{ - /// - /// Represents the parameters required for the authorization during the VCI authorization code flow. - /// The code itself will be part of the Client redirect uri that is created by the authorization server upon - /// successful authorization. - /// - public record AuthorizationCodeParameters - { - /// - /// Gets the code challenge. - /// - public string Challenge { get; } - - /// - /// Gets the code challenge method. SHA-256 is the only supported method. - /// - public string CodeChallengeMethod => "S256"; - - /// - /// Gets the code verifier. - /// - public string Verifier { get; } - - [JsonConstructor] - internal AuthorizationCodeParameters(string challenge, string verifier) - { - if (string.IsNullOrWhiteSpace(challenge) || string.IsNullOrWhiteSpace(verifier)) - { - throw new ArgumentException("Authorization Code Parameters cannot be null or empty."); - } - - Challenge = challenge; - Verifier = verifier; - } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationData.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationData.cs deleted file mode 100644 index b1073140..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationData.cs +++ /dev/null @@ -1,27 +0,0 @@ -using WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialOffer.GrantTypes; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization; - -public record AuthorizationData -{ - public ClientOptions ClientOptions { get; } - - public MetadataSet MetadataSet { get; } - - public string[] CredentialConfigurationIds { get; } - - public AuthorizationCode? AuthorizationCode { get; } - - public AuthorizationData( - ClientOptions clientOptions, - MetadataSet metadataSet, - string[] credentialConfigurationIds, - AuthorizationCode? authorizationCode) - { - ClientOptions = clientOptions; - MetadataSet = metadataSet; - CredentialConfigurationIds = credentialConfigurationIds; - AuthorizationCode = authorizationCode; - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationDetails.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationDetails.cs deleted file mode 100644 index 054993e0..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationDetails.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization -{ - /// - /// Represents the authorization details. - /// - internal record AuthorizationDetails - { - /// - /// Gets the type of the credential. - /// - [JsonProperty("type")] - public string Type { get; } = "openid_credential"; - - /// - /// Gets or Sets the credential configuration id. - /// - [JsonProperty("credential_configuration_id")] - public string CredentialConfigurationId { get; } - - /// - /// - /// - [JsonProperty("locations", NullValueHandling = NullValueHandling.Ignore)] - public string[]? Locations { get; } - - internal AuthorizationDetails( - string credentialConfigurationId, - string[]? locations) - { - if (string.IsNullOrWhiteSpace(credentialConfigurationId)) - { - throw new ArgumentException("CredentialConfigurationId must be provided."); - } - - CredentialConfigurationId = credentialConfigurationId; - Locations = locations; - } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationServerMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationServerMetadata.cs deleted file mode 100644 index 3664eb96..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/AuthorizationServerMetadata.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization -{ - /// - /// Represents the metadata associated with an OAuth 2.0 Authorization Server. - /// - public class AuthorizationServerMetadata - { - /// - /// Gets or sets the issuer location for the OAuth 2.0 Authorization Server. - /// - [JsonProperty("issuer")] - public string Issuer { get; set; } - - /// - /// Gets or sets the URL of the OAuth 2.0 token endpoint. - /// Clients use this endpoint to obtain an access token by presenting its authorization grant or refresh token. - /// - [JsonProperty("token_endpoint")] - public string TokenEndpoint { get; set; } - - /// - /// Gets or sets the URL of the OAuth 2.0 JSON Web Key Set (JWKS) document. - /// Clients use this to verify the signatures from the Authorization Server. - /// - [JsonProperty("jwks_uri")] - public string JwksUri { get; set; } - - /// - /// Gets or sets the URL of the OAuth 2.0 authorization endpoint. - /// - [JsonProperty("authorization_endpoint")] - public string AuthorizationEndpoint { get; set; } - - /// - /// Gets or sets the response types that the OAuth 2.0 Authorization Server supports. - /// These types determine how the Authorization Server responds to client requests. - /// - [JsonProperty("response_types_supported", NullValueHandling = NullValueHandling.Ignore)] - public string[]? ResponseTypesSupported { get; set; } - - /// - /// Gets or sets the supported authentication methods the OAuth 2.0 Authorization Server supports - /// when calling the token endpoint. - /// - [JsonProperty("token_endpoint_auth_methods_supported")] - public string[] TokenEndpointAuthMethodsSupported { get; set; } - - /// - /// Gets or sets the supported token endpoint authentication signing algorithms. - /// This indicates which algorithms the Authorization Server supports when receiving requests - /// at the token endpoint. - /// - [JsonProperty("token_endpoint_auth_signing_alg_values_supported")] - public string[] TokenEndpointAuthSigningAlgValuesSupported { get; set; } - - /// - /// Gets or sets the supported DPoP signing algorithms. - /// This indicates which algorithms the Authorization Server supports for DPoP Proof JWTs. - /// - [JsonProperty("dpop_signing_alg_values_supported")] - public string[]? DPopSigningAlgValuesSupported { get; set; } - - /// - /// Gets or sets the URL of the endpoint where the wallet sends the Pushed Authorization Request (PAR) to. - /// - [JsonProperty("pushed_authorization_request_endpoint")] - public string? PushedAuthorizationRequestEndpoint { get; set; } - - /// - /// Gets or sets a value indicating whether the Authorization Server requires the use of Pushed Authorization Requests. - /// - [JsonProperty("require_pushed_authorization_requests")] - public bool? RequirePushedAuthorizationRequests { get; set; } - - internal bool IsDPoPSupported => DPopSigningAlgValuesSupported != null && DPopSigningAlgValuesSupported.Contains("ES256"); - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/ClientOptions.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/ClientOptions.cs deleted file mode 100644 index 5d2c8482..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/ClientOptions.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization -{ - /// - /// Represents the client options that are used during the VCI Authorization Code Flow. Here the wallet acts as the client. - /// - public record ClientOptions - { - /// - /// Identifier of the client (wallet) - /// - public string ClientId { get; init; } - - /// - /// Identifier of the wallet issuer - /// - public string WalletIssuer { get; init; } - - /// - /// Redirect URI that the Authorization Server will use after the authorization was successful. - /// - public string RedirectUri { get; init; } - -#pragma warning disable CS8618 - /// - /// Parameterless Default Constructor. - /// - public ClientOptions() - { - } -#pragma warning restore CS8618 - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - [JsonConstructor] - public ClientOptions(string clientId, string walletIssuer, string redirectUri) - { - if (string.IsNullOrWhiteSpace(clientId) - || string.IsNullOrWhiteSpace(walletIssuer) - || !Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute)) - { - throw new ArgumentException("Invalid Client Options"); - } - - ClientId = clientId; - WalletIssuer = walletIssuer; - RedirectUri = redirectUri; - } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/PushedAuthorizationRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/PushedAuthorizationRequest.cs deleted file mode 100644 index b9dac5b5..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/PushedAuthorizationRequest.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Newtonsoft.Json; -using static Newtonsoft.Json.JsonConvert; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization -{ - internal record PushedAuthorizationRequest - { - [JsonProperty("client_id")] - public string ClientId { get; } - - [JsonProperty("response_type")] - public string ResponseType { get; } = "code"; - - [JsonProperty("redirect_uri")] - public string RedirectUri { get; } - - [JsonProperty("code_challenge")] - public string CodeChallenge { get; } - - [JsonProperty("code_challenge_method")] - public string CodeChallengeMethod { get; } - - [JsonProperty("authorization_details", NullValueHandling = NullValueHandling.Ignore)] - public AuthorizationDetails[]? AuthorizationDetails { get; } - - [JsonProperty("issuer_state", NullValueHandling = NullValueHandling.Ignore)] - public string? IssuerState { get; } - - [JsonProperty("wallet_issuer", NullValueHandling = NullValueHandling.Ignore)] - public string? WalletIssuer { get; } - - [JsonProperty("user_hint", NullValueHandling = NullValueHandling.Ignore)] - public string? UserHint { get; } - - [JsonProperty("scope", NullValueHandling = NullValueHandling.Ignore)] - public string? Scope { get; } - - [JsonProperty("resource", NullValueHandling = NullValueHandling.Ignore)] - public string? Resource { get; } - - public PushedAuthorizationRequest( - VciSessionId sessionId, - ClientOptions clientOptions, - AuthorizationCodeParameters authorizationCodeParameters, - AuthorizationDetails[]? authorizationDetails, - string? scope, - string? issuerState, - string? userHint, - string? resource) - { - ClientId = clientOptions.ClientId; - RedirectUri = clientOptions.RedirectUri + "?session=" + sessionId; - WalletIssuer = clientOptions.WalletIssuer; - CodeChallenge = authorizationCodeParameters.Challenge; - CodeChallengeMethod = authorizationCodeParameters.CodeChallengeMethod; - AuthorizationDetails = authorizationDetails; - IssuerState = issuerState; - UserHint = userHint; - Scope = scope; - Resource = resource; - } - - public FormUrlEncodedContent ToFormUrlEncoded() - { - var keyValuePairs = new List>(); - - if (!string.IsNullOrEmpty(ClientId)) - keyValuePairs.Add(new KeyValuePair("client_id", ClientId)); - - if (!string.IsNullOrEmpty(ResponseType)) - keyValuePairs.Add(new KeyValuePair("response_type", ResponseType)); - - if (!string.IsNullOrEmpty(RedirectUri)) - keyValuePairs.Add(new KeyValuePair("redirect_uri", RedirectUri)); - - if (!string.IsNullOrEmpty(CodeChallenge)) - keyValuePairs.Add(new KeyValuePair("code_challenge", CodeChallenge)); - - if (!string.IsNullOrEmpty(CodeChallengeMethod)) - keyValuePairs.Add(new KeyValuePair("code_challenge_method", CodeChallengeMethod)); - - if (AuthorizationDetails != null) - keyValuePairs.Add(new KeyValuePair("authorization_details", SerializeObject(AuthorizationDetails))); - - if (!string.IsNullOrEmpty(IssuerState)) - keyValuePairs.Add(new KeyValuePair("issuer_state", IssuerState)); - - if (!string.IsNullOrEmpty(WalletIssuer)) - keyValuePairs.Add(new KeyValuePair("wallet_issuer", WalletIssuer)); - - if (!string.IsNullOrEmpty(UserHint)) - keyValuePairs.Add(new KeyValuePair("user_hint", UserHint)); - - if (!string.IsNullOrEmpty(Scope)) - keyValuePairs.Add(new KeyValuePair("scope", Scope)); - - return new FormUrlEncodedContent(keyValuePairs); - } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/PushedAuthorizationRequestResponse.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/PushedAuthorizationRequestResponse.cs deleted file mode 100644 index fa1d8867..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/PushedAuthorizationRequestResponse.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization -{ - internal record PushedAuthorizationRequestResponse - { - [JsonProperty("request_uri")] - public Uri RequestUri { get; init; } - - [JsonProperty("expires_in")] - public string ExpiresIn { get; init; } - - [JsonConstructor] - private PushedAuthorizationRequestResponse(Uri requestUri, string expiresIn) - => (RequestUri, ExpiresIn) = (requestUri, expiresIn); - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/TokenRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/TokenRequest.cs deleted file mode 100644 index a5cfc33f..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/TokenRequest.cs +++ /dev/null @@ -1,91 +0,0 @@ -#nullable enable - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization -{ - /// - /// Represents a request for an access token from an OAuth 2.0 Authorization Server. - /// - public class TokenRequest - { - /// - /// Gets or sets the grant type of the request. Determines the type of token request being made. - /// - public string GrantType { get; set; } = null!; - - /// - /// Gets or sets the pre-authorized code. Represents the authorization to obtain specific credentials. - /// This is required if the grant type is urn:ietf:params:oauth:grant-type:pre-authorized_code. - /// - public string? PreAuthorizedCode { get; set; } - - /// - /// Gets or sets the pre-authorized code. Represents the authorization to obtain specific credentials. - /// This is required if the grant type is urn:ietf:params:oauth:grant-type:pre-authorized_code. - /// - public string? Code { get; set; } - - /// - /// Gets or sets the pre-authorized code. Represents the authorization to obtain specific credentials. - /// This is required if the grant type is urn:ietf:params:oauth:grant-type:pre-authorized_code. - /// - public string? CodeVerifier { get; set; } - - /// - /// Gets or sets the pre-authorized code. Represents the authorization to obtain specific credentials. - /// This is required if the grant type is urn:ietf:params:oauth:grant-type:pre-authorized_code. - /// - public string? ClientId { get; set; } - - /// - /// Gets or sets the pre-authorized code. Represents the authorization to obtain specific credentials. - /// This is required if the grant type is urn:ietf:params:oauth:grant-type:pre-authorized_code. - /// - public string? RedirectUri { get; set; } - - /// - /// Gets or sets the scope of the access request. Defines the permissions the client is asking for. - /// - public string? Scope { get; set; } - - /// - /// Gets or sets the transaction code. This value must be present if a transaction code was required in a previous step. - /// - public string? TransactionCode { get; set; } - - /// - /// Converts the properties of the TokenRequest instance into an FormUrlEncodedContent type suitable for HTTP POST - /// operations. - /// - /// Returns an instance of FormUrlEncodedContent containing the URL-encoded properties of the TokenRequest. - public FormUrlEncodedContent ToFormUrlEncoded() - { - var keyValuePairs = new List>(); - - if (!string.IsNullOrEmpty(GrantType)) - keyValuePairs.Add(new KeyValuePair("grant_type", GrantType)); - - if (!string.IsNullOrEmpty(PreAuthorizedCode)) - keyValuePairs.Add(new KeyValuePair("pre-authorized_code", PreAuthorizedCode)); - - if (!string.IsNullOrEmpty(Scope)) - keyValuePairs.Add(new KeyValuePair("scope", Scope)); - - if (!string.IsNullOrEmpty(TransactionCode)) - keyValuePairs.Add(new KeyValuePair("tx_code", TransactionCode)); - - if (!string.IsNullOrEmpty(Code)) - keyValuePairs.Add(new KeyValuePair("code", Code)); - - if (!string.IsNullOrEmpty(RedirectUri)) - keyValuePairs.Add(new KeyValuePair("redirect_uri", RedirectUri)); - - if (!string.IsNullOrEmpty(CodeVerifier)) - keyValuePairs.Add(new KeyValuePair("code_verifier", CodeVerifier)); - - if (!string.IsNullOrEmpty(ClientId)) - keyValuePairs.Add(new KeyValuePair("client_id", ClientId)); - - return new FormUrlEncodedContent(keyValuePairs); - } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/TokenResponse.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/TokenResponse.cs deleted file mode 100644 index 4ecea30b..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/TokenResponse.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization -{ - /// - /// Represents a successful response from the OAuth 2.0 Authorization Server containing - /// the issued access token and related information. - /// - internal class TokenResponse - { - /// - /// Indicates if the Token Request is still pending as the Credential Issuer - /// is waiting for the End-User interaction to complete. - /// - [JsonProperty("authorization_pending")] - public bool? AuthorizationPending { get; set; } - - /// - /// Gets or sets the lifetime in seconds of the c_nonce. - /// - [JsonProperty("c_nonce_expires_in")] - public int? CNonceExpiresIn { get; set; } - - /// - /// Gets or sets the lifetime in seconds of the access token. - /// - [JsonProperty("expires_in")] - public int? ExpiresIn { get; set; } - - /// - /// Gets or sets the minimum amount of time in seconds that the client should wait - /// between polling requests to the Token Endpoint. - /// - [JsonProperty("interval")] - public int? Interval { get; set; } - - /// - /// Gets or sets the access token issued by the authorization server. - /// - [JsonProperty("access_token")] - public string AccessToken { get; set; } - - /// - /// Gets or sets the nonce to be used to create a proof of possession of key material - /// when requesting a Credential. - /// - [JsonProperty("c_nonce")] - public string CNonce { get; set; } - - /// - /// Gets or sets the refresh token, which can be used to obtain new access tokens. - /// - [JsonProperty("refresh_token")] - public string RefreshToken { get; set; } - - /// - /// Gets or sets the scope of the access token. - /// - [JsonProperty("scope")] - public string Scope { get; set; } - - /// - /// Gets or sets the type of the token issued. - /// - [JsonProperty("token_type")] - public string TokenType { get; set; } - - /// - /// Gets or sets the credential identifier. - /// - [JsonProperty("credential_identifiers")] - public AuthorizationDetails? CredentialIdentifier { get; set; } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/VciAuthorizationSessionRecord.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/VciAuthorizationSessionRecord.cs deleted file mode 100644 index d45f68fc..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/VciAuthorizationSessionRecord.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Hyperledger.Aries.Storage; -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization -{ - /// - /// Represents the authorization session record. Used during the VCI Authorization Code Flow to hold session relevant information. - /// - public sealed class VciAuthorizationSessionRecord : RecordBase - { - /// - /// The session specific id. - /// - [JsonIgnore] - public VciSessionId SessionId - { - get => VciSessionId.CreateSessionId(Get()); - set => Set((string)value, false); - } - - /// - /// The Authorization Code from the CredentialOffer associated with the session. Only needed within the Pre Authorization Code flow. - /// - public AuthorizationData AuthorizationData { get; } - - /// - /// The parameters for the 'authorization_code' grant type. - /// - public AuthorizationCodeParameters AuthorizationCodeParameters { get; } - - /// - /// Initializes a new instance of the class. - /// - public override string TypeName => "AF.VciAuthorizationSessionRecord"; - -#pragma warning disable CS8618 - /// - /// Initializes a new instance of the class. - /// - public VciAuthorizationSessionRecord() - { - } -#pragma warning restore CS8618 - - /// - /// Initializes a new instance of the class. - /// - /// - /// - /// - [JsonConstructor] - public VciAuthorizationSessionRecord( - VciSessionId sessionId, - AuthorizationData authorizationData, - AuthorizationCodeParameters authorizationCodeParameters) - { - SessionId = sessionId; - Id = Guid.NewGuid().ToString(); - RecordVersion = 1; - AuthorizationCodeParameters = authorizationCodeParameters; - AuthorizationData = authorizationData; - } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/VciSessionId.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/VciSessionId.cs deleted file mode 100644 index 414cdb3f..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Authorization/VciSessionId.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization -{ - /// - /// Identifier of the authorization session during the VCI Authorization Code Flow. - /// - public struct VciSessionId - { - /// - /// Gets the value of the session identifier. - /// - private string Value { get; } - - private VciSessionId(string value) => Value = value; - - /// - /// Returns the value of the session identifier. - /// - /// - /// - public static implicit operator string(VciSessionId sessionParameters) => sessionParameters.Value; - - public static VciSessionId CreateSessionId(string sessionId) - { - if (!Guid.TryParse(sessionId, out _)) - { - throw new ArgumentException("SessionId must not be a Guid"); - } - - return new VciSessionId(sessionId); - } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/GrantTypes/AuthorizationCode.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/GrantTypes/AuthorizationCode.cs deleted file mode 100644 index c700a229..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/GrantTypes/AuthorizationCode.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialOffer.GrantTypes -{ - /// - /// Represents the parameters for the 'authorization_code' grant type. - /// - public class AuthorizationCode - { - /// - /// Gets or sets an optional string value created by the Credential Issuer, opaque to the Wallet, that is used to bind - /// the subsequent Authorization Request with the Credential Issuer to a context set up during previous steps. - /// - [JsonProperty("issuer_state")] - public string? IssuerState { get; set; } - - /// - /// Gets or sets the URL of the Authorization Server that the Wallet should use to obtain the Authorization Code. - /// - [JsonProperty("authorization_server")] - public string? AuthorizationServer { get; set; } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/GrantTypes/PreAuthorizedCode.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/GrantTypes/PreAuthorizedCode.cs deleted file mode 100644 index 43f8003b..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/GrantTypes/PreAuthorizedCode.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialOffer.GrantTypes -{ - /// - /// Represents the parameters for the 'pre-authorized_code' grant type. - /// - public class PreAuthorizedCode - { - /// - /// Gets or sets the pre-authorized code representing the Credential Issuer's authorization for the Wallet to obtain - /// Credentials of a certain type. - /// - [JsonProperty("pre-authorized_code")] - public string Value { get; set; } = null!; - - /// - /// Specifying whether the user must send a Transaction Code along with the Token Request in a Pre-Authorized Code Flow. - /// - [JsonProperty("tx_code")] - public TransactionCode? TransactionCode { get; set; } - } - - /// - /// Represents the details of the expected Transaction Code. - /// - public class TransactionCode - { - /// - /// Gets or sets the length of the transaction code. - /// - [JsonProperty("length")] - public int? Length { get; set; } - - /// - /// Gets or sets a description of the transaction code. - /// - [JsonProperty("description")] - public string? Description { get; set; } - - /// - /// Gets or sets the input mode of the transaction code which specifies the valid character set. (Must be 'numeric' ot 'text') - /// - [JsonProperty("input_mode")] - public string? InputMode { get; set; } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/Grants.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/Grants.cs deleted file mode 100644 index ec557b98..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/Grants.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Newtonsoft.Json; -using WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialOffer.GrantTypes; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialOffer -{ - /// - /// Represents the grant types that the Credential Issuer's AS is prepared to process for the credential offer. - /// - public class Grants - { - /// - /// Gets or sets the authorization_code grant type parameters. This includes an optional issuer state that is used to - /// bind the subsequent Authorization Request with the Credential Issuer to a context set up during previous steps. - /// - [JsonProperty("authorization_code")] - public AuthorizationCode? AuthorizationCode { get; set; } - - /// - /// Gets or sets the pre-authorized_code grant type parameters. This includes a required pre-authorized code - /// representing the Credential Issuer's authorization for the Wallet to obtain Credentials of a certain type, and an - /// optional boolean specifying whether a user PIN is required along with the Token Request. - /// - [JsonProperty("urn:ietf:params:oauth:grant-type:pre-authorized_code")] - public PreAuthorizedCode? PreAuthorizedCode { get; set; } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/OidCredentialOffer.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/OidCredentialOffer.cs deleted file mode 100644 index c0662a52..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialOffer/OidCredentialOffer.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialOffer -{ - /// - /// Represents an OpenID4VCI Credential Offer, which is used to obtain one or more credentials from a Credential - /// Issuer. - /// - public class OidCredentialOffer - { - /// - /// Gets or sets the JSON object indicating to the Wallet the Grant Types the Credential Issuer's AS is prepared to - /// process for this credential offer. If not present or empty, the Wallet must determine the Grant Types the - /// Credential Issuer's AS supports using the respective metadata. - /// - [JsonProperty("grants")] - public Grants? Grants { get; set; } - - /// - /// Gets or sets the list of credentials that the Wallet may request. The List contains CredentialMetadataIds - /// that must map to the keys in the credential_configurations_supported dictionary of the Issuer Metadata - /// - [JsonProperty("credential_configuration_ids")] - public List CredentialConfigurationIds { get; set; } = null!; - - /// - /// Gets or sets the URL of the Credential Issuer from where the Wallet is requested to obtain one or more Credentials - /// from. - /// - [JsonProperty("credential_issuer")] - public string CredentialIssuer { get; set; } = null!; - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialRequest/OidCredentialRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialRequest/OidCredentialRequest.cs deleted file mode 100644 index 41093c15..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialRequest/OidCredentialRequest.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Newtonsoft.Json; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential.Attributes; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialRequest -{ - /// - /// Represents a credential request made by a client to the Credential Endpoint. - /// This request contains the format of the credential, the type of credential, - /// and a proof of possession of the key material the issued credential shall be bound to. - /// - public class OidCredentialRequest - { - /// - /// Gets or sets the proof of possession of the key material the issued credential shall be bound to. - /// - [JsonProperty("proof")] - public OidProofOfPossession? Proof { get; set; } - - /// - /// Gets or sets the format of the credential to be issued. - /// - [JsonProperty("format")] - public string Format { get; set; } = null!; - - /// - /// Gets or sets the dictionary representing the attributes of the credential in different languages. - /// - [JsonProperty("claims")] - public Dictionary? Claims { get; set; } - - /// - /// Gets or sets the verifiable credential type (vct). - /// - [JsonProperty("vct")] - public string Vct { get; set; } = null!; - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialRequest/OidProofOfPossession.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialRequest/OidProofOfPossession.cs deleted file mode 100644 index 325d2716..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialRequest/OidProofOfPossession.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialRequest -{ - /// - /// Represents the proof of possession of the key material that the issued credential is bound to. - /// This contains the JWT that acts as the proof of possession, with the proof type being "jwt". - /// - public class OidProofOfPossession - { - /// - /// Gets or sets the JWT that acts as the proof of possession of the key material the issued credential is bound to. - /// - [JsonProperty("jwt")] - public string Jwt { get; set; } - - /// - /// Gets or sets the type of proof, expected to be "jwt". - /// - [JsonProperty("proof_type")] - public string ProofType { get; set; } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialResponse/OidCredentialResponse.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialResponse/OidCredentialResponse.cs deleted file mode 100644 index 79133497..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/CredentialResponse/OidCredentialResponse.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialResponse -{ - /// - /// Represents a Credential Response. The response can be either immediate or deferred. In the synchronous response, - /// the issued Credential is immediately returned to the client. In the deferred response, an acceptance token - /// is sent to the client, which will be used later to retrieve the Credential once it's ready. - /// - public class OidCredentialResponse - { - /// - /// OPTIONAL. JSON integer denoting the lifetime in seconds of the c_nonce. - /// - [JsonProperty("c_nonce_expires_in")] - public int? CNonceExpiresIn { get; set; } - - /// - /// OPTIONAL. A JSON string containing a security token subsequently used to obtain a Credential. - /// MUST be present when credential is not returned. - /// - [JsonProperty("acceptance_token")] - public string? AcceptanceToken { get; set; } - - /// - /// OPTIONAL. JSON string containing a nonce to be used to create a proof of possession of key material - /// when requesting a Credential. - /// - [JsonProperty("c_nonce")] - public string? CNonce { get; set; } - - /// - /// OPTIONAL. Contains issued Credential. MUST be present when acceptance_token is not returned. - /// MAY be a JSON string or a JSON object, depending on the Credential format. - /// - [JsonProperty("credential")] - public string? Credential { get; set; } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/DPop/OAuthToken.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/DPop/OAuthToken.cs deleted file mode 100644 index 3ee9d92e..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/DPop/OAuthToken.cs +++ /dev/null @@ -1,33 +0,0 @@ -using WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.DPop -{ - internal record OAuthToken - { - internal OAuthToken(TokenResponse tokenResponse, DPop? dPop = null) - { - TokenResponse = tokenResponse; - DPop = dPop; - } - - public TokenResponse TokenResponse { get; } - - public DPop? DPop { get; } - } - - internal record DPop(string KeyId, string? Nonce) - { - public string? Nonce { get; } = Nonce; - - public string KeyId { get; } = KeyId; - } - - internal static class OAuthTokenExtensions - { - internal static bool IsDPoPRequested(this OAuthToken oAuthToken) - { - return oAuthToken.TokenResponse.TokenType == "DPoP" - && oAuthToken.DPop != null; - } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/IssuanceSessionParameters.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/IssuanceSessionParameters.cs deleted file mode 100644 index 8c784786..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/IssuanceSessionParameters.cs +++ /dev/null @@ -1,44 +0,0 @@ -using WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization; -using static System.Web.HttpUtility; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models -{ - /// - /// Represents the parameters of an VCI Authorization Code Flow issuance session. - /// - public record IssuanceSessionParameters - { - /// - /// Gets the session identifier. - /// - public VciSessionId SessionId { get; } - - /// - /// Gets the actual authorization code that is received from the authorization server upon succesful authorization. - /// - public string Code { get; } - - private IssuanceSessionParameters(VciSessionId sessionId, string code) => (SessionId, Code) = (sessionId, code); - - /// - /// Creates a new instance of from the given . - /// - /// - /// - /// - public static IssuanceSessionParameters FromUri(Uri uri) - { - var queryParams = ParseQueryString(uri.Query); - - var code = queryParams.Get("code"); - var sessionId = queryParams.Get("session"); - - if (string.IsNullOrWhiteSpace(code) || string.IsNullOrWhiteSpace(sessionId)) - { - throw new InvalidOperationException("Query parameter 'code' and/or 'session' are missing"); - } - - return new IssuanceSessionParameters(VciSessionId.CreateSessionId(sessionId), code); - } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialDisplay.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialDisplay.cs deleted file mode 100644 index 0fb2a184..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialDisplay.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential -{ - /// - /// Represents the visual representations for the credential. - /// - public class OidCredentialDisplay - { - /// - /// Gets or sets the logo associated with this Credential. - /// - [JsonProperty("logo", NullValueHandling = NullValueHandling.Ignore)] - public OidCredentialLogo? Logo { get; set; } - - /// - /// Gets or sets the name of the Credential. - /// - [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string? Name { get; set; } - - /// - /// Gets or sets the background color for the Credential. - /// - [JsonProperty("background_color", NullValueHandling = NullValueHandling.Ignore)] - public string? BackgroundColor { get; set; } - - /// - /// Gets or sets the locale, which represents the specific culture or region. - /// - [JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)] - public string? Locale { get; set; } - - /// - /// Gets or sets the text color for the Credential. - /// - [JsonProperty("text_color", NullValueHandling = NullValueHandling.Ignore)] - public string? TextColor { get; set; } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialLogo.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialLogo.cs deleted file mode 100644 index 489f68bd..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialLogo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential -{ - /// - /// Represents the Logo for a Credential. - /// - public class OidCredentialLogo - { - /// - /// Gets or sets the alternate text that describes the logo image. This is typically used for accessibility purposes. - /// - [JsonProperty("alt_text")] - public string? AltText { get; set; } - - /// - /// Gets or sets the URL of the logo image. - /// - [JsonProperty("uri")] - public Uri Uri { get; set; } = null!; - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialMetadata.cs deleted file mode 100644 index 5c064696..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialMetadata.cs +++ /dev/null @@ -1,85 +0,0 @@ -using Newtonsoft.Json; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential.Attributes; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential -{ - /// - /// Represents the metadata of a specific type of credential that a Credential Issuer can issue. - /// - public class OidCredentialMetadata - { - /// - /// Gets or sets the verifiable credential type (vct). - /// - [JsonProperty("vct")] - public string Vct { get; set; } = null!; - - /// - /// Gets or sets the dictionary representing the attributes of the credential in different languages. - /// - [JsonProperty("claims")] - public Dictionary? Claims { get; set; } - - /// - /// Gets or sets a list of display properties of the supported credential for different languages. - /// - [JsonProperty("display", NullValueHandling = NullValueHandling.Ignore)] - public List? Display { get; set; } - - /// - /// Gets or sets a list of methods that identify how the Credential is bound to the identifier of the End-User who - /// possesses the Credential. - /// - [JsonProperty("cryptographic_binding_methods_supported", NullValueHandling = NullValueHandling.Ignore)] - public List? CryptographicBindingMethodsSupported { get; set; } - - /// - /// Gets or sets a list of identifiers for the signing algorithms that are supported by the issuer and used - /// to sign credentials. - /// - [JsonProperty("credential_signing_alg_values_supported", NullValueHandling = NullValueHandling.Ignore)] - public List? CredentialSigningAlgValuesSupported { get; set; } - - /// - /// A list of claim display names, arranged in the order in which they should be displayed by the Wallet. - /// - [JsonProperty("order", NullValueHandling = NullValueHandling.Ignore)] - public List? Order { get; set; } - - /// - /// Gets or sets the identifier for the format of the credential. - /// - [JsonProperty("format")] - public string Format { get; set; } = null!; - - /// - /// Gets or sets the unique identifier for the respective credential. - /// - [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] - public string? Id { get; set; } - - /// - /// Gets or sets a dictionary which maps a credential type to its supported signing algorithms for key proofs. - /// - [JsonProperty("proof_types_supported", NullValueHandling = NullValueHandling.Ignore)] - public Dictionary? ProofTypesSupported { get; set; } - - /// - /// Gets or sets a string indicating the credential that can be issued. - /// - [JsonProperty("scope", NullValueHandling = NullValueHandling.Ignore)] - public string? Scope { get; set; } - } - - /// - /// Represents credential type specific signing algorithm information. - /// - public class OidCredentialProofType - { - /// - /// Gets or sets the available signing algorithms for the associated credential type. - /// - [JsonProperty("proof_signing_alg_values_supported")] - public string[] ProofSigningAlgValuesSupported { get; set; } = null!; - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerDisplay.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerDisplay.cs new file mode 100644 index 00000000..9c6ce986 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerDisplay.cs @@ -0,0 +1,62 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; +using WalletFramework.Core.Localization; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; +using static WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models.IssuerName; +using static WalletFramework.Core.Localization.Locale; +using static WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer.IssuerLogo; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; + +/// +/// Represents the visual representations for the Issuer. +/// +public record IssuerDisplay +{ + /// + /// Gets the name of the Issuer + /// + [JsonProperty("name")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Name { get; } + + /// + /// Gets the locale which represents the specific culture or region + /// + [JsonProperty("locale")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Locale { get; } + + /// + /// Gets the logo of the Issuer + /// + [JsonProperty("logo")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Logo { get; } + + private IssuerDisplay( + Option name, + Option locale, + Option logo) + { + Name = name; + Locale = locale; + Logo = logo; + } + + public static Option OptionalIssuerDisplay(JToken display) => display.ToJObject().ToOption().OnSome(jObject => + { + var name = jObject.GetByKey("name").ToOption().OnSome(OptionIssuerName); + var locale = jObject.GetByKey("locale").OnSuccess(ValidLocale).ToOption(); + var logo = jObject.GetByKey("logo").ToOption().OnSome(OptionalIssuerLogo); + + if (name.IsNone && locale.IsNone && logo.IsNone) + return Option.None; + + return new IssuerDisplay(name, locale, logo); + }); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerLogo.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerLogo.cs new file mode 100644 index 00000000..fa4cf257 --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerLogo.cs @@ -0,0 +1,67 @@ +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; + +/// +/// Represents the Logo of the Issuer. +/// +public record IssuerLogo +{ + /// + /// Gets the alternate text that describes the logo image. This is typically used for accessibility purposes. + /// + [JsonProperty("alt_text")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option AltText { get; } + + /// + /// Gets the URL of the logo image. + /// + [JsonProperty("uri")] + [JsonConverter(typeof(OptionJsonConverter))] + public Option Uri { get; } + + private IssuerLogo( + Option altText, + Option uri) + { + AltText = altText; + Uri = uri; + } + + public static Option OptionalIssuerLogo(JToken logo) => logo.ToJObject().ToOption().OnSome(jObject => + { + var altText = jObject.GetByKey("alt_text").ToOption().OnSome(text => + { + var str = text.ToString(); + if (string.IsNullOrWhiteSpace(str)) + return Option.None; + + return str; + }); + + var imageUri = jObject.GetByKey("uri").ToOption().OnSome(uri => + { + try + { + var str = uri.ToString(); + var result = new Uri(str); + return result; + } + catch (Exception) + { + return Option.None; + } + }); + + if (altText.IsNone && imageUri.IsNone) + return Option.None; + + return new IssuerLogo(altText, imageUri); + }); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/OidIssuerDisplay.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/OidIssuerDisplay.cs deleted file mode 100644 index 83499f59..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/OidIssuerDisplay.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer -{ - /// - /// Represents the visual representations for the Issuer. - /// - public class OidIssuerDisplay - { - /// - /// Gets or sets the name of the Issuer. - /// - [JsonProperty("name")] - public string? Name { get; set; } - - /// - /// Gets or sets the locale, which represents the specific culture or region. - /// - [JsonProperty("locale")] - public string? Locale { get; set; } - - /// - /// Gets or sets the logo, which represents the specific culture or region.. - /// - [JsonProperty("logo", NullValueHandling = NullValueHandling.Ignore)] - public OidIssuerLogo? Logo { get; set; } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/OidIssuerLogo.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/OidIssuerLogo.cs deleted file mode 100644 index 3fa0eda0..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/OidIssuerLogo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer -{ - /// - /// Represents the Logo of the Issuer. - /// - public class OidIssuerLogo - { - /// - /// Gets or sets the alternate text that describes the logo image. This is typically used for accessibility purposes. - /// - [JsonProperty("alt_text")] - public string? AltText { get; set; } - - /// - /// Gets or sets the URL of the logo image. - /// - [JsonProperty("uri")] - public Uri Uri { get; set; } = null!; - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/OidIssuerMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/OidIssuerMetadata.cs deleted file mode 100644 index 9dda5825..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/OidIssuerMetadata.cs +++ /dev/null @@ -1,130 +0,0 @@ -using Newtonsoft.Json; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential.Attributes; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer -{ - /// - /// Represents the metadata of an OpenID4VCI Credential Issuer. - /// - public class OidIssuerMetadata - { - /// - /// Gets or sets a dictionary which maps a CredentialMetadataId to its credential metadata. - /// - [JsonProperty("credential_configurations_supported")] - public Dictionary CredentialConfigurationsSupported { get; set; } = null!; - - /// - /// Gets or sets a list of display properties of a Credential Issuer for different languages. - /// - [JsonProperty("display", NullValueHandling = NullValueHandling.Ignore)] - public List? Display { get; set; } - - /// - /// Gets or sets the URL of the Credential Issuer's Credential Endpoint. - /// - [JsonProperty("credential_endpoint")] - public string CredentialEndpoint { get; set; } = null!; - - /// - /// Gets or sets the identifier of the Credential Issuer. - /// - [JsonProperty("credential_issuer")] - public string CredentialIssuer { get; set; } = null!; - - /// - /// Gets or sets the identifier of the OAuth 2.0 Authorization Server that the Credential Issuer relies on for - /// authorization. If this property is omitted, it is assumed that the entity providing the Credential Issuer - /// is also acting as the Authorization Server. In such cases, the Credential Issuer's - /// identifier is used as the OAuth 2.0 Issuer value to obtain the Authorization Server - /// metadata. - /// - [JsonProperty("authorization_servers", NullValueHandling = NullValueHandling.Ignore)] - public string[]? AuthorizationServers { get; set; } - - [JsonConstructor] - public OidIssuerMetadata( - Dictionary credentialConfigurationsSupported, - List? display, - string credentialEndpoint, - string credentialIssuer, - string[]? authorizationServer - ) - { - CredentialConfigurationsSupported = credentialConfigurationsSupported ?? throw new ArgumentNullException(nameof(credentialConfigurationsSupported)); - Display = display; - CredentialEndpoint = credentialEndpoint ?? throw new ArgumentNullException(nameof(credentialEndpoint)); - CredentialIssuer = credentialIssuer ?? throw new ArgumentNullException(nameof(credentialIssuer)); - AuthorizationServers = authorizationServer; - } - - public OidIssuerMetadata() {} - - /// - /// Gets the display properties of a given Credential for different languages. - /// - /// The credentialMetadataId to retrieve the display properties for. - /// - /// A list of display properties for the specified Credential or null if the Credential is not found in the - /// metadata. - /// - public List? GetCredentialDisplay(string credentialMetadataId) - => CredentialConfigurationsSupported[credentialMetadataId].Display; - - /// - /// Gets the claim attributes of a given Credential. - /// - /// The credentialMetadataId to retrieve the claim attributes for. - /// - /// A dictionary of attribute names and their corresponding display properties for the specified Credential, or - /// null if the Credential is not found in the metadata. - /// - public Dictionary? GetCredentialClaims(string credentialMetadataId) => - CredentialConfigurationsSupported[credentialMetadataId].Claims; - - /// - /// Gets the localized attribute names of a given Credential for a specific locale. - /// - /// The credentialMetadataId to retrieve the localized attribute names for. - /// The locale to retrieve the attribute names in (e.g., "en-US"). - /// - /// A list of localized attribute names for the specified Credential and locale, or null if no matching attributes - /// are found. - /// - public List? GetLocalizedCredentialAttributeNames(string credentialMetadataId, string locale) - { - var displayNames = new List(); - - var matchingCredential = CredentialConfigurationsSupported[credentialMetadataId]; - - if (matchingCredential == null) - return null; - - var localeDisplayNames = matchingCredential.Claims - .SelectMany(subject => subject.Value.Display) - .Where(display => display.Locale == locale) - .Select(display => display.Name); - - displayNames.AddRange(localeDisplayNames!); - - return displayNames.Count > 0 ? displayNames : null; - } - - /// - /// Gets the localized alias name of the Credential Issuer for a specific locale. - /// - /// The locale to retrieve the issuer alias name in (e.g., "en-US"). - /// - /// The localized alias name for the specified locale, or null if no matching alias is found. - /// - public string? GetLocalizedIssuerAlias(string locale) - { - if (Display == null) - return string.Empty; - - var display = Display.FirstOrDefault(display => display.Locale == locale); - return display?.Name; - } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/IssuerMetadataSet.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/IssuerMetadataSet.cs new file mode 100644 index 00000000..405f665f --- /dev/null +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/IssuerMetadataSet.cs @@ -0,0 +1,32 @@ +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; + +namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata; + +/// +/// Represents the metadata set of an OIDC issuer and authorization server. +/// +public record IssuerMetadataSet +{ + /// + /// Gets the metadata of the OIDC issuer. + /// + public IssuerMetadata IssuerMetadata { get; } + + /// + /// Gets the metadata of the OIDC authorization server. + /// + public AuthorizationServerMetadata AuthorizationServerMetadata { get; } + + /// + /// Creates a new instance of . + /// + /// + /// + public IssuerMetadataSet(IssuerMetadata issuerMetadata, AuthorizationServerMetadata authorizationServerMetadata) + { + IssuerMetadata = issuerMetadata; + AuthorizationServerMetadata = authorizationServerMetadata; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/MetadataSet.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/MetadataSet.cs deleted file mode 100644 index bdfaa392..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/MetadataSet.cs +++ /dev/null @@ -1,29 +0,0 @@ -using WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata -{ - /// - /// Represents the metadata set of an OIDC issuer and authorization server. - /// - public record MetadataSet - { - /// - /// Gets the metadata of the OIDC issuer. - /// - public OidIssuerMetadata IssuerMetadata { get; } - - /// - /// Gets the metadata of the OIDC authorization server. - /// - public AuthorizationServerMetadata AuthorizationServerMetadata { get; } - - /// - /// Creates a new instance of . - /// - /// - /// - public MetadataSet(OidIssuerMetadata issuerMetadata, AuthorizationServerMetadata authorizationServerMetadata) => - (IssuerMetadata, AuthorizationServerMetadata) = (issuerMetadata, authorizationServerMetadata); - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Services/ISessionRecordService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Services/ISessionRecordService.cs deleted file mode 100644 index ed787a19..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Services/ISessionRecordService.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Hyperledger.Aries.Agents; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Services -{ - /// - /// Service for managing authorization records. They are used during the VCI Authorization Code Flow to hold session relevant inforation. - /// - public interface ISessionRecordService - { - /// - /// Stores the authorization session record. - /// - /// The Agent Context - /// Session Identifier of a Authorization Code Flow session - /// Options specified by the Client (Wallet) - /// Parameters required for the authorization during the VCI authorization code flow. - /// - Task StoreAsync( - IAgentContext agentContext, - VciSessionId sessionId, - AuthorizationData authorizationData, - AuthorizationCodeParameters authorizationCodeParameters); - - /// - /// Retrieves the authorization session record by the session identifier. - /// - /// Agent Context - /// Session Identifier of a Authorization Code Flow session - /// - Task GetAsync(IAgentContext context, VciSessionId sessionId); - - /// - /// Deletes the authorization session record by the session identifier. - /// - /// Agent Context - /// Session Identifier of a Authorization Code Flow session - /// - Task DeleteAsync(IAgentContext context, VciSessionId sessionId); - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Services/Oid4VciClientService/IOid4VciClientService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Services/Oid4VciClientService/IOid4VciClientService.cs deleted file mode 100644 index 12506e16..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Services/Oid4VciClientService/IOid4VciClientService.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Hyperledger.Aries.Agents; -using WalletFramework.Oid4Vc.Oid4Vci.Models; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization; -using WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialOffer; -using WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialResponse; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Services.Oid4VciClientService -{ - /// - /// Provides an interface for services related to OpenID for Verifiable Credential Issuance. - /// - public interface IOid4VciClientService - { - /// - /// Fetches the metadata related to the OID issuer from the specified endpoint. - /// - /// The Credential Offer. - /// The preferred language of the wallet in which it would like to retrieve the issuer metadata. The default is "en" - /// A task that represents the asynchronous operation. The task result contains the OID issuer metadata. - //Task FetchIssuerMetadataAsync(Uri endpoint, string preferredLanguage = "en"); - Task FetchMetadataAsync(OidCredentialOffer offer, string preferredLanguage = "en"); - - /// - /// Initiates the authorization process of the VCI authorization code flow. - /// - /// The Agent Context - /// Holds all the necessary data to initiate the authorization within the Oid4Vci authorization code flow - /// - Task InitiateAuthentication( - IAgentContext agentContext, - AuthorizationData authorizationData); - - /// - /// Requests a verifiable credential using the pre-authorized code flow. - /// - /// Holds the Issuer Metadata and Authorization Server Metadata - /// The credential metadata. - /// The pre-authorized code for token request. - /// /// The Transaction Code. - /// - /// A tuple containing the credential response and the key ID used during the signing of the Proof of Possession. - /// - Task<(OidCredentialResponse credentialResponse, string keyId)[]> RequestCredentialAsync( - MetadataSet metadataSet, - OidCredentialMetadata credentialMetadata, - string preAuthorizedCode, - string? transactionCode - ); - - /// - /// Requests a verifiable credential using the authorization code flow. - /// - /// The agent context. - /// Holds authorization session relevant information. - /// - /// A tuple containing the credential response and the key ID used during the signing of the Proof of Possession. - /// - Task<(OidCredentialResponse credentialResponse, string keyId)[]> RequestCredentialAsync( - IAgentContext context, - IssuanceSessionParameters issuanceSessionParameters - ); - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Services/Oid4VciClientService/Oid4VciClientService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Services/Oid4VciClientService/Oid4VciClientService.cs deleted file mode 100644 index b2f0694a..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Services/Oid4VciClientService/Oid4VciClientService.cs +++ /dev/null @@ -1,559 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using Hyperledger.Aries.Agents; -using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json.Linq; -using WalletFramework.Oid4Vc.Oid4Vci.Exceptions; -using WalletFramework.Oid4Vc.Oid4Vci.Extensions; -using WalletFramework.Oid4Vc.Oid4Vci.Models; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization; -using WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialOffer; -using WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialRequest; -using WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialResponse; -using WalletFramework.Oid4Vc.Oid4Vci.Models.DPop; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; -using WalletFramework.SdJwtVc.KeyStore.Services; -using static Newtonsoft.Json.JsonConvert; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Services.Oid4VciClientService -{ - /// - public class Oid4VciClientService : IOid4VciClientService - { - /// - /// Initializes a new instance of the class. - /// - /// - /// The factory to create instances of . Used for making HTTP - /// requests. - /// - /// The authorization record service - /// The key store. - public Oid4VciClientService( - IHttpClientFactory httpClientFactory, - ISessionRecordService sessionRecordService, - IKeyStore keyStore) - { - _httpClientFactory = httpClientFactory; - RecordService = sessionRecordService; - _keyStore = keyStore; - } - - private readonly IHttpClientFactory _httpClientFactory; - private readonly IKeyStore _keyStore; - - private const string ErrorCodeKey = "error"; - private const string InvalidGrantError = "invalid_grant"; - private const string UseDPopNonceError = "use_dpop_nonce"; - private const string AuthorizationCodeGrantTypeIdentifier = "authorization_code"; - private const string PreAuthorizedCodeGrantTypeIdentifier = "urn:ietf:params:oauth:grant-type:pre-authorized_code"; - - /// - /// The service responsible for wallet record operations. - /// - protected readonly ISessionRecordService RecordService; - - /// - public async Task FetchMetadataAsync(OidCredentialOffer offer, string preferredLanguage) - { - var issuerEndpoint = new Uri(offer.CredentialIssuer); - var issuerMetadata = await FetchIssuerMetadataAsync(issuerEndpoint, "en"); - - var authorizationServerMetadata = await FetchAuthorizationServerMetadataAsync(issuerMetadata, offer); - - return new MetadataSet(issuerMetadata, authorizationServerMetadata); - } - - /// - public async Task InitiateAuthentication( - IAgentContext agentContext, - AuthorizationData authorizationData) - { - var authorizationCodeParameters = CreateAndStoreCodeChallenge(); - var sessionId = Guid.NewGuid().ToString(); - - var credentialMetadatas = authorizationData.MetadataSet.IssuerMetadata.CredentialConfigurationsSupported - .Where(credentialConfiguration => authorizationData.CredentialConfigurationIds.Contains(credentialConfiguration.Key)) - .Select(y => y.Value).ToList(); - - var par = new PushedAuthorizationRequest( - VciSessionId.CreateSessionId(sessionId), - authorizationData.ClientOptions, - authorizationCodeParameters, - authorizationData.CredentialConfigurationIds - .Select(configurationId => new AuthorizationDetails( - configurationId, - new []{authorizationData.MetadataSet.IssuerMetadata.CredentialIssuer}) - ).ToArray(), - string.Join(" ", credentialMetadatas.Select(metadata => metadata.Scope)), - authorizationData.AuthorizationCode?.IssuerState, - null, - null - ); - - var client = _httpClientFactory.CreateClient(); - client.DefaultRequestHeaders.Clear(); - - var response = await client.PostAsync( - authorizationData.MetadataSet.AuthorizationServerMetadata.PushedAuthorizationRequestEndpoint, - par.ToFormUrlEncoded() - ); - - var parResponse = DeserializeObject(await response.Content.ReadAsStringAsync()) - ?? throw new InvalidOperationException("Failed to deserialize the PAR response."); - - var authorizationRequestUri = new Uri(authorizationData.MetadataSet.AuthorizationServerMetadata.AuthorizationEndpoint - + "?client_id=" + par.ClientId - + "&request_uri=" + System.Net.WebUtility.UrlEncode(parResponse.RequestUri.ToString())); - - await RecordService.StoreAsync( - agentContext, - VciSessionId.CreateSessionId(sessionId), - authorizationData, - authorizationCodeParameters); - - return authorizationRequestUri; - } - - /// - public async Task<(OidCredentialResponse credentialResponse, string keyId)[]> RequestCredentialAsync( - IAgentContext context, - IssuanceSessionParameters issuanceSessionParameters) - { - var record = await RecordService.GetAsync(context, issuanceSessionParameters.SessionId); - - var tokenRequest = new TokenRequest - { - GrantType = AuthorizationCodeGrantTypeIdentifier, - RedirectUri = record.AuthorizationData.ClientOptions.RedirectUri + "?session=" + record.SessionId, - CodeVerifier = record.AuthorizationCodeParameters.Verifier, - Code = issuanceSessionParameters.Code, - ClientId = record.AuthorizationData.ClientOptions.ClientId - }; - - var oAuthToken = record.AuthorizationData.MetadataSet.AuthorizationServerMetadata.IsDPoPSupported - ? await RequestTokenWithDPop( - new Uri(record.AuthorizationData.MetadataSet.AuthorizationServerMetadata.TokenEndpoint), - tokenRequest) - : await RequestTokenWithoutDPop( - new Uri(record.AuthorizationData.MetadataSet.AuthorizationServerMetadata.TokenEndpoint), - tokenRequest); - - var credentialMetadatas = record.AuthorizationData.MetadataSet.IssuerMetadata.CredentialConfigurationsSupported - .Where(credentialConfiguration => record.AuthorizationData.CredentialConfigurationIds.Contains(credentialConfiguration.Key)) - .Select(y => y.Value); - - var credential = oAuthToken.IsDPoPRequested() - ? await RequestCredentialWithDPoP( - credentialMetadatas.First(), - record.AuthorizationData.MetadataSet.IssuerMetadata, - oAuthToken, - record.AuthorizationData.ClientOptions) - : await RequestCredentialWithoutDPoP( - credentialMetadatas.First(), - record.AuthorizationData.MetadataSet.IssuerMetadata, - oAuthToken, - record.AuthorizationData.ClientOptions); - - await RecordService.DeleteAsync(context, record.SessionId); - - //TODO: Return multiple credentials - return new[] { credential }; - } - - /// - public async Task<(OidCredentialResponse credentialResponse, string keyId)[]> RequestCredentialAsync( - MetadataSet metadataSet, - OidCredentialMetadata credentialMetadata, - string preAuthorizedCode, - string? transactionCode) - { - var tokenRequest = new TokenRequest - { - GrantType = PreAuthorizedCodeGrantTypeIdentifier, - PreAuthorizedCode = preAuthorizedCode, - TransactionCode = transactionCode - }; - - var oAuthToken = metadataSet.AuthorizationServerMetadata.IsDPoPSupported - ? await RequestTokenWithDPop( - new Uri(metadataSet.AuthorizationServerMetadata.TokenEndpoint), - tokenRequest) - : await RequestTokenWithoutDPop( - new Uri(metadataSet.AuthorizationServerMetadata.TokenEndpoint), - tokenRequest); - - var credential = oAuthToken.IsDPoPRequested() - ? await RequestCredentialWithDPoP( - credentialMetadata, - metadataSet.IssuerMetadata, - oAuthToken) - : await RequestCredentialWithoutDPoP( - credentialMetadata, - metadataSet.IssuerMetadata, - oAuthToken); - - //TODO: Return multiple credentials - return new[] { credential }; - } - - private async Task FetchIssuerMetadataAsync(Uri endpoint, string preferredLanguage) - { - var baseEndpoint = endpoint - .AbsolutePath - .EndsWith("/") - ? endpoint - : new Uri(endpoint.OriginalString + "/"); - - var metadataUrl = new Uri(baseEndpoint, ".well-known/openid-credential-issuer"); - - var client = _httpClientFactory.CreateClient(); - client.DefaultRequestHeaders.Add("Accept-Language", preferredLanguage); - - var response = await client.GetAsync(metadataUrl); - - if (!response.IsSuccessStatusCode) - { - throw new HttpRequestException( - $"Failed to get Issuer metadata. Status code is {response.StatusCode}" - ); - } - - return DeserializeObject( - await response.Content.ReadAsStringAsync() - ) ?? throw new InvalidOperationException("Failed to deserialize the issuer metadata."); - } - - private async Task FetchAuthorizationServerMetadataAsync(OidIssuerMetadata issuerMetadata, OidCredentialOffer offer) - { - if (!string.IsNullOrWhiteSpace(offer.Grants?.AuthorizationCode?.AuthorizationServer) - && (issuerMetadata.AuthorizationServers == null - || !issuerMetadata.AuthorizationServers.Contains(offer.Grants.AuthorizationCode.AuthorizationServer))) - { - throw new InvalidOperationException("The AuthorizationServer in the offer must be one of the Authorization Servers listed in the Issuer Metadata."); - } - - var authServerUrl = new Uri(offer.Grants?.AuthorizationCode?.AuthorizationServer - ?? issuerMetadata.AuthorizationServers?.First() - ?? issuerMetadata.CredentialIssuer); - - var getAuthServerUrl = string.IsNullOrEmpty(authServerUrl.AbsolutePath) || authServerUrl.AbsolutePath == "/" - ? $"{authServerUrl.GetLeftPart(UriPartial.Authority)}/.well-known/oauth-authorization-server" - : $"{authServerUrl.GetLeftPart(UriPartial.Authority)}/.well-known/oauth-authorization-server" - + authServerUrl.AbsolutePath.TrimEnd('/'); - - var httpClient = _httpClientFactory.CreateClient(); - - var getAuthServerResponse = await httpClient.GetAsync(getAuthServerUrl); - - if (!getAuthServerResponse.IsSuccessStatusCode) - { - throw new HttpRequestException( - $"Failed to get authorization server metadata. Status Code is: {getAuthServerResponse.StatusCode}" - ); - } - - var authServer = - DeserializeObject( - await getAuthServerResponse.Content.ReadAsStringAsync() - ) - ?? throw new InvalidOperationException( - "Failed to deserialize the authorization server metadata." - ); - - return authServer; - } - - private async Task RequestTokenWithDPop( - Uri authServerTokenEndpoint, - TokenRequest tokenRequest) - { - var dPopKey = await _keyStore.GenerateKey(); - var dPopProofJwt = await _keyStore.GenerateDPopProofOfPossessionAsync( - dPopKey, - authServerTokenEndpoint.ToString(), - null, - null - ); - - var httpClient = _httpClientFactory.CreateClient(); - httpClient.AddDPopHeader(dPopProofJwt); - - var response = await httpClient.PostAsync( - authServerTokenEndpoint, - tokenRequest.ToFormUrlEncoded() - ); - - await ThrowIfInvalidGrantError(response); - - var dPopNonce = await GetDPopNonce(response); - - if (!string.IsNullOrEmpty(dPopNonce)) - { - dPopProofJwt = await _keyStore.GenerateDPopProofOfPossessionAsync( - dPopKey, - authServerTokenEndpoint.ToString(), - dPopNonce, - null); - - httpClient.AddDPopHeader(dPopProofJwt); - response = await httpClient.PostAsync( - authServerTokenEndpoint, - tokenRequest.ToFormUrlEncoded()); - } - - await ThrowIfInvalidGrantError(response); - - if (!response.IsSuccessStatusCode) - { - throw new HttpRequestException( - $"Failed to get token. Status Code is {response.StatusCode}" - ); - } - - var tokenResponse = DeserializeObject(await response.Content.ReadAsStringAsync()) - ?? throw new InvalidOperationException("Failed to deserialize the token response"); - - var dPop = new DPop(dPopKey, dPopNonce); - var oAuthToken = new OAuthToken(tokenResponse, dPop); - - return oAuthToken; - } - - private async Task RequestTokenWithoutDPop( - Uri authServerTokenEndpoint, - TokenRequest tokenRequest) - { - var httpClient = _httpClientFactory.CreateClient(); - - var response = await httpClient.PostAsync( - authServerTokenEndpoint, - tokenRequest.ToFormUrlEncoded()); - - await ThrowIfInvalidGrantError(response); - - if (!response.IsSuccessStatusCode) - { - throw new HttpRequestException( - $"Failed to get token. Status Code is {response.StatusCode}" - ); - } - - var tokenResponse = DeserializeObject(await response.Content.ReadAsStringAsync()) - ?? throw new InvalidOperationException("Failed to deserialize the token response"); - - var oAuthToken = new OAuthToken(tokenResponse); - return oAuthToken; - } - - private async Task<(OidCredentialResponse credentialResponse, string keyId)> RequestCredentialWithDPoP( - OidCredentialMetadata credentialMetadata, - OidIssuerMetadata issuerMetadata, - OAuthToken oAuthToken, - ClientOptions? clientOptions = null) - { - if (oAuthToken.DPop == null) - { - throw new InvalidOperationException("The DPoP Flow requires the DPoP specific parameters."); - } - - var dPopProofJwt = await _keyStore.GenerateDPopProofOfPossessionAsync( - oAuthToken.DPop.KeyId, - issuerMetadata.CredentialEndpoint, - oAuthToken.DPop.Nonce, - oAuthToken.TokenResponse.AccessToken); - - var sdJwtKeyId = await _keyStore.GenerateKey(); - var keyBindingJwt = await _keyStore.GenerateKbProofOfPossessionAsync( - sdJwtKeyId, - issuerMetadata.CredentialIssuer, - oAuthToken.TokenResponse.CNonce, - "openid4vci-proof+jwt", - null, - clientOptions?.ClientId - ); - - var httpClient = _httpClientFactory.CreateClient(); - httpClient.AddAuthorizationHeader(oAuthToken); - httpClient.AddDPopHeader(dPopProofJwt); - - var response = await httpClient.PostAsync( - issuerMetadata.CredentialEndpoint, - new StringContent( - content: new OidCredentialRequest - { - Format = credentialMetadata.Format, - Vct = credentialMetadata.Vct, - Proof = new OidProofOfPossession - { - ProofType = "jwt", - Jwt = keyBindingJwt - } - }.ToJson(), - encoding: Encoding.UTF8, - mediaType: "application/json" - ) - ); - - var refreshedDPopNonce = await GetDPopNonce(response); - - if (!string.IsNullOrEmpty(refreshedDPopNonce)) - { - dPopProofJwt = await _keyStore.GenerateDPopProofOfPossessionAsync( - oAuthToken.DPop.KeyId, - issuerMetadata.CredentialEndpoint, - refreshedDPopNonce, - oAuthToken.TokenResponse.AccessToken); - httpClient.AddDPopHeader(dPopProofJwt); - - response = await httpClient.PostAsync( - issuerMetadata.CredentialEndpoint, - new StringContent( - content: new OidCredentialRequest - { - Format = credentialMetadata.Format, - Vct = credentialMetadata.Vct, - Proof = new OidProofOfPossession - { - ProofType = "jwt", - Jwt = keyBindingJwt - } - }.ToJson(), - encoding: Encoding.UTF8, - mediaType: "application/json" - ) - ); - } - - if (!string.IsNullOrEmpty(oAuthToken.DPop.KeyId)) - { - await _keyStore.DeleteKey(oAuthToken.DPop.KeyId); - } - - if (!response.IsSuccessStatusCode) - { - throw new HttpRequestException( - $"Failed to request Credential. Status Code is {response.StatusCode}" - ); - } - - var credentialResponse = DeserializeObject( - await response.Content.ReadAsStringAsync() - ); - - if (credentialResponse?.Credential == null) - { - throw new InvalidOperationException("Credential response is null."); - } - - return (credentialResponse, sdJwtKeyId); - } - - private async Task<(OidCredentialResponse credentialResponse, string keyId)> RequestCredentialWithoutDPoP( - OidCredentialMetadata credentialMetadata, - OidIssuerMetadata issuerMetadata, - OAuthToken oAuthToken, - ClientOptions? clientOptions = null) - { - var sdJwtKeyId = await _keyStore.GenerateKey(); - var proofOfPossession = await _keyStore.GenerateKbProofOfPossessionAsync( - sdJwtKeyId, - issuerMetadata.CredentialIssuer, - oAuthToken.TokenResponse.CNonce, - "openid4vci-proof+jwt", - null, - clientOptions?.ClientId - ); - - var httpClient = _httpClientFactory.CreateClient(); - httpClient.AddAuthorizationHeader(oAuthToken); - - var response = await httpClient.PostAsync( - issuerMetadata.CredentialEndpoint, - new StringContent( - content: new OidCredentialRequest - { - Format = credentialMetadata.Format, - Vct = credentialMetadata.Vct, - Proof = new OidProofOfPossession - { - ProofType = "jwt", - Jwt = proofOfPossession - } - }.ToJson(), - encoding: Encoding.UTF8, - mediaType: "application/json" - ) - ); - - if (!response.IsSuccessStatusCode) - { - throw new HttpRequestException( - $"Failed to request Credential. Status Code is {response.StatusCode}" - ); - } - - var credentialResponse = DeserializeObject( - await response.Content.ReadAsStringAsync() - ); - - if (credentialResponse?.Credential == null) - { - throw new InvalidOperationException("Credential response is null."); - } - - return (credentialResponse, sdJwtKeyId); - } - - private async Task ThrowIfInvalidGrantError(HttpResponseMessage response) - { - var content = await response.Content.ReadAsStringAsync(); - var errorReason = string.IsNullOrEmpty(content) - ? null - : JObject.Parse(content)[ErrorCodeKey]?.ToString(); - - if (response.StatusCode is System.Net.HttpStatusCode.BadRequest - && errorReason == InvalidGrantError) - { - throw new Oid4VciInvalidGrantException(response.StatusCode); - } - } - - private async Task GetDPopNonce(HttpResponseMessage response) - { - var content = await response.Content.ReadAsStringAsync(); - var errorReason = string.IsNullOrEmpty(content) - ? null - : JObject.Parse(content)[ErrorCodeKey]?.ToString(); - - if (response.StatusCode - is System.Net.HttpStatusCode.BadRequest - or System.Net.HttpStatusCode.Unauthorized - && errorReason == UseDPopNonceError - && response.Headers.TryGetValues("DPoP-Nonce", out var dPopNonce)) - { - return dPopNonce?.FirstOrDefault(); - } - - return null; - } - - private AuthorizationCodeParameters CreateAndStoreCodeChallenge() - { - var rng = new RNGCryptoServiceProvider(); - byte[] randomNumber = new byte[32]; - rng.GetBytes(randomNumber); - - var codeVerifier = Base64UrlEncoder.Encode(randomNumber); - - var sha256 = SHA256.Create(); - byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier)); - - var codeChallenge = Base64UrlEncoder.Encode(bytes); - - return new AuthorizationCodeParameters(codeChallenge, codeVerifier); - } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Services/SessionRecordService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Services/SessionRecordService.cs deleted file mode 100644 index b415988d..00000000 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Services/SessionRecordService.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Hyperledger.Aries; -using Hyperledger.Aries.Agents; -using Hyperledger.Aries.Storage; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization; -using WalletFramework.Oid4Vc.Oid4Vp.Services; - -namespace WalletFramework.Oid4Vc.Oid4Vci.Services -{ - /// - public class SessionRecordService : ISessionRecordService - { - /// - /// The service responsible for wallet record operations. - /// - protected readonly IWalletRecordService RecordService; - - /// - /// Initializes a new instance of the class. - /// - /// The service responsible for wallet record operations. - public SessionRecordService(IWalletRecordService recordService) - { - RecordService = recordService; - } - - /// - public async Task StoreAsync( - IAgentContext agentContext, - VciSessionId sessionId, - AuthorizationData authorizationData, - AuthorizationCodeParameters authorizationCodeParameters) - { - var record = new VciAuthorizationSessionRecord( - sessionId, - authorizationData, - authorizationCodeParameters); - - await RecordService.AddAsync( - agentContext.Wallet, - record - ); - - return record.Id; - } - - /// - public async Task GetAsync(IAgentContext context, VciSessionId sessionId) - { - var record = (await RecordService.SearchAsync( - context.Wallet, - SearchQuery.Equal( - "~" + nameof(VciAuthorizationSessionRecord.SessionId), - sessionId - ))).First(); - if (record == null) - throw new AriesFrameworkException(ErrorCode.RecordNotFound, "VciAuthorizationSessionRecord record not found"); - - return record; - } - - /// - public async Task DeleteAsync(IAgentContext context, VciSessionId sessionId) - { - return await RecordService.DeleteAsync(context.Wallet, sessionId); - } - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Services/PexService.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Services/PexService.cs index 48a14664..cf3ddccb 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Services/PexService.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Services/PexService.cs @@ -75,7 +75,7 @@ private static SdJwtRecord[] _filterMatchingCredentialsForFields(SdJwtRecord[] r foreach (var record in records) { var doc = _toSdJwtDoc(record); - bool isAMatch = fields.All(field => + var isAMatch = fields.All(field => { try { diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Services/IOid4VpHaipClient.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Services/IOid4VpHaipClient.cs index 92abbfea..1a60942f 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Services/IOid4VpHaipClient.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Services/IOid4VpHaipClient.cs @@ -1,29 +1,28 @@ using WalletFramework.Oid4Vc.Oid4Vp.Models; -namespace WalletFramework.Oid4Vc.Oid4Vp.Services +namespace WalletFramework.Oid4Vc.Oid4Vp.Services; + +/// +/// This Service offers methods to handle the OpenId4Vp protocol +/// +public interface IOid4VpHaipClient { /// - /// This Service offers methods to handle the OpenId4Vp protocol + /// Processes an OpenID4VP Authorization Request Url. /// - internal interface IOid4VpHaipClient - { - /// - /// Processes an OpenID4VP Authorization Request Url. - /// - /// - /// - /// A task representing the asynchronous operation. The task result contains the Authorization Response object associated with the OpenID4VP Authorization Request Url. - /// - Task ProcessAuthorizationRequestAsync(HaipAuthorizationRequestUri haipAuthorizationRequestUri); + /// + /// + /// A task representing the asynchronous operation. The task result contains the Authorization Response object associated with the OpenID4VP Authorization Request Url. + /// + Task ProcessAuthorizationRequestAsync(HaipAuthorizationRequestUri haipAuthorizationRequestUri); - /// - /// Creates the Parameters that are necessary to send an OpenId4VP Authorization Response. - /// - /// - /// /// - /// - /// A task representing the asynchronous operation. The task result contains the Presentation Submission and the VP Token. - /// - Task CreateAuthorizationResponseAsync(AuthorizationRequest authorizationRequest, (string inputDescriptorId, string presentation)[] presentationMap); - } + /// + /// Creates the Parameters that are necessary to send an OpenId4VP Authorization Response. + /// + /// + /// /// + /// + /// A task representing the asynchronous operation. The task result contains the Presentation Submission and the VP Token. + /// + Task CreateAuthorizationResponseAsync(AuthorizationRequest authorizationRequest, (string inputDescriptorId, string presentation)[] presentationMap); } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpClientService.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpClientService.cs index a80e7064..22c6fc31 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpClientService.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpClientService.cs @@ -7,184 +7,183 @@ using WalletFramework.SdJwtVc.Services.SdJwtVcHolderService; using static Newtonsoft.Json.JsonConvert; -namespace WalletFramework.Oid4Vc.Oid4Vp.Services +namespace WalletFramework.Oid4Vc.Oid4Vp.Services; + +/// +public class Oid4VpClientService : IOid4VpClientService { - /// - internal class Oid4VpClientService : IOid4VpClientService + /// + /// Initializes a new instance of the class. + /// + /// The http client factory to create http clients. + /// The service responsible for SD-JWT related operations. + /// The Presentation Exchange service. + /// The service responsible for OpenId4VP related operations. + /// The ILogger. + /// The service responsible for OidPresentationRecord related operations. + public Oid4VpClientService( + IHttpClientFactory httpClientFactory, + ISdJwtVcHolderService sdJwtVcHolderService, + IPexService pexService, + IOid4VpHaipClient oid4VpHaipClient, + ILogger logger, + IOid4VpRecordService oid4VpRecordService) { - /// - /// Initializes a new instance of the class. - /// - /// The http client factory to create http clients. - /// The service responsible for SD-JWT related operations. - /// The Presentation Exchange service. - /// The service responsible for OpenId4VP related operations. - /// The ILogger. - /// The service responsible for OidPresentationRecord related operations. - public Oid4VpClientService( - IHttpClientFactory httpClientFactory, - ISdJwtVcHolderService sdJwtVcHolderService, - IPexService pexService, - IOid4VpHaipClient oid4VpHaipClient, - ILogger logger, - IOid4VpRecordService oid4VpRecordService) - { - _httpClientFactory = httpClientFactory; - _sdJwtVcHolderService = sdJwtVcHolderService; - _oid4VpHaipClient = oid4VpHaipClient; - _logger = logger; - _oid4VpRecordService = oid4VpRecordService; - _pexService = pexService; - } + _httpClientFactory = httpClientFactory; + _sdJwtVcHolderService = sdJwtVcHolderService; + _oid4VpHaipClient = oid4VpHaipClient; + _logger = logger; + _oid4VpRecordService = oid4VpRecordService; + _pexService = pexService; + } - private readonly IHttpClientFactory _httpClientFactory; - private readonly IOid4VpHaipClient _oid4VpHaipClient; - private readonly ILogger _logger; - private readonly IOid4VpRecordService _oid4VpRecordService; - private readonly ISdJwtVcHolderService _sdJwtVcHolderService; - private readonly IPexService _pexService; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IOid4VpHaipClient _oid4VpHaipClient; + private readonly ILogger _logger; + private readonly IOid4VpRecordService _oid4VpRecordService; + private readonly ISdJwtVcHolderService _sdJwtVcHolderService; + private readonly IPexService _pexService; - /// - public async Task<(AuthorizationRequest, CredentialCandidates[])> ProcessAuthorizationRequestAsync( - IAgentContext agentContext, Uri authorizationRequestUri) - { - var haipAuthorizationRequestUri = HaipAuthorizationRequestUri.FromUri(authorizationRequestUri); + /// + public async Task<(AuthorizationRequest, CredentialCandidates[])> ProcessAuthorizationRequestAsync( + IAgentContext agentContext, Uri authorizationRequestUri) + { + var haipAuthorizationRequestUri = HaipAuthorizationRequestUri.FromUri(authorizationRequestUri); - var authorizationRequest = - await _oid4VpHaipClient.ProcessAuthorizationRequestAsync(haipAuthorizationRequestUri); + var authorizationRequest = + await _oid4VpHaipClient.ProcessAuthorizationRequestAsync(haipAuthorizationRequestUri); - var credentials = await _sdJwtVcHolderService.ListAsync(agentContext); + var credentials = await _sdJwtVcHolderService.ListAsync(agentContext); - var credentialCandidates = await _pexService.FindCredentialCandidates( - credentials.ToArray(), - authorizationRequest.PresentationDefinition.InputDescriptors - ); + var credentialCandidates = await _pexService.FindCredentialCandidates( + credentials.ToArray(), + authorizationRequest.PresentationDefinition.InputDescriptors + ); - return (authorizationRequest, credentialCandidates); - } + return (authorizationRequest, credentialCandidates); + } + + /// + public async Task SendAuthorizationResponseAsync( + IAgentContext agentContext, + AuthorizationRequest authorizationRequest, + SelectedCredential[] selectedCredentials) + { + var createPresentationMaps = + from credential in selectedCredentials + from inputDescriptor in authorizationRequest.PresentationDefinition.InputDescriptors + where credential.InputDescriptorId == inputDescriptor.Id + let disclosedClaims = inputDescriptor + .Constraints + .Fields? + .SelectMany(field => field.Path.Select(path => path.TrimStart('$', '.'))) + let createPresentation = _sdJwtVcHolderService.CreatePresentation( + (SdJwtRecord)credential.Credential, + disclosedClaims.ToArray(), + authorizationRequest.ClientId, + authorizationRequest.Nonce + ) + select (inputDescriptor.Id, createPresentation); - /// - public async Task SendAuthorizationResponseAsync( - IAgentContext agentContext, - AuthorizationRequest authorizationRequest, - SelectedCredential[] selectedCredentials) + var presentationMaps = new List<(string, string)>(); + foreach (var (inputDescriptorId, createPresentation) in createPresentationMaps) { - var createPresentationMaps = - from credential in selectedCredentials - from inputDescriptor in authorizationRequest.PresentationDefinition.InputDescriptors - where credential.InputDescriptorId == inputDescriptor.Id - let disclosedClaims = inputDescriptor - .Constraints - .Fields? - .SelectMany(field => field.Path.Select(path => path.TrimStart('$', '.'))) - let createPresentation = _sdJwtVcHolderService.CreatePresentation( - (SdJwtRecord)credential.Credential, - disclosedClaims.ToArray(), - authorizationRequest.ClientId, - authorizationRequest.Nonce - ) - select (inputDescriptor.Id, createPresentation); - - var presentationMaps = new List<(string, string)>(); - foreach (var (inputDescriptorId, createPresentation) in createPresentationMaps) - { - presentationMaps.Add((inputDescriptorId, await createPresentation)); - } + presentationMaps.Add((inputDescriptorId, await createPresentation)); + } - var authorizationResponse = await _oid4VpHaipClient.CreateAuthorizationResponseAsync( - authorizationRequest, - presentationMaps.ToArray() - ); + var authorizationResponse = await _oid4VpHaipClient.CreateAuthorizationResponseAsync( + authorizationRequest, + presentationMaps.ToArray() + ); - var httpClient = _httpClientFactory.CreateClient(); - httpClient.DefaultRequestHeaders.Clear(); + var httpClient = _httpClientFactory.CreateClient(); + httpClient.DefaultRequestHeaders.Clear(); - var responseMessage = - await httpClient.SendAsync( - new HttpRequestMessage - { - RequestUri = new Uri(authorizationRequest.ResponseUri), - Method = HttpMethod.Post, - Content = new FormUrlEncodedContent( - DeserializeObject>( - SerializeObject(authorizationResponse) - )? - .ToList() - ?? throw new InvalidOperationException("Authorization Response could not be parsed") - ) - } - ); - - if (!responseMessage.IsSuccessStatusCode) - throw new InvalidOperationException("Authorization Response could not be sent"); - - var presentedCredentials = presentationMaps - .Join( - selectedCredentials, - presentation => presentation.Item1, - selectedCredential => selectedCredential.InputDescriptorId, - (presentation, selectedCredential) => new - { - inputDescriptorId = presentation.Item1, - presentationFormat = presentation.Item2, - credential = selectedCredential.Credential - } - ).Select(presentationMapItem => + var responseMessage = + await httpClient.SendAsync( + new HttpRequestMessage { - var credentialRecord = (SdJwtRecord)presentationMapItem.credential; - var issuanceSdJwtDoc = credentialRecord.ToSdJwtDoc(); - var presentationSdJwtDoc = new SdJwtDoc(presentationMapItem.presentationFormat); - - var nonDisclosedDisclosure = - from issuedDisclosures in issuanceSdJwtDoc.Disclosures - let base64Encoded = issuedDisclosures.Base64UrlEncoded - where presentationSdJwtDoc.Disclosures.All(itm => itm.Base64UrlEncoded != base64Encoded) - select issuedDisclosures; - - var presentedClaims = - from claim in credentialRecord.Claims - where !nonDisclosedDisclosure.Any(nd => claim.Key.StartsWith(nd.Path ?? string.Empty)) - select new - { - key = claim.Key, - value = new PresentedClaim { Value = claim.Value } - }; - - return new PresentedCredential - { - CredentialId = credentialRecord.Id, - PresentedClaims = presentedClaims.ToDictionary(itm => itm.key, itm => itm.value) - }; - }); - - await _oid4VpRecordService.StoreAsync( - agentContext, - authorizationRequest.ClientId, - authorizationRequest.ClientMetadata, - authorizationRequest.PresentationDefinition.Name, - presentedCredentials.ToArray() + RequestUri = new Uri(authorizationRequest.ResponseUri), + Method = HttpMethod.Post, + Content = new FormUrlEncodedContent( + DeserializeObject>( + SerializeObject(authorizationResponse) + )? + .ToList() + ?? throw new InvalidOperationException("Authorization Response could not be parsed") + ) + } ); - var redirectUriJson = await responseMessage.Content.ReadAsStringAsync(); + if (!responseMessage.IsSuccessStatusCode) + throw new InvalidOperationException("Authorization Response could not be sent"); - try + var presentedCredentials = presentationMaps + .Join( + selectedCredentials, + presentation => presentation.Item1, + selectedCredential => selectedCredential.InputDescriptorId, + (presentation, selectedCredential) => new + { + inputDescriptorId = presentation.Item1, + presentationFormat = presentation.Item2, + credential = selectedCredential.Credential + } + ).Select(presentationMapItem => { - return DeserializeObject(redirectUriJson); + var credentialRecord = (SdJwtRecord)presentationMapItem.credential; + var issuanceSdJwtDoc = credentialRecord.ToSdJwtDoc(); + var presentationSdJwtDoc = new SdJwtDoc(presentationMapItem.presentationFormat); + + var nonDisclosedDisclosure = + from issuedDisclosures in issuanceSdJwtDoc.Disclosures + let base64Encoded = issuedDisclosures.Base64UrlEncoded + where presentationSdJwtDoc.Disclosures.All(itm => itm.Base64UrlEncoded != base64Encoded) + select issuedDisclosures; + + var presentedClaims = + from claim in credentialRecord.Claims + where !nonDisclosedDisclosure.Any(nd => claim.Key.StartsWith(nd.Path ?? string.Empty)) + select new + { + key = claim.Key, + value = new PresentedClaim { Value = claim.Value } + }; - } - catch (Exception e) - { - _logger.LogWarning("Could not parse Redirect URI received from: {ResponseUri} due to exception: {Exception}", authorizationRequest.ResponseUri, e); - return null; - } + return new PresentedCredential + { + CredentialId = credentialRecord.Id, + PresentedClaims = presentedClaims.ToDictionary(itm => itm.key, itm => itm.value) + }; + }); + + await _oid4VpRecordService.StoreAsync( + agentContext, + authorizationRequest.ClientId, + authorizationRequest.ClientMetadata, + authorizationRequest.PresentationDefinition.Name, + presentedCredentials.ToArray() + ); + + var redirectUriJson = await responseMessage.Content.ReadAsStringAsync(); + + try + { + return DeserializeObject(redirectUriJson); + + } + catch (Exception e) + { + _logger.LogWarning("Could not parse Redirect URI received from: {ResponseUri} due to exception: {Exception}", authorizationRequest.ResponseUri, e); + return null; } } +} - internal static class SdJwtRecordExtensions +internal static class SdJwtRecordExtensions +{ + internal static SdJwtDoc ToSdJwtDoc(this SdJwtRecord record) { - internal static SdJwtDoc ToSdJwtDoc(this SdJwtRecord record) - { - return new SdJwtDoc(record.EncodedIssuerSignedJwt + "~" + string.Join("~", record.Disclosures) + "~"); - } + return new SdJwtDoc(record.EncodedIssuerSignedJwt + "~" + string.Join("~", record.Disclosures) + "~"); } } diff --git a/src/WalletFramework.Oid4Vc/SeviceCollectionExtensions.cs b/src/WalletFramework.Oid4Vc/SeviceCollectionExtensions.cs index a35055b3..63e9b2a1 100644 --- a/src/WalletFramework.Oid4Vc/SeviceCollectionExtensions.cs +++ b/src/WalletFramework.Oid4Vc/SeviceCollectionExtensions.cs @@ -1,8 +1,22 @@ using Microsoft.Extensions.DependencyInjection; -using WalletFramework.Oid4Vc.Oid4Vci.Services; -using WalletFramework.Oid4Vc.Oid4Vci.Services.Oid4VciClientService; +using WalletFramework.Oid4Vc.Oid4Vci.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Implementations; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.DPop.Implementations; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Implementations; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Implementations; +using WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Implementations; +using WalletFramework.Oid4Vc.Oid4Vci.Implementations; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Abstractions; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Implementations; using WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Services; using WalletFramework.Oid4Vc.Oid4Vp.Services; +using WalletFramework.SdJwtVc; +using WalletFramework.SdJwtVc.Services.SdJwtVcHolderService; namespace WalletFramework.Oid4Vc; @@ -12,15 +26,23 @@ public static class SeviceCollectionExtensions /// Adds the default OpenID services. /// /// The builder. - public static IServiceCollection AddOpenIdDefaultServices(this IServiceCollection builder) + public static IServiceCollection AddOpenIdServices(this IServiceCollection builder) { - builder.AddSingleton(); + builder.AddSingleton(); + builder.AddSingleton(); + builder.AddSingleton(); + builder.AddSingleton(); + builder.AddSingleton(); + builder.AddSingleton(); builder.AddSingleton(); builder.AddSingleton(); builder.AddSingleton(); builder.AddSingleton(); - builder.AddSingleton(); - + builder.AddSingleton(); + builder.AddSingleton(); + + builder.AddSdJwtVcServices(); + return builder; } diff --git a/src/WalletFramework.Oid4Vc/WalletFramework.Oid4Vc.csproj b/src/WalletFramework.Oid4Vc/WalletFramework.Oid4Vc.csproj index 69f2e6af..842e193b 100644 --- a/src/WalletFramework.Oid4Vc/WalletFramework.Oid4Vc.csproj +++ b/src/WalletFramework.Oid4Vc/WalletFramework.Oid4Vc.csproj @@ -6,10 +6,15 @@ enable + + + <_Parameter1>WalletFramework.Oid4Vc.Tests + + + - diff --git a/src/WalletFramework.SdJwtVc/KeyStore/Services/IKeyStore.cs b/src/WalletFramework.SdJwtVc/KeyStore/Services/IKeyStore.cs deleted file mode 100644 index fa8fee3c..00000000 --- a/src/WalletFramework.SdJwtVc/KeyStore/Services/IKeyStore.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace WalletFramework.SdJwtVc.KeyStore.Services -{ - /// - /// Represents a store for managing keys. - /// This interface is intended to be implemented outside of the framework on the device side, - /// allowing flexibility in key generation or retrieval mechanisms. - /// - public interface IKeyStore - { - /// - /// Asynchronously generates a key for the specified algorithm and returns the key identifier. - /// - /// The algorithm for key generation (default is "ES256"). - /// A representing the generated key's identifier as a string. - Task GenerateKey(string alg = "ES256"); - - /// - /// Asynchronously creates a proof of possession for a specific key, based on the provided audience and nonce. - /// - /// The identifier of the key to be used in creating the proof of possession. - /// The intended recipient of the proof. Typically represents the entity that will verify it. - /// - /// A unique token, typically used to prevent replay attacks by ensuring that the proof is only used once. - /// - /// The type of the proof. (For example "openid4vci-proof+jwt") - /// Base64url-encoded hash digest over the Issuer-signed JWT and the selected Disclosures for integrity protection - /// - /// A representing the asynchronous operation. When evaluated, the task's result contains - /// the proof. - /// - Task GenerateKbProofOfPossessionAsync(string keyId, string audience, string nonce, string type, string? sdHash = null, string? clientId = null); - - /// - /// Asynchronously creates a DPoP Proof JWT for a specific key, based on the provided audience, nonce and access token. - /// - /// The identifier of the key to be used in creating the proof of possession. - /// The intended recipient of the proof. Typically represents the entity that will verify it. - /// A unique token, typically used to prevent replay attacks by ensuring that the proof is only used once. - /// The access token, that the DPoP Proof JWT is bound to - /// - /// A representing the asynchronous operation. When evaluated, the task's result contains - /// the DPoP Proof JWT. - /// - Task GenerateDPopProofOfPossessionAsync(string keyId, string audience, string? nonce, string? accessToken); - - /// - /// Asynchronously loads a key by its identifier and returns it as a JSON Web Key (JWK) containing the public key - /// information. - /// - /// The identifier of the key to load. - /// A representing the loaded key as a JWK string. - Task LoadKey(string keyId); - - /// - /// Asynchronously signs the given payload using the key identified by the provided key ID. - /// - /// The identifier of the key to use for signing. - /// The payload to sign. - /// A representing the signed payload as a byte array. - Task Sign(string keyId, byte[] payload); - - /// - /// Asynchronously deletes the key associated with the provided key ID. - /// - /// The identifier of the key that should be deleted - /// A representing the asynchronous operation. - Task DeleteKey(string keyId); - } -} diff --git a/src/WalletFramework.SdJwtVc/Models/Credential/CredentialDisplayMetadata.cs b/src/WalletFramework.SdJwtVc/Models/Credential/CredentialDisplayMetadata.cs deleted file mode 100644 index 4ff43db0..00000000 --- a/src/WalletFramework.SdJwtVc/Models/Credential/CredentialDisplayMetadata.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.SdJwtVc.Models.Credential -{ - /// - /// Represents the visual representations for the credential. - /// - public class CredentialDisplayMetadata - { - /// - /// Gets or sets the logo associated with this Credential. - /// - [JsonProperty("logo", NullValueHandling = NullValueHandling.Ignore)] - public CredentialLogo? Logo { get; set; } - - /// - /// Gets or sets the name of the Credential. - /// - [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string? Name { get; set; } - - /// - /// Gets or sets the background color for the Credential. - /// - [JsonProperty("background_color", NullValueHandling = NullValueHandling.Ignore)] - public string? BackgroundColor { get; set; } - - /// - /// Gets or sets the locale, which represents the specific culture or region. - /// - [JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)] - public string? Locale { get; set; } - - /// - /// Gets or sets the text color for the Credential. - /// - [JsonProperty("text_color", NullValueHandling = NullValueHandling.Ignore)] - public string? TextColor { get; set; } - - /// - /// Represents the Logo for a Credential. - /// - public class CredentialLogo - { - /// - /// Gets or sets the alternate text that describes the logo image. This is typically used for accessibility purposes. - /// - [JsonProperty("alt_text")] - public string? AltText { get; set; } - - /// - /// Gets or sets the URL of the logo image. - /// - [JsonProperty("uri")] - public Uri Uri { get; set; } = null!; - } - } -} diff --git a/src/WalletFramework.SdJwtVc/Models/Credential/SdJwtDisplay.cs b/src/WalletFramework.SdJwtVc/Models/Credential/SdJwtDisplay.cs new file mode 100644 index 00000000..03248476 --- /dev/null +++ b/src/WalletFramework.SdJwtVc/Models/Credential/SdJwtDisplay.cs @@ -0,0 +1,57 @@ +using Newtonsoft.Json; + +namespace WalletFramework.SdJwtVc.Models.Credential; + +/// +/// Represents the visual representations for the credential. +/// +public record SdJwtDisplay +{ + /// + /// Gets or sets the logo associated with this Credential. + /// + [JsonProperty("logo", NullValueHandling = NullValueHandling.Ignore)] + public SdJwtLogo? Logo { get; set; } + + /// + /// Gets or sets the name of the Credential. + /// + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string? Name { get; set; } + + /// + /// Gets or sets the background color for the Credential. + /// + [JsonProperty("background_color", NullValueHandling = NullValueHandling.Ignore)] + public string? BackgroundColor { get; set; } + + /// + /// Gets or sets the locale, which represents the specific culture or region. + /// + [JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)] + public string? Locale { get; set; } + + /// + /// Gets or sets the text color for the Credential. + /// + [JsonProperty("text_color", NullValueHandling = NullValueHandling.Ignore)] + public string? TextColor { get; set; } + + /// + /// Represents the Logo for a Credential. + /// + public class SdJwtLogo + { + /// + /// Gets or sets the alternate text that describes the logo image. This is typically used for accessibility purposes. + /// + [JsonProperty("alt_text")] + public string? AltText { get; set; } + + /// + /// Gets or sets the URL of the logo image. + /// + [JsonProperty("uri")] + public Uri Uri { get; set; } = null!; + } +} diff --git a/src/WalletFramework.SdJwtVc/Models/Credential/CredentialMetadata.cs b/src/WalletFramework.SdJwtVc/Models/Credential/SdJwtMetadata.cs similarity index 97% rename from src/WalletFramework.SdJwtVc/Models/Credential/CredentialMetadata.cs rename to src/WalletFramework.SdJwtVc/Models/Credential/SdJwtMetadata.cs index fb8044df..a6b9b7d5 100644 --- a/src/WalletFramework.SdJwtVc/Models/Credential/CredentialMetadata.cs +++ b/src/WalletFramework.SdJwtVc/Models/Credential/SdJwtMetadata.cs @@ -6,7 +6,7 @@ namespace WalletFramework.SdJwtVc.Models.Credential /// /// Represents the metadata of a specific type of credential that a Credential Issuer can issue. /// - public class CredentialMetadata + public class SdJwtMetadata { /// /// Gets or sets the verifiable credential type (vct). This is SD-JWT specific. @@ -36,7 +36,7 @@ public class CredentialMetadata /// Gets or sets a list of display properties of the supported credential for different languages. /// [JsonProperty("display", NullValueHandling = NullValueHandling.Ignore)] - public List? Display { get; set; } + public List? Display { get; set; } /// /// Gets or sets a list of methods that identify how the Credential is bound to the identifier of the End-User who diff --git a/src/WalletFramework.SdJwtVc/Models/Issuer/IssuerDisplay.cs b/src/WalletFramework.SdJwtVc/Models/Issuer/IssuerDisplay.cs deleted file mode 100644 index 66f2dafa..00000000 --- a/src/WalletFramework.SdJwtVc/Models/Issuer/IssuerDisplay.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.SdJwtVc.Models.Issuer -{ - /// - /// Represents the visual representations for the Issuer. - /// - public class IssuerDisplay - { - /// - /// Gets or sets the name of the Issuer. - /// - [JsonProperty("name")] - public string? Name { get; set; } - - /// - /// Gets or sets the locale, which represents the specific culture or region. - /// - [JsonProperty("locale")] - public string? Locale { get; set; } - - /// - /// Gets or sets the logo, which represents the specific culture or region.. - /// - [JsonProperty("logo", NullValueHandling = NullValueHandling.Ignore)] - public IssuerLogo? Logo { get; set; } - - /// - /// Represents the Logo of the Issuer. - /// - public class IssuerLogo - { - /// - /// Gets or sets the alternate text that describes the logo image. This is typically used for accessibility purposes. - /// - [JsonProperty("alt_text")] - public string? AltText { get; set; } - - /// - /// Gets or sets the URL of the logo image. - /// - [JsonProperty("uri")] - public Uri Uri { get; set; } = null!; - } - } -} diff --git a/src/WalletFramework.SdJwtVc/Models/Issuer/IssuerMetadata.cs b/src/WalletFramework.SdJwtVc/Models/Issuer/IssuerMetadata.cs deleted file mode 100644 index d2f9e4f8..00000000 --- a/src/WalletFramework.SdJwtVc/Models/Issuer/IssuerMetadata.cs +++ /dev/null @@ -1,113 +0,0 @@ -using Newtonsoft.Json; -using WalletFramework.SdJwtVc.Models.Credential; -using WalletFramework.SdJwtVc.Models.Credential.Attributes; - -namespace WalletFramework.SdJwtVc.Models.Issuer -{ - // Todo: Clean up this class and remove the unnecessary fields - /// - /// Represents the metadata of an SdJwtVc issuer. - /// - public class IssuerMetadata - { - /// - /// Gets or sets a dictionary which maps a CredentialMetadataId to its credential metadata. - /// - [JsonProperty("credential_configurations_supported")] - public Dictionary CredentialConfigurationsSupported { get; set; } = null!; - - /// - /// Gets or sets a list of display properties of a Credential Issuer for different languages. - /// - [JsonProperty("display", NullValueHandling = NullValueHandling.Ignore)] - public List? Display { get; set; } - - /// - /// Gets or sets the URL of the Credential Issuer's Credential Endpoint. - /// - [JsonProperty("credential_endpoint")] - public string CredentialEndpoint { get; set; } = null!; - - /// - /// Gets or sets the identifier of the Credential Issuer. - /// - [JsonProperty("credential_issuer")] - public string CredentialIssuer { get; set; } = null!; - - /// - /// Gets or sets the identifier of the OAuth 2.0 Authorization Server that the Credential Issuer relies on for - /// authorization. If this property is omitted, it is assumed that the entity providing the Credential Issuer - /// is also acting as the Authorization Server. In such cases, the Credential Issuer's - /// identifier is used as the OAuth 2.0 Issuer value to obtain the Authorization Server - /// metadata. - /// - [JsonProperty("authorization_servers", NullValueHandling = NullValueHandling.Ignore)] - public string[]? AuthorizationServers { get; set; } - - /// - /// Gets the display properties of a given Credential for different languages. - /// - /// The credentialMetadataId to retrieve the display properties for. - /// - /// A list of display properties for the specified Credential or null if the Credential is not found in the - /// metadata. - /// - public List? GetCredentialDisplay(string credentialMetadataId) - => CredentialConfigurationsSupported[credentialMetadataId].Display; - - /// - /// Gets the claim attributes of a given Credential. - /// - /// The credentialMetadataId to retrieve the claim attributes for. - /// - /// A dictionary of attribute names and their corresponding display properties for the specified Credential, or - /// null if the Credential is not found in the metadata. - /// - public Dictionary? GetCredentialClaims(string credentialMetadataId) => - CredentialConfigurationsSupported[credentialMetadataId].Claims; - - /// - /// Gets the localized attribute names of a given Credential for a specific locale. - /// - /// The credentialMetadataId to retrieve the localized attribute names for. - /// The locale to retrieve the attribute names in (e.g., "en-US"). - /// - /// A list of localized attribute names for the specified Credential and locale, or null if no matching attributes - /// are found. - /// - public List? GetLocalizedCredentialAttributeNames(string credentialMetadataId, string locale) - { - var displayNames = new List(); - - var matchingCredential = CredentialConfigurationsSupported[credentialMetadataId]; - - if (matchingCredential == null) - return null; - - var localeDisplayNames = matchingCredential.Claims - .SelectMany(subject => subject.Value.Display) - .Where(display => display.Locale == locale) - .Select(display => display.Name); - - displayNames.AddRange(localeDisplayNames!); - - return displayNames.Count > 0 ? displayNames : null; - } - - /// - /// Gets the localized alias name of the Credential Issuer for a specific locale. - /// - /// The locale to retrieve the issuer alias name in (e.g., "en-US"). - /// - /// The localized alias name for the specified locale, or null if no matching alias is found. - /// - public string? GetLocalizedIssuerAlias(string locale) - { - if (Display == null) - return string.Empty; - - var display = Display.FirstOrDefault(display => display.Locale == locale); - return display?.Name; - } - } -} diff --git a/src/WalletFramework.SdJwtVc/Models/Records/SdJwtRecord.cs b/src/WalletFramework.SdJwtVc/Models/Records/SdJwtRecord.cs index 715ba8a1..625034e8 100644 --- a/src/WalletFramework.SdJwtVc/Models/Records/SdJwtRecord.cs +++ b/src/WalletFramework.SdJwtVc/Models/Records/SdJwtRecord.cs @@ -4,237 +4,244 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SD_JWT.Models; +using WalletFramework.Core.Cryptography.Models; +using WalletFramework.Core.Functional; using WalletFramework.SdJwtVc.Models.Credential; using WalletFramework.SdJwtVc.Models.Credential.Attributes; -using WalletFramework.SdJwtVc.Models.Issuer; -namespace WalletFramework.SdJwtVc.Models.Records +namespace WalletFramework.SdJwtVc.Models.Records; + +/// +/// A record that represents a Selective Disclosure JSON Web Token (SD-JWT) Credential with additional properties. +/// Inherits from base class RecordBase. +/// +public sealed class SdJwtRecord : RecordBase, ICredential { /// - /// A record that represents a Selective Disclosure JSON Web Token (SD-JWT) Credential with additional properties. - /// Inherits from base class RecordBase. + /// Gets or sets the attributes that should be displayed. + /// + public Dictionary? DisplayedAttributes { get; set; } + + /// + /// Gets or sets the attributes that should be displayed. + /// + public List? AttributeOrder { get; set; } + + /// + /// Gets or sets the flattened structure of the claims in the credential. + /// The key is a JSON path to the claim value in the decoded SdJwtVc. + /// + public Dictionary Claims { get; set; } + + /// + /// Gets or sets the name of the issuer in different languages. + /// + public Dictionary? IssuerName { get; set; } + + /// + /// Gets the disclosures. + /// + public ImmutableArray Disclosures { get; set; } + + /// + /// Gets or sets the display of the credential. + /// + public List? Display { get; set; } + + /// + /// Gets the Issuer-signed JWT part of the SD-JWT. + /// + public string EncodedIssuerSignedJwt { get; set; } = null!; + + /// + /// Gets or sets the identifier for the issuer. + /// + [JsonIgnore] + public string IssuerId + { + get => Get(); + set => Set(value, false); + } + + /// + /// Gets the key record ID. /// - public sealed class SdJwtRecord : RecordBase, ICredential + [JsonIgnore] + public KeyId KeyId { - /// - /// Gets or sets the attributes that should be displayed. - /// - public Dictionary? DisplayedAttributes { get; set; } - - /// - /// Gets or sets the attributes that should be displayed. - /// - public List? AttributeOrder { get; set; } - - /// - /// Gets or sets the flattened structure of the claims in the credential. - /// The key is a JSON path to the claim value in the decoded SdJwtVc. - /// - public Dictionary Claims { get; set; } - - /// - /// Gets or sets the name of the issuer in different languages. - /// - public Dictionary? IssuerName { get; set; } - - /// - /// Gets the disclosures. - /// - public ImmutableArray Disclosures { get; set; } - - /// - /// Gets or sets the display of the credential. - /// - public List? Display { get; set; } - - /// - /// Gets the Issuer-signed JWT part of the SD-JWT. - /// - public string EncodedIssuerSignedJwt { get; set; } = null!; - - /// - /// Gets or sets the identifier for the issuer. - /// - [JsonIgnore] - public string IssuerId + get { - get => Get(); - set => Set(value, false); + var str = Get(); + return KeyId + .ValidKeyId(str) + .UnwrapOrThrow(new InvalidOperationException("Persisted Key-ID in SD-JWT Record is corrupt")); } - - /// - /// Gets or sets the key record ID. - /// - [JsonIgnore] - public string KeyId + private set { - get => Get(); - set => Set(value); + string keyId = value; + Set(keyId); } + } - /// - public override string TypeName => "AF.SdJwtRecord"; + /// + public override string TypeName => "AF.SdJwtRecord"; - /// - /// Gets the verifiable credential type. - /// - [JsonIgnore] - public string Vct - { - get => Get(); - set => Set(value, false); - } + /// + /// Gets the verifiable credential type. + /// + [JsonIgnore] + public string Vct + { + get => Get(); + set => Set(value, false); + } + #pragma warning disable CS8618 - /// - /// Parameterless Default Constructor. - /// - public SdJwtRecord() - { - } + /// + /// Parameterless Default Constructor. + /// + public SdJwtRecord() + { + } #pragma warning restore CS8618 - /// - /// Constructor for Serialization. - /// - /// The attributes that should be displayed. - /// The claims made. - /// The name of the issuer in different languages. - /// The disclosures. - /// The display of the credential. - /// The Issuer-signed JWT part of the SD-JWT. - /// The identifier. - /// The identifier for the issuer. - /// The key record ID. - /// The verifiable credential type. - [JsonConstructor] - public SdJwtRecord( - Dictionary displayedAttributes, - Dictionary claims, - Dictionary issuerName, - ImmutableArray disclosures, - List display, - string encodedIssuerSignedJwt, - string issuerId, - string keyId, - string vct) - { - Id = Guid.NewGuid().ToString(); + /// + /// Constructor for Serialization. + /// + /// The attributes that should be displayed. + /// The claims made. + /// The name of the issuer in different languages. + /// The disclosures. + /// The display of the credential. + /// + /// The Issuer-signed JWT part of the SD-JWT. + [JsonConstructor] + public SdJwtRecord( + Dictionary displayedAttributes, + Dictionary claims, + Dictionary issuerName, + ImmutableArray disclosures, + List display, + string issuerId, + string encodedIssuerSignedJwt) + { + Claims = claims; + Disclosures = disclosures; + + Display = display; + DisplayedAttributes = displayedAttributes; - Claims = claims; - Disclosures = disclosures; + EncodedIssuerSignedJwt = encodedIssuerSignedJwt; - Display = display; - DisplayedAttributes = displayedAttributes; + IssuerName = issuerName; + + IssuerId = issuerId; + } + + public SdJwtRecord( + string serializedSdJwtWithDisclosures, + Dictionary displayedAttributes, + List display, + Dictionary issuerName, + KeyId keyId) + { + Id = Guid.NewGuid().ToString(); - EncodedIssuerSignedJwt = encodedIssuerSignedJwt; + var sdJwtDoc = new SdJwtDoc(serializedSdJwtWithDisclosures); + EncodedIssuerSignedJwt = sdJwtDoc.IssuerSignedJwt; + Disclosures = sdJwtDoc.Disclosures.Select(x => x.Serialize()).ToImmutableArray(); + Claims = sdJwtDoc.GetAllSubjectClaims(); + Display = display; + DisplayedAttributes = displayedAttributes; - IssuerId = issuerId; - IssuerName = issuerName; + IssuerName = issuerName; - KeyId = keyId; - Vct = vct; - } - - public SdJwtRecord( - string serializedSdJwtWithDisclosures, - Dictionary displayedAttributes, - List display, - Dictionary issuerName, - string keyId - ) - { - Id = Guid.NewGuid().ToString(); + KeyId = keyId; + IssuerId = sdJwtDoc.UnsecuredPayload.SelectToken("iss")?.Value() + ?? throw new ArgumentNullException(nameof(IssuerId), "iss claim is missing or null"); + Vct = sdJwtDoc.UnsecuredPayload.SelectToken("vct")?.Value() + ?? throw new ArgumentNullException(nameof(Vct), "vct claim is missing or null"); + } + + public SdJwtRecord( + SdJwtDoc sdJwtDoc, + Dictionary displayedAttributes, + List display, + Dictionary issuerName, + KeyId keyId) + { + Id = Guid.NewGuid().ToString(); - SdJwtDoc sdJwtDoc = new SdJwtDoc(serializedSdJwtWithDisclosures); - EncodedIssuerSignedJwt = sdJwtDoc.IssuerSignedJwt; - Disclosures = sdJwtDoc.Disclosures.Select(x => x.Serialize()).ToImmutableArray(); - Claims = sdJwtDoc.GetAllSubjectClaims(); - Display = display; - DisplayedAttributes = displayedAttributes; + EncodedIssuerSignedJwt = sdJwtDoc.IssuerSignedJwt; + Disclosures = sdJwtDoc.Disclosures.Select(disclosure => disclosure.Serialize()).ToImmutableArray(); + Claims = sdJwtDoc.GetAllSubjectClaims(); + Display = display; + DisplayedAttributes = displayedAttributes; - IssuerName = issuerName; + IssuerName = issuerName; - KeyId = keyId; - IssuerId = sdJwtDoc.UnsecuredPayload.SelectToken("iss")?.Value() ?? - throw new ArgumentNullException(nameof(IssuerId), "iss claim is missing or null"); - Vct = sdJwtDoc.UnsecuredPayload.SelectToken("vct")?.Value() ?? - throw new ArgumentNullException(nameof(Vct), "vct claim is missing or null"); // Extract vct - } - - /// - /// Creates a dictionary of the issuer name in different languages based on the issuer metadata. - /// - /// The issuer metadata. - /// The dictionary of the issuer name in different languages. - private static Dictionary? CreateIssuerNameDictionary(IssuerMetadata issuerMetadata) - { - var issuerNameDictionary = new Dictionary(); - - foreach (var display in issuerMetadata.Display?.Where(d => d.Locale != null) ?? - Enumerable.Empty()) - { - issuerNameDictionary[display.Locale!] = display.Name!; - } - - return issuerNameDictionary.Count > 0 ? issuerNameDictionary : null; - } + KeyId = keyId; + IssuerId = sdJwtDoc.UnsecuredPayload.SelectToken("iss")?.Value() + ?? throw new ArgumentNullException(nameof(IssuerId), "iss claim is missing or null"); + Vct = sdJwtDoc.UnsecuredPayload.SelectToken("vct")?.Value() + ?? throw new ArgumentNullException(nameof(Vct), "vct claim is missing or null"); } +} - internal static class JsonExtensions +internal static class JsonExtensions +{ + internal static Dictionary GetAllSubjectClaims(this SdJwtDoc sdJwtDoc) { - internal static Dictionary GetAllSubjectClaims(this SdJwtDoc sdJwtDoc) - { - var unsecuredPayload = (JObject)sdJwtDoc.UnsecuredPayload.DeepClone(); + var unsecuredPayload = (JObject)sdJwtDoc.UnsecuredPayload.DeepClone(); - RemoveRegisteredClaims(unsecuredPayload); + RemoveRegisteredClaims(unsecuredPayload); - var allLeafClaims = GetLeafNodePaths(unsecuredPayload); + var allLeafClaims = GetLeafNodePaths(unsecuredPayload); - return allLeafClaims.ToDictionary(key => key, key => unsecuredPayload.SelectToken(key)?.ToString() ?? string.Empty); + return allLeafClaims.ToDictionary(key => key, key => unsecuredPayload.SelectToken(key)?.ToString() ?? string.Empty); - void RemoveRegisteredClaims(JObject jObject) + void RemoveRegisteredClaims(JObject jObject) + { + string[] registeredClaims = { "iss", "sub", "aud", "exp", "nbf", "iat", "jti", "vct", "cnf", "status" }; + foreach (var claim in registeredClaims) { - string[] registeredClaims = { "iss", "sub", "aud", "exp", "nbf", "iat", "jti", "vct", "cnf", "status" }; - foreach (var claim in registeredClaims) - { - jObject.Remove(claim); - } + jObject.Remove(claim); } } + } - private static List GetLeafNodePaths(JObject jObject) - { - var leafNodePaths = new List(); + private static List GetLeafNodePaths(JObject jObject) + { + var leafNodePaths = new List(); - TraverseJToken(jObject, "", leafNodePaths); + TraverseJToken(jObject, "", leafNodePaths); - return leafNodePaths; - } + return leafNodePaths; + } - private static void TraverseJToken(JToken token, string currentPath, List leafNodePaths) + private static void TraverseJToken(JToken token, string currentPath, List leafNodePaths) + { + switch (token.Type) { - switch (token.Type) - { - case JTokenType.Object: - foreach (var property in token.Children()) - { - TraverseJToken(property.Value, $"{currentPath}.{property.Name}", leafNodePaths); - } - break; - - case JTokenType.Array: - int index = 0; - foreach (var item in token.Children()) - { - TraverseJToken(item, $"{currentPath}[{index}]", leafNodePaths); - index++; - } - break; - - default: - leafNodePaths.Add(currentPath.TrimStart('.')); - break; - } + case JTokenType.Object: + foreach (var property in token.Children()) + { + TraverseJToken(property.Value, $"{currentPath}.{property.Name}", leafNodePaths); + } + break; + + case JTokenType.Array: + int index = 0; + foreach (var item in token.Children()) + { + TraverseJToken(item, $"{currentPath}[{index}]", leafNodePaths); + index++; + } + break; + + default: + leafNodePaths.Add(currentPath.TrimStart('.')); + break; } } } diff --git a/src/WalletFramework.SdJwtVc/Models/Vct.cs b/src/WalletFramework.SdJwtVc/Models/Vct.cs new file mode 100644 index 00000000..eee00c2b --- /dev/null +++ b/src/WalletFramework.SdJwtVc/Models/Vct.cs @@ -0,0 +1,30 @@ +using System.Globalization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Functional.Errors; +using WalletFramework.Core.Json; +using WalletFramework.Core.Json.Converters; + +namespace WalletFramework.SdJwtVc.Models; + +[JsonConverter(typeof(ValueTypeJsonConverter))] +public readonly struct Vct +{ + private string Value { get; } + + private Vct(string vct) => Value = vct; + + public override string ToString() => Value; + + public static implicit operator string(Vct vct) => vct.Value; + + public static Validation ValidVct(JToken vct) => vct.ToJValue().OnSuccess(value => + { + var str = value.ToString(CultureInfo.InvariantCulture); + if (string.IsNullOrWhiteSpace(str)) + return new StringIsNullOrWhitespaceError().ToInvalid(); + + return new Vct(str); + }); +} diff --git a/src/WalletFramework.SdJwtVc/ServiceCollectionExtensions.cs b/src/WalletFramework.SdJwtVc/ServiceCollectionExtensions.cs index fa14b6b6..7dbfe4fe 100644 --- a/src/WalletFramework.SdJwtVc/ServiceCollectionExtensions.cs +++ b/src/WalletFramework.SdJwtVc/ServiceCollectionExtensions.cs @@ -7,28 +7,24 @@ namespace WalletFramework.SdJwtVc; public static class ServiceCollectionExtensions { - public static IServiceCollection AddSdJwtVcDefaultServices(this IServiceCollection builder) + public static IServiceCollection AddSdJwtVcServices(this IServiceCollection builder) { builder.AddSingleton(); - builder.AddSingleton(); + builder.AddSingleton(); return builder; } - /// /// Adds the extended Sd-JWT credential service. /// /// The extended SD-JWT credential service. /// Builder. - /// The 1st type parameter. /// The 2nd type parameter. - public static IServiceCollection AddExtendedSdJwtCredentialService(this IServiceCollection builder) - where TService : class, ISdJwtVcHolderService - where TImplementation : class, TService, ISdJwtVcHolderService + public static IServiceCollection AddExtendedSdJwtHolderService(this IServiceCollection builder) + where TImplementation : class, ISdJwtVcHolderService { builder.AddSingleton(); builder.AddSingleton(x => x.GetService()); - builder.AddSingleton(x => x.GetService()); return builder; } } diff --git a/src/WalletFramework.SdJwtVc/Services/SdJwtVcHolderService/DefaultSdJwtVcHolderService.cs b/src/WalletFramework.SdJwtVc/Services/SdJwtVcHolderService/DefaultSdJwtVcHolderService.cs deleted file mode 100644 index 6cc0d257..00000000 --- a/src/WalletFramework.SdJwtVc/Services/SdJwtVcHolderService/DefaultSdJwtVcHolderService.cs +++ /dev/null @@ -1,140 +0,0 @@ -using Hyperledger.Aries; -using Hyperledger.Aries.Agents; -using Hyperledger.Aries.Storage; -using Hyperledger.Indy.WalletApi; -using SD_JWT.Models; -using SD_JWT.Roles; -using WalletFramework.SdJwtVc.KeyStore.Services; -using WalletFramework.SdJwtVc.Models.Credential; -using WalletFramework.SdJwtVc.Models.Credential.Attributes; -using WalletFramework.SdJwtVc.Models.Issuer; -using WalletFramework.SdJwtVc.Models.Records; - -namespace WalletFramework.SdJwtVc.Services.SdJwtVcHolderService -{ - /// - public class DefaultSdJwtVcHolderService : ISdJwtVcHolderService - { - /// - /// The service responsible for holder operations. - /// - protected readonly IHolder Holder; - - /// - /// The key store responsible for key operations. - /// - protected readonly IKeyStore KeyStore; - - /// - /// The service responsible for wallet record operations. - /// - protected readonly IWalletRecordService RecordService; - - /// - /// Initializes a new instance of the class. - /// - /// The key store responsible for key operations. - /// The service responsible for wallet record operations. - /// The service responsible for holder operations. - public DefaultSdJwtVcHolderService( - IHolder holder, - IKeyStore keyStore, - IWalletRecordService recordService) - { - Holder = holder; - KeyStore = keyStore; - RecordService = recordService; - } - - /// - public async Task CreatePresentation(SdJwtRecord credential, string[] disclosedClaimPaths, - string? audience = null, - string? nonce = null) - { - var sdJwtDoc = credential.ToSdJwtDoc(); - var disclosures = new List(); - foreach (var disclosure in sdJwtDoc.Disclosures) - { - if (disclosedClaimPaths.Any(disclosedClaim => disclosedClaim.StartsWith(disclosure.Path ?? string.Empty))) - { - disclosures.Add(disclosure); - } - } - - var presentationFormat = Holder.CreatePresentationFormat(credential.EncodedIssuerSignedJwt, disclosures.ToArray()); - - if (!string.IsNullOrEmpty(credential.KeyId) && !string.IsNullOrEmpty(nonce) && - !string.IsNullOrEmpty(audience)) - { - var keybindingJwt = - await KeyStore.GenerateKbProofOfPossessionAsync(credential.KeyId, audience, nonce, "kb+jwt", presentationFormat.ToSdHash()); - return presentationFormat.AddKeyBindingJwt(keybindingJwt); - } - - return presentationFormat.Value; - } - - /// - public virtual async Task DeleteAsync(IAgentContext context, string recordId) - { - return await RecordService.DeleteAsync(context.Wallet, recordId); - } - - /// - public virtual async Task GetAsync(IAgentContext context, string credentialId) - { - var record = await RecordService.GetAsync(context.Wallet, credentialId); - if (record == null) - throw new AriesFrameworkException(ErrorCode.RecordNotFound, "SD-JWT Credential record not found"); - - return record; - } - - /// - public virtual Task> ListAsync(IAgentContext context, ISearchQuery query = null, - int count = 100, - int skip = 0) - { - return RecordService.SearchAsync(context.Wallet, query, null, count, skip); - } - - /// - [Obsolete("Use SaveAsync instead.")] - public virtual async Task StoreAsync( - IAgentContext context, - string combinedIssuance, - string keyId, - IssuerMetadata issuerMetadata, - List displayMetadata, - Dictionary claimMetadata, - Dictionary issuerName - ) - { - var record = new SdJwtRecord(combinedIssuance, claimMetadata, displayMetadata, issuerName, keyId); - - await SaveAsync(context, record); - return record.Id; - } - - /// - public virtual async Task SaveAsync(IAgentContext context, SdJwtRecord record) - { - try - { - await RecordService.AddAsync(context.Wallet, record); - } - catch (WalletItemAlreadyExistsException) - { - await RecordService.UpdateAsync(context.Wallet, record); - } - } - } - - internal static class SdJwtRecordExtensions - { - internal static SdJwtDoc ToSdJwtDoc(this SdJwtRecord record) - { - return new SdJwtDoc(record.EncodedIssuerSignedJwt + "~" + string.Join("~", record.Disclosures) + "~"); - } - } -} diff --git a/src/WalletFramework.SdJwtVc/Services/SdJwtVcHolderService/ISdJwtVcHolderService.cs b/src/WalletFramework.SdJwtVc/Services/SdJwtVcHolderService/ISdJwtVcHolderService.cs index 4e700b66..72e1ec9e 100644 --- a/src/WalletFramework.SdJwtVc/Services/SdJwtVcHolderService/ISdJwtVcHolderService.cs +++ b/src/WalletFramework.SdJwtVc/Services/SdJwtVcHolderService/ISdJwtVcHolderService.cs @@ -1,84 +1,75 @@ using Hyperledger.Aries.Agents; using Hyperledger.Aries.Storage; -using WalletFramework.SdJwtVc.Models.Credential; -using WalletFramework.SdJwtVc.Models.Credential.Attributes; -using WalletFramework.SdJwtVc.Models.Issuer; using WalletFramework.SdJwtVc.Models.Records; -namespace WalletFramework.SdJwtVc.Services.SdJwtVcHolderService +namespace WalletFramework.SdJwtVc.Services.SdJwtVcHolderService; + +/// +/// Provides methods for handling SD-JWT credentials. +/// +public interface ISdJwtVcHolderService { /// - /// Provides methods for handling SD-JWT credentials. + /// Deletes a specific SD-JWT record by its ID. /// - public interface ISdJwtVcHolderService - { - /// - /// Deletes a specific SD-JWT record by its ID. - /// - /// The agent context. - /// The ID of the SD-JWT credential record to delete. - /// - /// A task representing the asynchronous operation. The task result indicates whether the deletion was successful. - /// - Task DeleteAsync(IAgentContext context, string recordId); + /// The agent context. + /// The ID of the SD-JWT credential record to delete. + /// + /// A task representing the asynchronous operation. The task result indicates whether the deletion was successful. + /// + Task DeleteAsync(IAgentContext context, string recordId); - /// - /// Creates a SD-JWT in presentation format where the provided claims are disclosed. - /// The key binding is optional and can be activated by providing an audience and a nonce. - /// - /// - /// The SD-JWT is created using the provided SD-JWT credential and the provided claims are disclosed - /// - /// The claims to disclose - /// The SD-JWT credential - /// The targeted audience - /// The nonce - /// The SD-JWT in presentation format - Task CreatePresentation(SdJwtRecord credential, string[] disclosedClaimPaths, string? audience = null, string? nonce = null); + /// + /// Creates a SD-JWT in presentation format where the provided claims are disclosed. + /// The key binding is optional and can be activated by providing an audience and a nonce. + /// + /// + /// The SD-JWT is created using the provided SD-JWT credential and the provided claims are disclosed + /// + /// The claims to disclose + /// The SD-JWT credential + /// The targeted audience + /// The nonce + /// The SD-JWT in presentation format + Task CreatePresentation( + SdJwtRecord credential, + string[] disclosedClaimPaths, + string? audience = null, + string? nonce = null); - /// - /// Retrieves a specific SD-JWT record by its ID. - /// - /// The agent context. - /// The ID of the SD-JWT credential record to retrieve. - /// - /// A task representing the asynchronous operation. The task result contains the - /// associated with the given ID. - /// - Task GetAsync(IAgentContext context, string credentialId); + /// + /// Retrieves a specific SD-JWT record by its ID. + /// + /// The agent context. + /// The ID of the SD-JWT credential record to retrieve. + /// + /// A task representing the asynchronous operation. The task result contains the + /// associated with the given ID. + /// + Task GetAsync(IAgentContext context, string credentialId); - /// - /// Lists SD-JWT records based on specified criteria. - /// - /// The agent context. - /// The search query to filter SD-JWT records. Default is null, meaning no filter. - /// The maximum number of records to retrieve. Default is 100. - /// The number of records to skip. Default is 0. - /// - /// A task representing the asynchronous operation. The task result contains a list of - /// that match the criteria. - /// - Task> ListAsync(IAgentContext context, ISearchQuery? query = null, int count = 100, - int skip = 0); + /// + /// Lists SD-JWT records based on specified criteria. + /// + /// The agent context. + /// The search query to filter SD-JWT records. Default is null, meaning no filter. + /// The maximum number of records to retrieve. Default is 100. + /// The number of records to skip. Default is 0. + /// + /// A task representing the asynchronous operation. The task result contains a list of + /// that match the criteria. + /// + Task> ListAsync( + IAgentContext context, + ISearchQuery? query = null, + int count = 100, + int skip = 0); - /// - /// Stores a new SD-JWT record. - /// - /// The agent context. - /// The combined issuance. - /// The key id. - /// The issuer metadata. - /// - /// - /// - /// A task representing the asynchronous operation. The task result contains the ID of the stored JWT record. - Task StoreAsync( - IAgentContext context, - string combinedIssuance, - string keyId, - IssuerMetadata issuerMetadata, - List displayMetadata, - Dictionary claimMetadata, - Dictionary issuerName); - } + /// + /// Stores or updates a SD-JWT record. + /// + /// The agent context. + /// The SD-JWT record to be saved + /// A task representing the asynchronous operation. The task result contains the ID of the stored JWT record. + Task SaveAsync(IAgentContext context, SdJwtRecord record); } diff --git a/src/WalletFramework.SdJwtVc/Services/SdJwtVcHolderService/SdJwtVcHolderService.cs b/src/WalletFramework.SdJwtVc/Services/SdJwtVcHolderService/SdJwtVcHolderService.cs new file mode 100644 index 00000000..7597ed02 --- /dev/null +++ b/src/WalletFramework.SdJwtVc/Services/SdJwtVcHolderService/SdJwtVcHolderService.cs @@ -0,0 +1,113 @@ +using Hyperledger.Aries; +using Hyperledger.Aries.Agents; +using Hyperledger.Aries.Storage; +using Hyperledger.Indy.WalletApi; +using SD_JWT.Models; +using SD_JWT.Roles; +using WalletFramework.Core.Cryptography.Abstractions; +using WalletFramework.SdJwtVc.Models.Records; + +namespace WalletFramework.SdJwtVc.Services.SdJwtVcHolderService; + +/// +public class SdJwtVcHolderService : ISdJwtVcHolderService +{ + /// + /// Initializes a new instance of the class. + /// + /// The key store responsible for key operations. + /// The service responsible for wallet record operations. + /// The service responsible for holder operations. + public SdJwtVcHolderService( + IHolder holder, + IKeyStore keyStore, + IWalletRecordService recordService) + { + _holder = holder; + _keyStore = keyStore; + _recordService = recordService; + } + + private readonly IHolder _holder; + private readonly IKeyStore _keyStore; + private readonly IWalletRecordService _recordService; + + /// + public async Task CreatePresentation( + SdJwtRecord credential, + string[] disclosedClaimPaths, + string? audience = null, + string? nonce = null) + { + var sdJwtDoc = credential.ToSdJwtDoc(); + var disclosures = new List(); + foreach (var disclosure in sdJwtDoc.Disclosures) + { + if (disclosedClaimPaths.Any(disclosedClaim => disclosedClaim.StartsWith(disclosure.Path ?? string.Empty))) + { + disclosures.Add(disclosure); + } + } + + var presentationFormat = + _holder.CreatePresentationFormat(credential.EncodedIssuerSignedJwt, disclosures.ToArray()); + + if (!string.IsNullOrEmpty(credential.KeyId) + && !string.IsNullOrEmpty(nonce) + && !string.IsNullOrEmpty(audience)) + { + + + var keybindingJwt = await _keyStore.GenerateKbProofOfPossessionAsync( + credential.KeyId, + audience, + nonce, + "kb+jwt", + presentationFormat.ToSdHash()); + + return presentationFormat.AddKeyBindingJwt(keybindingJwt); + } + + return presentationFormat.Value; + } + + /// + public virtual async Task DeleteAsync(IAgentContext context, string recordId) => + await _recordService.DeleteAsync(context.Wallet, recordId); + + /// + public async Task GetAsync(IAgentContext context, string credentialId) + { + var record = await _recordService.GetAsync(context.Wallet, credentialId); + if (record == null) + throw new AriesFrameworkException(ErrorCode.RecordNotFound, "SD-JWT Credential record not found"); + + return record; + } + + /// + public Task> ListAsync( + IAgentContext context, + ISearchQuery? query = null, + int count = 100, + int skip = 0) => _recordService.SearchAsync(context.Wallet, query, null, count, skip); + + /// + public virtual async Task SaveAsync(IAgentContext context, SdJwtRecord record) + { + try + { + await _recordService.AddAsync(context.Wallet, record); + } + catch (WalletItemAlreadyExistsException) + { + await _recordService.UpdateAsync(context.Wallet, record); + } + } +} + +internal static class SdJwtRecordExtensions +{ + internal static SdJwtDoc ToSdJwtDoc(this SdJwtRecord record) => + new(record.EncodedIssuerSignedJwt + "~" + string.Join("~", record.Disclosures) + "~"); +} diff --git a/src/WalletFramework.SdJwtVc/WalletFramework.SdJwtVc.csproj b/src/WalletFramework.SdJwtVc/WalletFramework.SdJwtVc.csproj index 18890638..95c50643 100644 --- a/src/WalletFramework.SdJwtVc/WalletFramework.SdJwtVc.csproj +++ b/src/WalletFramework.SdJwtVc/WalletFramework.SdJwtVc.csproj @@ -8,10 +8,10 @@ + - diff --git a/src/WalletFramework.sln b/src/WalletFramework.sln index eb5211af..56766f62 100644 --- a/src/WalletFramework.sln +++ b/src/WalletFramework.sln @@ -37,13 +37,19 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.Oid4Vc.Test EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.SdJwtVc.Tests", "..\test\WalletFramework.SdJwtVc.Tests\WalletFramework.SdJwtVc.Tests.csproj", "{16BE497A-1ED7-41CD-AD68-BEB0E2A0AC0B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.Mdoc", "WalletFramework.Mdoc\WalletFramework.Mdoc.csproj", "{0EDD27CB-967F-4451-81AE-309E7F534F1C}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.MdocLib", "WalletFramework.MdocLib\WalletFramework.MdocLib.csproj", "{0EDD27CB-967F-4451-81AE-309E7F534F1C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.Mdoc.Tests", "..\test\WalletFramework.Mdoc.Tests\WalletFramework.Mdoc.Tests.csproj", "{15D8778B-F83C-4DDF-B30E-8F95F79ACAE8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.MdocLib.Tests", "..\test\WalletFramework.MdocLib.Tests\WalletFramework.MdocLib.Tests.csproj", "{15D8778B-F83C-4DDF-B30E-8F95F79ACAE8}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Functional", "Functional", "{F8F02219-0462-4031-843E-6F3DC8CD1638}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.MdocVc", "WalletFramework.MdocVc\WalletFramework.MdocVc.csproj", "{D1ABF6E9-AD2C-4068-A86F-D35E78C187B7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.Functional", "WalletFramework.Functional\WalletFramework.Functional.csproj", "{314D2A44-8A23-48CC-8848-7FBDC33847F5}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.MdocVc.Tests", "..\test\WalletFramework.MdocVc.Tests\WalletFramework.MdocVc.Tests.csproj", "{A7DC0511-DE0A-4EC2-8CC1-197FB81D7A76}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.Core", "WalletFramework.Core\WalletFramework.Core.csproj", "{F6B3A24B-CDA2-4CC1-9F68-380203355099}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{873772C5-60B9-442B-B06E-C279919B963C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mdoc", "Mdoc", "{A1DD69B3-DC35-43CF-AE14-D751722F074A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -111,10 +117,18 @@ Global {15D8778B-F83C-4DDF-B30E-8F95F79ACAE8}.Debug|Any CPU.Build.0 = Debug|Any CPU {15D8778B-F83C-4DDF-B30E-8F95F79ACAE8}.Release|Any CPU.ActiveCfg = Release|Any CPU {15D8778B-F83C-4DDF-B30E-8F95F79ACAE8}.Release|Any CPU.Build.0 = Release|Any CPU - {314D2A44-8A23-48CC-8848-7FBDC33847F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {314D2A44-8A23-48CC-8848-7FBDC33847F5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {314D2A44-8A23-48CC-8848-7FBDC33847F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {314D2A44-8A23-48CC-8848-7FBDC33847F5}.Release|Any CPU.Build.0 = Release|Any CPU + {D1ABF6E9-AD2C-4068-A86F-D35E78C187B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1ABF6E9-AD2C-4068-A86F-D35E78C187B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1ABF6E9-AD2C-4068-A86F-D35E78C187B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1ABF6E9-AD2C-4068-A86F-D35E78C187B7}.Release|Any CPU.Build.0 = Release|Any CPU + {A7DC0511-DE0A-4EC2-8CC1-197FB81D7A76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7DC0511-DE0A-4EC2-8CC1-197FB81D7A76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7DC0511-DE0A-4EC2-8CC1-197FB81D7A76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7DC0511-DE0A-4EC2-8CC1-197FB81D7A76}.Release|Any CPU.Build.0 = Release|Any CPU + {F6B3A24B-CDA2-4CC1-9F68-380203355099}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F6B3A24B-CDA2-4CC1-9F68-380203355099}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F6B3A24B-CDA2-4CC1-9F68-380203355099}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F6B3A24B-CDA2-4CC1-9F68-380203355099}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -134,9 +148,11 @@ Global {FE43A9CD-1E5B-4F1F-BA08-A4A7E9A131FD} = {615A7979-79AD-4485-805F-3F4B772510CD} {8E8B8EFA-3AFE-4D17-B381-8C230A86DB88} = {02ADBA96-A50C-44F0-A9D9-FA0629AA2DF4} {16BE497A-1ED7-41CD-AD68-BEB0E2A0AC0B} = {02ADBA96-A50C-44F0-A9D9-FA0629AA2DF4} - {0EDD27CB-967F-4451-81AE-309E7F534F1C} = {615A7979-79AD-4485-805F-3F4B772510CD} {15D8778B-F83C-4DDF-B30E-8F95F79ACAE8} = {02ADBA96-A50C-44F0-A9D9-FA0629AA2DF4} - {314D2A44-8A23-48CC-8848-7FBDC33847F5} = {F8F02219-0462-4031-843E-6F3DC8CD1638} + {D1ABF6E9-AD2C-4068-A86F-D35E78C187B7} = {615A7979-79AD-4485-805F-3F4B772510CD} + {A7DC0511-DE0A-4EC2-8CC1-197FB81D7A76} = {02ADBA96-A50C-44F0-A9D9-FA0629AA2DF4} + {F6B3A24B-CDA2-4CC1-9F68-380203355099} = {873772C5-60B9-442B-B06E-C279919B963C} + {0EDD27CB-967F-4451-81AE-309E7F534F1C} = {A1DD69B3-DC35-43CF-AE14-D751722F074A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4FFA80F9-ADC6-40DB-BBD1-A522B8A68560} diff --git a/test/Hyperledger.Aries.Tests/Routing/RoutingInboxHandlerTests.cs b/test/Hyperledger.Aries.Tests/Routing/RoutingInboxHandlerTests.cs index ee6355ba..4f833337 100644 --- a/test/Hyperledger.Aries.Tests/Routing/RoutingInboxHandlerTests.cs +++ b/test/Hyperledger.Aries.Tests/Routing/RoutingInboxHandlerTests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; using Hyperledger.Aries.Agents; @@ -9,6 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; +using Newtonsoft.Json.Linq; using Xunit; namespace Hyperledger.Aries.Tests.Routing @@ -46,7 +48,7 @@ public async Task CreateInboxRecordAsync() CreateInboxResponseMessage agentMessage = (CreateInboxResponseMessage)await routingInboxHandler.ProcessAsync(agentContext.Object, unpackedMessage); walletService.Verify(w => w.CreateWalletAsync(It.Is(wc => wc.Id == agentMessage.InboxId), It.Is(wc => wc.Key == agentMessage.InboxKey))); - recordService.Verify(m => m.AddAsync(agentContext.Object.Wallet, It.Is(i => i.Tags.Count == 0)), Times.Once()); + recordService.Verify(m => m.AddAsync(agentContext.Object.Wallet, It.Is(i => i.Tags.Count == 0), It.IsAny?>()), Times.Once()); recordService.Verify(m => m.UpdateAsync(agentContext.Object.Wallet, It.Is(c => c.GetTag("InboxId") == agentMessage.InboxId))); } @@ -72,7 +74,7 @@ public async Task CreateInboxRecordWithMetadataAsync() agentMessage.InboxKey.Should().HaveLength(44); walletService.Verify(w => w.CreateWalletAsync(It.Is(wc => wc.Id == agentMessage.InboxId), It.Is(wc => wc.Key == agentMessage.InboxKey))); - recordService.Verify(m => m.AddAsync(agentContext.Object.Wallet, It.Is(i => i.GetTag(key) == value)), Times.Once()); + recordService.Verify(m => m.AddAsync(agentContext.Object.Wallet, It.Is(i => i.GetTag(key) == value), It.IsAny?>()), Times.Once()); recordService.Verify(m => m.UpdateAsync(agentContext.Object.Wallet, It.Is(c => c.GetTag("InboxId") == agentMessage.InboxId))); } diff --git a/test/WalletFramework.Mdoc.Tests/Samples.cs b/test/WalletFramework.Mdoc.Tests/Samples.cs deleted file mode 100644 index a846f88c..00000000 --- a/test/WalletFramework.Mdoc.Tests/Samples.cs +++ /dev/null @@ -1,68 +0,0 @@ -using PeterO.Cbor; -using WalletFramework.Functional; - -namespace WalletFramework.Mdoc.Tests; - -public static class Samples -{ - public const string DocType = "org.iso.18013.5.1.mDL"; - - public const string MdlNameSpace = "org.iso.18013.5.1"; - - public static string EncodedMdoc = - "omdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiam5hbWVTcGFjZXOhcW9yZy5pc28uMTgwMTMuNS4xiNgYWFukaGRpZ2VzdElEAWZyYW5kb21QcbnmTIHt0_17t-AcHkKZbHFlbGVtZW50SWRlbnRpZmllcmppc3N1ZV9kYXRlbGVsZW1lbnRWYWx1ZdkD7GoyMDI0LTAxLTEy2BhYXKRoZGlnZXN0SUQCZnJhbmRvbVBRwvzBVJYBc2plhd7vXZwTcWVsZW1lbnRJZGVudGlmaWVya2V4cGlyeV9kYXRlbGVsZW1lbnRWYWx1ZdkD7GoyMDI1LTAxLTEy2BhYWqRoZGlnZXN0SUQDZnJhbmRvbVDcuBh2xE6SqxDDECOY9H3CcWVsZW1lbnRJZGVudGlmaWVya2ZhbWlseV9uYW1lbGVsZW1lbnRWYWx1ZWtTaWx2ZXJzdG9uZdgYWFKkaGRpZ2VzdElEBGZyYW5kb21QHu5Fe96gJQH-NeOAvSuJdHFlbGVtZW50SWRlbnRpZmllcmpnaXZlbl9uYW1lbGVsZW1lbnRWYWx1ZWRJbmdh2BhYW6RoZGlnZXN0SUQFZnJhbmRvbVDI-4b03R-29ljFhUoZMHP0cWVsZW1lbnRJZGVudGlmaWVyamJpcnRoX2RhdGVsZWxlbWVudFZhbHVl2QPsajE5OTEtMTEtMDbYGFhVpGhkaWdlc3RJRAZmcmFuZG9tUCJlXpl0UAxhiiN9BwSnLeBxZWxlbWVudElkZW50aWZpZXJvaXNzdWluZ19jb3VudHJ5bGVsZW1lbnRWYWx1ZWJVU9gYWFukaGRpZ2VzdElEB2ZyYW5kb21QbWz_ggUxytSax7_FqCzoEHFlbGVtZW50SWRlbnRpZmllcm9kb2N1bWVudF9udW1iZXJsZWxlbWVudFZhbHVlaDEyMzQ1Njc42BhYoqRoZGlnZXN0SUQIZnJhbmRvbVBbSwOg91lMspu_ctBa2uqgcWVsZW1lbnRJZGVudGlmaWVycmRyaXZpbmdfcHJpdmlsZWdlc2xlbGVtZW50VmFsdWWBo3V2ZWhpY2xlX2NhdGVnb3J5X2NvZGVhQWppc3N1ZV9kYXRl2QPsajIwMjMtMDEtMDFrZXhwaXJ5X2RhdGXZA-xqMjA0My0wMS0wMWppc3N1ZXJBdXRohEOhASahGCFZAWEwggFdMIIBBKADAgECAgYBjJHZwhkwCgYIKoZIzj0EAwIwNjE0MDIGA1UEAwwrSjFGd0pQODdDNi1RTl9XU0lPbUpBUWM2bjVDUV9iWmRhRko1R0RuVzFSazAeFw0yMzEyMjIxNDA2NTZaFw0yNDEwMTcxNDA2NTZaMDYxNDAyBgNVBAMMK0oxRndKUDg3QzYtUU5fV1NJT21KQVFjNm41Q1FfYlpkYUZKNUdEblcxUmswWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQCilV5ugmlhHJzDVgqSRE5d8KkoQqX1jVg8WE4aPjFODZQ66fFPFIhWRP3ioVUi67WGQSgTY3F6Vmjf7JMVQ4MMAoGCCqGSM49BAMCA0cAMEQCIGcWNJwFy8RGV4uMwK7k1vEkqQ2xr-BCGRdN8OZur5PeAiBVrNuxV1C9mCW5z2clhDFaXNdP2Lp_7CBQrHQoJhuPcNgYWQHopWd2ZXJzaW9uYzEuMG9kaWdlc3RBbGdvcml0aG1nU0hBLTI1Nmx2YWx1ZURpZ2VzdHOhcW9yZy5pc28uMTgwMTMuNS4xqAFYIKuS8FCeCcvDMwZgEezuuVv-DYsUpdypJp9abJrqHAmXAlggu7D-3vr-NrLg3zigunUzEKFqYAyG5sA-ffvmDjRxZ24DWCC2OBnhoZFhqE7s8PRfdej8t5frp-HgF_2X4qMtzvEY6ARYIBF_rl93VR21umkIdSMiWqFmT5Jxs0n3H5SWonWrJoDrBVggKDvVyMU358Le0n6TkVb2c0BbhbSMJwpswtPLNiZrTR8GWCAFZzJwAmnC7QcMQwq72FDQlmPxk0434cZbh6_rt1VagQdYIHwBHQ3-sVPtco-RcUhuYYq6iivujjYyJmQBbQ_OdhFDCFggcjT2HYgkoxnwWP-9jqO_6-D-d69H9UW2xjpDWrknlvBnZG9jVHlwZXVvcmcuaXNvLjE4MDEzLjUuMS5tRExsdmFsaWRpdHlJbmZvo2ZzaWduZWTAdDIwMjQtMDEtMTJUMDA6MTA6MDVaaXZhbGlkRnJvbcB0MjAyNC0wMS0xMlQwMDoxMDowNVpqdmFsaWRVbnRpbMB0MjAyNS0wMS0xMlQwMDoxMDowNVpYQHFzEb09NFyFlj533FE_1B9I2rku90K52ar64Id1CyOUXWXzhINeVfoJU1cfxgCT2CX1369cGd_TQxSjhVx8bpY"; - - public static ElementIdentifier GivenName => - ElementIdentifier.ValidElementIdentifier(CBORObject.FromObject("given_name")).Match( - identifier => identifier, - _ => throw new InvalidOperationException() - ); - - public static ElementIdentifier FamilyName => - ElementIdentifier.ValidElementIdentifier(CBORObject.FromObject("family_name")).Match( - identifier => identifier, - _ => throw new InvalidOperationException() - ); - - public static ElementIdentifier DrivingPrivileges => - ElementIdentifier.ValidElementIdentifier(CBORObject.FromObject("driving_privileges")).Match( - identifier => identifier, - _ => throw new InvalidOperationException() - ); - - public static CoseLabel Es256CoseLabel => - CoseLabel.ValidCoseLabel(CBORObject.FromObject(1)).Match( - label => label, - _ => throw new InvalidOperationException() - ); - - public static CoseLabel X509ChainCoseLabel => - CoseLabel.ValidCoseLabel(CBORObject.FromObject(33)).Match( - label => label, - _ => throw new InvalidOperationException() - ); - - public static ElementIdentifier ExpiryDateIdentifier => - ElementIdentifier.ValidElementIdentifier(CBORObject.FromObject("expiry_date")).Match( - identifier => identifier, - _ => throw new InvalidOperationException() - ); - - public static ElementIdentifier IssueDateIdentifier => - ElementIdentifier.ValidElementIdentifier(CBORObject.FromObject("issue_date")).Match( - identifier => identifier, - _ => throw new InvalidOperationException() - ); - - public static ElementIdentifier VehicleCategoryCodeIdentifier => - ElementIdentifier.ValidElementIdentifier(CBORObject.FromObject("vehicle_category_code")).Match( - identifier => identifier, - _ => throw new InvalidOperationException() - ); - - public static NameSpace DocTypeNameSpace => - NameSpace.ValidNameSpace(CBORObject.FromObject(MdlNameSpace)).Match( - space => space, - _ => throw new InvalidOperationException() - ); -} diff --git a/test/WalletFramework.Mdoc.Tests/Helpers.cs b/test/WalletFramework.MdocLib.Tests/Helpers.cs similarity index 87% rename from test/WalletFramework.Mdoc.Tests/Helpers.cs rename to test/WalletFramework.MdocLib.Tests/Helpers.cs index 420dee2c..cab055f7 100644 --- a/test/WalletFramework.Mdoc.Tests/Helpers.cs +++ b/test/WalletFramework.MdocLib.Tests/Helpers.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography; -namespace WalletFramework.Mdoc.Tests; +namespace WalletFramework.MdocLib.Tests; public static class Helpers { diff --git a/test/WalletFramework.Mdoc.Tests/MdocTests.cs b/test/WalletFramework.MdocLib.Tests/MdocTests.cs similarity index 80% rename from test/WalletFramework.Mdoc.Tests/MdocTests.cs rename to test/WalletFramework.MdocLib.Tests/MdocTests.cs index f9f64481..96a964e2 100644 --- a/test/WalletFramework.Mdoc.Tests/MdocTests.cs +++ b/test/WalletFramework.MdocLib.Tests/MdocTests.cs @@ -1,13 +1,13 @@ using FluentAssertions; using Microsoft.IdentityModel.Tokens; using PeterO.Cbor; -using WalletFramework.Functional; -using WalletFramework.Mdoc.Common; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib.Common; using Xunit; -using static WalletFramework.Mdoc.Mdoc; -using static WalletFramework.Mdoc.Common.Constants; +using static WalletFramework.MdocLib.Mdoc; +using static WalletFramework.MdocLib.Common.Constants; -namespace WalletFramework.Mdoc.Tests; +namespace WalletFramework.MdocLib.Tests; public class MdocTests { @@ -21,17 +21,8 @@ public void Can_Encode() ValidMdoc(encodedSample).Match( mdoc => { - var encoded = mdoc.Encode(); - ValidMdoc(encoded).Match( - sut => - { - // TODO: Assertion - // var equals = sut.Equals(mdoc); - // if (!equals) - // Assert.Fail("encoded mdoc is not equals to input mdoc"); - }, - _ => Assert.Fail("Encoded mdoc could not be decoded again") - ); + var sut = mdoc.Encode(); + sut.Should().Be(Samples.EncodedMdoc); }, _ => Assert.Fail("Validation of mDoc failed") ); @@ -53,9 +44,9 @@ public void Can_Parse_Mdoc() ValidMdoc(encoded).Match(sut => { // Assert - sut.DocType.Value.Should().Be(Samples.DocType); + sut.DocType.ToString().Should().Be(Samples.DocType); - var issuerSignedItems = sut.IssuerSigned.NameSpaces[Samples.DocTypeNameSpace]; + var issuerSignedItems = sut.IssuerSigned.NameSpaces[Samples.MdlIsoNameSpace]; issuerSignedItems[0].ElementId.Value.Should().Be("issue_date"); issuerSignedItems[0].ElementValue.Value.AsT0.Value.Should().Be("2024-01-12"); @@ -87,7 +78,7 @@ public void Can_Parse_Mdoc() issuerAuth.UnprotectedHeaders[Samples.X509ChainCoseLabel].Should().BeOfType(); issuerAuth.Payload.DigestAlgorithm.Value.Should().Be(DigestAlgorithmValue.Sha256); - issuerAuth.Payload.DocType.Value.Should().Be(Samples.DocType); + issuerAuth.Payload.DocType.ToString().Should().Be(Samples.DocType); issuerAuth.Payload.Version.Should().Be(new Version("1.0")); issuerAuth.Payload.ValidityInfo.ValidFrom.Should().Be(new DateTime(2024, 01, 12, 01, 10, 05)); @@ -115,10 +106,11 @@ public void Can_Selectively_Disclose() }; // Act - sut.SelectivelyDisclose(Samples.DocTypeNameSpace, disclosures); + sut.SelectivelyDisclose(Samples.MdlIsoNameSpace, disclosures); // Assert - var items = sut.IssuerSigned.NameSpaces.Value[Samples.DocTypeNameSpace]; + var items = sut.IssuerSigned.NameSpaces.Value[Samples.MdlIsoNameSpace]; + items.Count.Should().Be(disclosures.Count); items.Should().Contain(item => item.ElementId.Value == Samples.GivenName); items.Should().Contain(item => item.ElementId.Value == Samples.FamilyName); items.Should().Contain(item => item.ElementId.Value == Samples.DrivingPrivileges); @@ -144,20 +136,11 @@ public void Can_Selectively_Disclose_And_Encode() }; // Act - mdoc.SelectivelyDisclose(Samples.DocTypeNameSpace, disclosures); - var encoded = mdoc.Encode(); + mdoc.SelectivelyDisclose(Samples.MdlIsoNameSpace, disclosures); + var sut = mdoc.Encode(); // Assert - ValidMdoc(encoded).Match( - sut => - { - // TODO: Assertion - // var equals = mdoc.Equals(sampleMdoc); - // if (!equals) - // Assert.Fail("encoded mdoc is not equals to input mdoc"); - }, - _ => Assert.Fail("Encoded mdoc could not be decoded again") - ); + sut.Should().Be(Samples.SelectivelyDisclosedEncodedMdoc); }, _ => Assert.Fail("Validation of mDoc failed") ); @@ -171,7 +154,8 @@ public void Mdoc_With_Invalid_Digests_Is_Rejected() var base64 = Base64UrlEncoder.DecodeBytes(mdoc); var cbor = CBORObject.DecodeFromBytes(base64); - var msoBytes = cbor[IssuerSignedLabel][IssuerAuthLabel][2].GetByteString(); + var msoEncodedBytes = cbor[IssuerSignedLabel][IssuerAuthLabel][2].GetByteString(); + var msoBytes = CBORObject.DecodeFromBytes(msoEncodedBytes).GetByteString(); var mso = CBORObject.DecodeFromBytes(msoBytes); var valueDigests = mso["valueDigests"][Samples.MdlNameSpace]; @@ -182,7 +166,8 @@ public void Mdoc_With_Invalid_Digests_Is_Rejected() var corruptedDigestsBytes = CBORObject.FromObject(valueDigests); mso["valueDigests"][Samples.MdlNameSpace] = corruptedDigestsBytes; var encodedMso = CBORObject.FromObject(mso.EncodeToBytes()); - cbor[IssuerSignedLabel][IssuerAuthLabel][2] = encodedMso; + var encodedIssuerAuthPayload = CBORObject.FromObject(CBORObject.FromObject(encodedMso).EncodeToBytes()); + cbor[IssuerSignedLabel][IssuerAuthLabel][2] = encodedIssuerAuthPayload; var corruptedMdocBytes = cbor.EncodeToBytes(); var corruptedMdoc = Base64UrlEncoder.Encode(corruptedMdocBytes); @@ -190,7 +175,7 @@ public void Mdoc_With_Invalid_Digests_Is_Rejected() // Act ValidMdoc(corruptedMdoc).Match( // Assert - _ => Assert.Fail("mdoc must not be valid"), + _ => Assert.Fail("mdoc must not invalid"), sut => { sut.Should().Contain(error => ((InvalidDigestError)error).Id.Value == 2); @@ -218,7 +203,7 @@ public void Mdoc_With_Invalid_Structure_Is_Rejected() // Act ValidMdoc(encoded).Match( - _ => Assert.Fail("Mdoc must not be valid"), + _ => Assert.Fail("Mdoc must invalid"), sut => { sut.Should().ContainItemsAssignableTo(); diff --git a/test/WalletFramework.MdocLib.Tests/Samples.cs b/test/WalletFramework.MdocLib.Tests/Samples.cs new file mode 100644 index 00000000..33f63832 --- /dev/null +++ b/test/WalletFramework.MdocLib.Tests/Samples.cs @@ -0,0 +1,71 @@ +using PeterO.Cbor; +using WalletFramework.Core.Functional; + +namespace WalletFramework.MdocLib.Tests; + +public static class Samples +{ + public const string DocType = "org.iso.18013.5.1.mDL"; + + public const string MdlNameSpace = "org.iso.18013.5.1"; + + public static string EncodedMdoc = + "omdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiamlzc3VlckF1dGiEQ6EBJqEYIVkBYTCCAV0wggEEoAMCAQICBgGMkdnCGTAKBggqhkjOPQQDAjA2MTQwMgYDVQQDDCtKMUZ3SlA4N0M2LVFOX1dTSU9tSkFRYzZuNUNRX2JaZGFGSjVHRG5XMVJrMB4XDTIzMTIyMjE0MDY1NloXDTI0MTAxNzE0MDY1NlowNjE0MDIGA1UEAwwrSjFGd0pQODdDNi1RTl9XU0lPbUpBUWM2bjVDUV9iWmRhRko1R0RuVzFSazBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAKKVXm6CaWEcnMNWCpJETl3wqShCpfWNWDxYTho-MU4NlDrp8U8UiFZE_eKhVSLrtYZBKBNjcXpWaN_skxVDgwwCgYIKoZIzj0EAwIDRwAwRAIgZxY0nAXLxEZXi4zAruTW8SSpDbGv4EIZF03w5m6vk94CIFWs27FXUL2YJbnPZyWEMVpc10_Yun_sIFCsdCgmG49wWQHt2BhZAeilZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2bHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGoAVggq5LwUJ4Jy8MzBmAR7O65W_4NixSl3Kkmn1psmuocCZcCWCC7sP7e-v42suDfOKC6dTMQoWpgDIbmwD59--YONHFnbgNYILY4GeGhkWGoTuzw9F916Py3l-un4eAX_Zfioy3O8RjoBFggEX-uX3dVHbW6aQh1IyJaoWZPknGzSfcflJaidasmgOsFWCAoO9XIxTfnwt7SfpORVvZzQFuFtIwnCmzC08s2JmtNHwZYIAVnMnACacLtBwxDCrvYUNCWY_GTTjfhxluHr-u3VVqBB1ggfAEdDf6xU-1yj5FxSG5hirqKK-6ONjImZAFtD852EUMIWCByNPYdiCSjGfBY_72Oo7_r4P53r0f1RbbGOkNauSeW8Gdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGx2YWxpZGl0eUluZm-jZnNpZ25lZMB0MjAyNC0wMS0xMlQwMDoxMDowNVppdmFsaWRGcm9twHQyMDI0LTAxLTEyVDAwOjEwOjA1Wmp2YWxpZFVudGlswHQyMDI1LTAxLTEyVDAwOjEwOjA1WlhAcXMRvT00XIWWPnfcUT_UH0jauS73QrnZqvrgh3ULI5RdZfOEg15V-glTVx_GAJPYJfXfr1wZ39NDFKOFXHxulmpuYW1lU3BhY2VzoXFvcmcuaXNvLjE4MDEzLjUuMYjYGFhbpGhkaWdlc3RJRAFmcmFuZG9tUHG55kyB7dP9e7fgHB5CmWxxZWxlbWVudElkZW50aWZpZXJqaXNzdWVfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMjAyNC0wMS0xMtgYWFykaGRpZ2VzdElEAmZyYW5kb21QUcL8wVSWAXNqZYXe712cE3FlbGVtZW50SWRlbnRpZmllcmtleHBpcnlfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMjAyNS0wMS0xMtgYWFqkaGRpZ2VzdElEA2ZyYW5kb21Q3LgYdsROkqsQwxAjmPR9wnFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVrU2lsdmVyc3RvbmXYGFhSpGhkaWdlc3RJRARmcmFuZG9tUB7uRXveoCUB_jXjgL0riXRxZWxlbWVudElkZW50aWZpZXJqZ2l2ZW5fbmFtZWxlbGVtZW50VmFsdWVkSW5nYdgYWFukaGRpZ2VzdElEBWZyYW5kb21QyPuG9N0ftvZYxYVKGTBz9HFlbGVtZW50SWRlbnRpZmllcmpiaXJ0aF9kYXRlbGVsZW1lbnRWYWx1ZdkD7GoxOTkxLTExLTA22BhYVaRoZGlnZXN0SUQGZnJhbmRvbVAiZV6ZdFAMYYojfQcEpy3gcWVsZW1lbnRJZGVudGlmaWVyb2lzc3VpbmdfY291bnRyeWxlbGVtZW50VmFsdWViVVPYGFhbpGhkaWdlc3RJRAdmcmFuZG9tUG1s_4IFMcrUmse_xags6BBxZWxlbWVudElkZW50aWZpZXJvZG9jdW1lbnRfbnVtYmVybGVsZW1lbnRWYWx1ZWgxMjM0NTY3ONgYWKKkaGRpZ2VzdElECGZyYW5kb21QW0sDoPdZTLKbv3LQWtrqoHFlbGVtZW50SWRlbnRpZmllcnJkcml2aW5nX3ByaXZpbGVnZXNsZWxlbWVudFZhbHVlgaN1dmVoaWNsZV9jYXRlZ29yeV9jb2RlYUFqaXNzdWVfZGF0ZdkD7GoyMDIzLTAxLTAxa2V4cGlyeV9kYXRl2QPsajIwNDMtMDEtMDE"; + + public static string SelectivelyDisclosedEncodedMdoc = + "omdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiamlzc3VlckF1dGiEQ6EBJqEYIVkBYTCCAV0wggEEoAMCAQICBgGMkdnCGTAKBggqhkjOPQQDAjA2MTQwMgYDVQQDDCtKMUZ3SlA4N0M2LVFOX1dTSU9tSkFRYzZuNUNRX2JaZGFGSjVHRG5XMVJrMB4XDTIzMTIyMjE0MDY1NloXDTI0MTAxNzE0MDY1NlowNjE0MDIGA1UEAwwrSjFGd0pQODdDNi1RTl9XU0lPbUpBUWM2bjVDUV9iWmRhRko1R0RuVzFSazBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAKKVXm6CaWEcnMNWCpJETl3wqShCpfWNWDxYTho-MU4NlDrp8U8UiFZE_eKhVSLrtYZBKBNjcXpWaN_skxVDgwwCgYIKoZIzj0EAwIDRwAwRAIgZxY0nAXLxEZXi4zAruTW8SSpDbGv4EIZF03w5m6vk94CIFWs27FXUL2YJbnPZyWEMVpc10_Yun_sIFCsdCgmG49wWQHt2BhZAeilZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2bHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGoAVggq5LwUJ4Jy8MzBmAR7O65W_4NixSl3Kkmn1psmuocCZcCWCC7sP7e-v42suDfOKC6dTMQoWpgDIbmwD59--YONHFnbgNYILY4GeGhkWGoTuzw9F916Py3l-un4eAX_Zfioy3O8RjoBFggEX-uX3dVHbW6aQh1IyJaoWZPknGzSfcflJaidasmgOsFWCAoO9XIxTfnwt7SfpORVvZzQFuFtIwnCmzC08s2JmtNHwZYIAVnMnACacLtBwxDCrvYUNCWY_GTTjfhxluHr-u3VVqBB1ggfAEdDf6xU-1yj5FxSG5hirqKK-6ONjImZAFtD852EUMIWCByNPYdiCSjGfBY_72Oo7_r4P53r0f1RbbGOkNauSeW8Gdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGx2YWxpZGl0eUluZm-jZnNpZ25lZMB0MjAyNC0wMS0xMlQwMDoxMDowNVppdmFsaWRGcm9twHQyMDI0LTAxLTEyVDAwOjEwOjA1Wmp2YWxpZFVudGlswHQyMDI1LTAxLTEyVDAwOjEwOjA1WlhAcXMRvT00XIWWPnfcUT_UH0jauS73QrnZqvrgh3ULI5RdZfOEg15V-glTVx_GAJPYJfXfr1wZ39NDFKOFXHxulmpuYW1lU3BhY2VzoXFvcmcuaXNvLjE4MDEzLjUuMYPYGFhapGhkaWdlc3RJRANmcmFuZG9tUNy4GHbETpKrEMMQI5j0fcJxZWxlbWVudElkZW50aWZpZXJrZmFtaWx5X25hbWVsZWxlbWVudFZhbHVla1NpbHZlcnN0b25l2BhYUqRoZGlnZXN0SUQEZnJhbmRvbVAe7kV73qAlAf4144C9K4l0cWVsZW1lbnRJZGVudGlmaWVyamdpdmVuX25hbWVsZWxlbWVudFZhbHVlZEluZ2HYGFiipGhkaWdlc3RJRAhmcmFuZG9tUFtLA6D3WUyym79y0Fra6qBxZWxlbWVudElkZW50aWZpZXJyZHJpdmluZ19wcml2aWxlZ2VzbGVsZW1lbnRWYWx1ZYGjdXZlaGljbGVfY2F0ZWdvcnlfY29kZWFBamlzc3VlX2RhdGXZA-xqMjAyMy0wMS0wMWtleHBpcnlfZGF0ZdkD7GoyMDQzLTAxLTAx"; + + public static ElementIdentifier GivenName => + ElementIdentifier.ValidElementIdentifier(CBORObject.FromObject("given_name")).Match( + identifier => identifier, + _ => throw new InvalidOperationException() + ); + + public static ElementIdentifier FamilyName => + ElementIdentifier.ValidElementIdentifier(CBORObject.FromObject("family_name")).Match( + identifier => identifier, + _ => throw new InvalidOperationException() + ); + + public static ElementIdentifier DrivingPrivileges => + ElementIdentifier.ValidElementIdentifier(CBORObject.FromObject("driving_privileges")).Match( + identifier => identifier, + _ => throw new InvalidOperationException() + ); + + public static CoseLabel Es256CoseLabel => + CoseLabel.ValidCoseLabel(CBORObject.FromObject(1)).Match( + label => label, + _ => throw new InvalidOperationException() + ); + + public static CoseLabel X509ChainCoseLabel => + CoseLabel.ValidCoseLabel(CBORObject.FromObject(33)).Match( + label => label, + _ => throw new InvalidOperationException() + ); + + public static ElementIdentifier ExpiryDateIdentifier => + ElementIdentifier.ValidElementIdentifier(CBORObject.FromObject("expiry_date")).Match( + identifier => identifier, + _ => throw new InvalidOperationException() + ); + + public static ElementIdentifier IssueDateIdentifier => + ElementIdentifier.ValidElementIdentifier(CBORObject.FromObject("issue_date")).Match( + identifier => identifier, + _ => throw new InvalidOperationException() + ); + + public static ElementIdentifier VehicleCategoryCodeIdentifier => + ElementIdentifier.ValidElementIdentifier(CBORObject.FromObject("vehicle_category_code")).Match( + identifier => identifier, + _ => throw new InvalidOperationException() + ); + + public static NameSpace MdlIsoNameSpace => + NameSpace.ValidNameSpace(CBORObject.FromObject(MdlNameSpace)).Match( + space => space, + _ => throw new InvalidOperationException() + ); +} diff --git a/test/WalletFramework.Mdoc.Tests/WalletFramework.Mdoc.Tests.csproj b/test/WalletFramework.MdocLib.Tests/WalletFramework.MdocLib.Tests.csproj similarity index 84% rename from test/WalletFramework.Mdoc.Tests/WalletFramework.Mdoc.Tests.csproj rename to test/WalletFramework.MdocLib.Tests/WalletFramework.MdocLib.Tests.csproj index 2dd8568c..3f30eb8a 100644 --- a/test/WalletFramework.Mdoc.Tests/WalletFramework.Mdoc.Tests.csproj +++ b/test/WalletFramework.MdocLib.Tests/WalletFramework.MdocLib.Tests.csproj @@ -7,16 +7,17 @@ false true + WalletFramework.MdocLib.Tests - + - + diff --git a/test/WalletFramework.MdocVc.Tests/MdocRecordTests.cs b/test/WalletFramework.MdocVc.Tests/MdocRecordTests.cs new file mode 100644 index 00000000..32c722f0 --- /dev/null +++ b/test/WalletFramework.MdocVc.Tests/MdocRecordTests.cs @@ -0,0 +1,36 @@ +using FluentAssertions; +using Hyperledger.Aries.Storage; +using LanguageExt; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib; +using Xunit; + +namespace WalletFramework.MdocVc.Tests; + +public class MdocRecordTests +{ + [Fact] + public void Can_Encode_To_Json() + { + var encodedMdoc = MdocLib.Tests.Samples.EncodedMdoc; + var mdoc = Mdoc.ValidMdoc(encodedMdoc).UnwrapOrThrow(new InvalidOperationException("Mdoc sample is corrupt")); + var record = mdoc.ToRecord(Option>.None); + + var sut = JObject.FromObject(record); + + sut[nameof(RecordBase.Id)]!.ToString().Should().Be(record.Id); + sut[MdocRecordJsonKeys.MdocJsonKey]!.ToString().Should().Be(encodedMdoc); + } + + [Fact] + public void Can_Decode_From_Json() + { + var json = MdocVcSamples.MdocRecordJson; + + var sut = JsonConvert.DeserializeObject(json.ToString())!; + + sut.Mdoc.DocType.ToString().Should().Be(MdocLib.Tests.Samples.DocType); + } +} diff --git a/test/WalletFramework.MdocVc.Tests/MdocVcSamples.cs b/test/WalletFramework.MdocVc.Tests/MdocVcSamples.cs new file mode 100644 index 00000000..5c29c387 --- /dev/null +++ b/test/WalletFramework.MdocVc.Tests/MdocVcSamples.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json.Linq; + +namespace WalletFramework.MdocVc.Tests; + +public static class MdocVcSamples +{ + public static JObject MdocRecordJson => new() + { + ["Id"] = "5c75f511-8ebb-4f3a-9ef2-cc63eaa65cc0", + ["mdoc"] = "omdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGxpc3N1ZXJTaWduZWSiamlzc3VlckF1dGiEQ6EBJqEYIVkBYTCCAV0wggEEoAMCAQICBgGMkdnCGTAKBggqhkjOPQQDAjA2MTQwMgYDVQQDDCtKMUZ3SlA4N0M2LVFOX1dTSU9tSkFRYzZuNUNRX2JaZGFGSjVHRG5XMVJrMB4XDTIzMTIyMjE0MDY1NloXDTI0MTAxNzE0MDY1NlowNjE0MDIGA1UEAwwrSjFGd0pQODdDNi1RTl9XU0lPbUpBUWM2bjVDUV9iWmRhRko1R0RuVzFSazBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABAKKVXm6CaWEcnMNWCpJETl3wqShCpfWNWDxYTho-MU4NlDrp8U8UiFZE_eKhVSLrtYZBKBNjcXpWaN_skxVDgwwCgYIKoZIzj0EAwIDRwAwRAIgZxY0nAXLxEZXi4zAruTW8SSpDbGv4EIZF03w5m6vk94CIFWs27FXUL2YJbnPZyWEMVpc10_Yun_sIFCsdCgmG49wWQHt2BhZAeilZ3ZlcnNpb25jMS4wb2RpZ2VzdEFsZ29yaXRobWdTSEEtMjU2bHZhbHVlRGlnZXN0c6Fxb3JnLmlzby4xODAxMy41LjGoAVggq5LwUJ4Jy8MzBmAR7O65W_4NixSl3Kkmn1psmuocCZcCWCC7sP7e-v42suDfOKC6dTMQoWpgDIbmwD59--YONHFnbgNYILY4GeGhkWGoTuzw9F916Py3l-un4eAX_Zfioy3O8RjoBFggEX-uX3dVHbW6aQh1IyJaoWZPknGzSfcflJaidasmgOsFWCAoO9XIxTfnwt7SfpORVvZzQFuFtIwnCmzC08s2JmtNHwZYIAVnMnACacLtBwxDCrvYUNCWY_GTTjfhxluHr-u3VVqBB1ggfAEdDf6xU-1yj5FxSG5hirqKK-6ONjImZAFtD852EUMIWCByNPYdiCSjGfBY_72Oo7_r4P53r0f1RbbGOkNauSeW8Gdkb2NUeXBldW9yZy5pc28uMTgwMTMuNS4xLm1ETGx2YWxpZGl0eUluZm-jZnNpZ25lZMB0MjAyNC0wMS0xMlQwMDoxMDowNVppdmFsaWRGcm9twHQyMDI0LTAxLTEyVDAwOjEwOjA1Wmp2YWxpZFVudGlswHQyMDI1LTAxLTEyVDAwOjEwOjA1WlhAcXMRvT00XIWWPnfcUT_UH0jauS73QrnZqvrgh3ULI5RdZfOEg15V-glTVx_GAJPYJfXfr1wZ39NDFKOFXHxulmpuYW1lU3BhY2VzoXFvcmcuaXNvLjE4MDEzLjUuMYjYGFhbpGhkaWdlc3RJRAFmcmFuZG9tUHG55kyB7dP9e7fgHB5CmWxxZWxlbWVudElkZW50aWZpZXJqaXNzdWVfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMjAyNC0wMS0xMtgYWFykaGRpZ2VzdElEAmZyYW5kb21QUcL8wVSWAXNqZYXe712cE3FlbGVtZW50SWRlbnRpZmllcmtleHBpcnlfZGF0ZWxlbGVtZW50VmFsdWXZA-xqMjAyNS0wMS0xMtgYWFqkaGRpZ2VzdElEA2ZyYW5kb21Q3LgYdsROkqsQwxAjmPR9wnFlbGVtZW50SWRlbnRpZmllcmtmYW1pbHlfbmFtZWxlbGVtZW50VmFsdWVrU2lsdmVyc3RvbmXYGFhSpGhkaWdlc3RJRARmcmFuZG9tUB7uRXveoCUB_jXjgL0riXRxZWxlbWVudElkZW50aWZpZXJqZ2l2ZW5fbmFtZWxlbGVtZW50VmFsdWVkSW5nYdgYWFukaGRpZ2VzdElEBWZyYW5kb21QyPuG9N0ftvZYxYVKGTBz9HFlbGVtZW50SWRlbnRpZmllcmpiaXJ0aF9kYXRlbGVsZW1lbnRWYWx1ZdkD7GoxOTkxLTExLTA22BhYVaRoZGlnZXN0SUQGZnJhbmRvbVAiZV6ZdFAMYYojfQcEpy3gcWVsZW1lbnRJZGVudGlmaWVyb2lzc3VpbmdfY291bnRyeWxlbGVtZW50VmFsdWViVVPYGFhbpGhkaWdlc3RJRAdmcmFuZG9tUG1s_4IFMcrUmse_xags6BBxZWxlbWVudElkZW50aWZpZXJvZG9jdW1lbnRfbnVtYmVybGVsZW1lbnRWYWx1ZWgxMjM0NTY3ONgYWKKkaGRpZ2VzdElECGZyYW5kb21QW0sDoPdZTLKbv3LQWtrqoHFlbGVtZW50SWRlbnRpZmllcnJkcml2aW5nX3ByaXZpbGVnZXNsZWxlbWVudFZhbHVlgaN1dmVoaWNsZV9jYXRlZ29yeV9jb2RlYUFqaXNzdWVfZGF0ZdkD7GoyMDIzLTAxLTAxa2V4cGlyeV9kYXRl2QPsajIwNDMtMDEtMDE" + }; +} diff --git a/test/WalletFramework.MdocVc.Tests/WalletFramework.MdocVc.Tests.csproj b/test/WalletFramework.MdocVc.Tests/WalletFramework.MdocVc.Tests.csproj new file mode 100644 index 00000000..85de78e4 --- /dev/null +++ b/test/WalletFramework.MdocVc.Tests/WalletFramework.MdocVc.Tests.csproj @@ -0,0 +1,25 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + + + + diff --git a/test/WalletFramework.Oid4Vc.Tests/Extensions/ObjectExtensions.cs b/test/WalletFramework.Oid4Vc.Tests/Extensions/ObjectExtensions.cs index e7d623ee..e9addf63 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Extensions/ObjectExtensions.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Extensions/ObjectExtensions.cs @@ -1,18 +1,15 @@ -using System; using System.Linq.Expressions; -using System.Reflection; -namespace Hyperledger.Aries.Tests.Extensions +namespace WalletFramework.Oid4Vc.Tests.Extensions; + +public static class ObjectExtensions { - public static class ObjectExtensions + public static void PrivateSet(this T member, Expression> property, TProperty value) { - public static void PrivateSet(this T member, Expression> property, TProperty value) - { - var name = ((MemberExpression)property.Body).Member.Name; + var name = ((MemberExpression)property.Body).Member.Name; - PropertyInfo propertyInfo = typeof(T).GetProperty(name); - if (propertyInfo == null) return; - propertyInfo.SetValue(member, value); - } + var propertyInfo = typeof(T).GetProperty(name); + if (propertyInfo == null) return; + propertyInfo.SetValue(member, value); } } diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs new file mode 100644 index 00000000..b5bafb9a --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs @@ -0,0 +1,78 @@ +using FluentAssertions; +using Hyperledger.Aries.Storage; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Uri; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; +using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Records; +using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.AuthFlow.Samples; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples; + +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.AuthFlow; + +public class AuthFlowSessionRecordTests +{ + [Fact] + public void Can_Encode_To_Json() + { + // Arrange + var clientOptions = new ClientOptions + { + ClientId = "i can write anything", + WalletIssuer = "i can write anything", + RedirectUri = "i can write anything" + }; + + var issuerMetadata = IssuerMetadataSample.Decoded; + + var authorizationServerMetadata = new AuthorizationServerMetadata + { + Issuer = "i can write anything", + TokenEndpoint = "i can write anything", + JwksUri = "i can write anything", + AuthorizationEndpoint = "i can write anything", + ResponseTypesSupported = new[] { "i can write anything" }, + }; + + var credentialConfigurationId = CredentialConfigurationId + .ValidCredentialConfigurationId(IssuerMetadataSample.MdocConfigurationId.ToString()) + .UnwrapOrThrow(new InvalidOperationException()); + + var authorizationData = new AuthorizationData( + clientOptions, + issuerMetadata, + authorizationServerMetadata, + new List { credentialConfigurationId }); + + var authorizationCodeParameters = new AuthorizationCodeParameters("hello", "world"); + + var sessionId = VciSessionId.CreateSessionId(); + var record = new AuthFlowSessionRecord(authorizationData, authorizationCodeParameters, sessionId); + + // Act + var recordSut = JObject.FromObject(record); + var tagsSut = JObject.FromObject(record.Tags); + + // Assert + recordSut[nameof(RecordBase.Id)]!.ToString().Should().Be(record.Id); + tagsSut[nameof(AuthFlowSessionRecord.SessionId)] = record.SessionId.ToString(); + } + + [Fact] + public void Can_Decode_From_Json() + { + // Arrange + var json = AuthFlowSamples.AuthFlowSessionRecordJson; + + // Act + var record = JsonConvert.DeserializeObject(json.ToString()); + + // Assert + record.Should().NotBeNull(); + record!.Id.Should().Be(json[nameof(RecordBase.Id)]!.ToString()); + record.AuthorizationData.IssuerMetadata.CredentialIssuer.ToString().Should().Be(IssuerMetadataSample.CredentialIssuer.ToStringWithoutTrail()); + } +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs new file mode 100644 index 00000000..2bf810b1 --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs @@ -0,0 +1,38 @@ +using Newtonsoft.Json.Linq; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples; + +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.AuthFlow.Samples; + +public static class AuthFlowSamples +{ + public static JObject AuthFlowSessionRecordJson => new() + { + ["AuthorizationData"] = new JObject + { + ["ClientOptions"] = new JObject + { + ["ClientId"] = "https://test-issuer.com/redirect", + ["WalletIssuer"] = "i can write anything", + ["RedirectUri"] = "https://test-issuer.com/redirect" + }, + ["IssuerMetadata"] = IssuerMetadataSample.EncodedAsJson, + ["AuthorizationServerMetadata"] = new JObject + { + ["issuer"] = "i can write anything", + ["token_endpoint"] = "i can write anything", + ["jwks_uri"] = "i can write anything", + ["authorization_endpoint"] = "i can write anything", + ["response_types_supported"] = new JArray("i can write anything"), + }, + ["CredentialConfigurationIds"] = new JArray("org.iso.18013.5.1.mDL") + }, + ["AuthorizationCodeParameters"] = new JObject + { + ["Challenge"] = "hello", + ["CodeChallengeMethod"] = "S256", + ["Verifier"] = "world" + }, + ["RecordVersion"] = 1, + ["Id"] = "598e7661-95a8-4531-b707-3d256d3c1745" + }; +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/MdocConfigurationTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/MdocConfigurationTests.cs new file mode 100644 index 00000000..30916c93 --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/MdocConfigurationTests.cs @@ -0,0 +1,78 @@ +using FluentAssertions; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json.Errors; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.Mdoc; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.MdocConfiguration; + +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration; + +public class MdocConfigurationTests +{ + [Fact] + public void Can_Parse() + { + // Arrange + var sample = MdocConfigurationSample.Valid; + + // Act + ValidMdocConfiguration(sample).Match( + // Assert + sut => + { + sut.Format.Should().Be(MdocConfigurationSample.Format); + sut.DocType.Should().Be(MdocConfigurationSample.DocType); + sut.Policy.Match( + policy => + { + policy.OneTimeUse.Should().Be(MdocConfigurationSample.OneTimeUse); + policy.BatchSize.Match( + batchSize => batchSize.Should().Be(MdocConfigurationSample.BatchSize), + () => Assert.Fail("BatchSize must be some") + ); + }, + () => Assert.Fail("Policy must be some")); + + sut.CryptographicSuitesSupported.Match( + list => list.Should().Contain(MdocConfigurationSample.CryptoSuite), + () => Assert.Fail("CryptographicSuitesSupported must be some")); + + sut.CryptographicCurvesSupported.Match( + list => list.Should().Contain(MdocConfigurationSample.CryptoCurve), + () => Assert.Fail("CryptographicCurvesSupported must be some")); + + sut.Claims.Match( + claims => + { + var dict = claims.Value[MdocConfigurationSample.NameSpace]; + dict[MdocConfigurationSample.GivenName].Display.Match( + list => + { + list.Should().Contain(MdocConfigurationSample.EnglishDisplay); + list.Should().Contain(MdocConfigurationSample.JapaneseDisplay); + }, + () => Assert.Fail("Display must be some")); + }, + () => Assert.Fail("Claims must be some")); + }, + _ => Assert.Fail("Must be valid") + ); + } + + [Fact] + public void Configuration_With_Invalid_Structure_Is_Rejected() + { + // Arrange + var sample = MdocConfigurationSample.Valid; + + sample["format"] = ""; + sample["doctype"] = null; + + ValidMdocConfiguration(sample).Match( + _ => Assert.Fail("MdocConfiguration must be invalid"), + errors => + { + errors.Count.Should().Be(2); + errors.Should().AllBeOfType(); + }); + } +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwtConfigurationTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwtConfigurationTests.cs new file mode 100644 index 00000000..eccf508e --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwtConfigurationTests.cs @@ -0,0 +1,10 @@ +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration; + +public class SdJwtConfigurationTests +{ + [Fact] + public void Can_Parse() + { + + } +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredOffer/CredentialOfferTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredOffer/CredentialOfferTests.cs new file mode 100644 index 00000000..1d630c3d --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredOffer/CredentialOfferTests.cs @@ -0,0 +1,81 @@ +using FluentAssertions; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Functional.Errors; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; +using static WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models.CredentialOffer; +using static WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.CredentialOfferSample; + +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredOffer; + +public class CredentialOfferTests +{ + [Fact] + public void Can_Parse_From_Json() + { + var sample = PreAuth; + + ValidCredentialOffer(sample).Match( + offer => + { + offer.CredentialIssuer.Should().Be(CredentialIssuer); + + var ids = offer.CredentialConfigurationIds.Select(id => (string)id).ToList(); + + ids.Length().Should().Be(2); + ids.Should().Contain(UniversityDegreeCredential); + ids.Should().Contain(OrgIso1801351Mdl); + + offer.Grants.Match( + grants => + { + grants.AuthorizationCode.IsNone.Should().BeTrue(); + grants.PreAuthorizedCode.Match( + preAuthCode => + { + preAuthCode.ToString().Should().Be(PreAuthorizedCode); + preAuthCode.TransactionCode.Match( + transactionCode => + { + transactionCode.Length.Match( + length => length.Should().Be(Length), + () => Assert.Fail("Length must be some")); + + transactionCode.Description.Match( + description => description.Should().Be(Description), + () => Assert.Fail("Description must be some")); + + transactionCode.InputMode.Match( + mode => mode.ToString().Should().Be("numeric"), + () => Assert.Fail("InputMode must be some")); + }, + () => Assert.Fail("TransactionCode must be some")); + }, + () => Assert.Fail("PreAuthorizedCode must be some")); + }, + () => Assert.Fail("Grants must be some")); + }, + _ => Assert.Fail("Offer must be valid")); + } + + [Fact] + public void Offer_With_Invalid_Structure_Is_Rejected() + { + var sample = PreAuth; + + sample["credential_issuer"] = "this is not a valid URI"; + sample["credential_configuration_ids"] = new JArray(); + + sample["grants"]!["urn:ietf:params:oauth:grant-type:pre-authorized_code"]!["pre-authorized_code"] = null; + + ValidCredentialOffer(sample).Match( + _ => Assert.Fail("Offer with invalid structure must be invalid"), + errors => + { + errors.Should().ContainSingle(error => error is CredentialIssuerError); + errors.Should().ContainSingle(error => error is EnumerableIsEmptyError); + } + ); + } +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs new file mode 100644 index 00000000..64b4a8e4 --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs @@ -0,0 +1,84 @@ +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Json; +using WalletFramework.Core.Uri; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.Mdoc; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.SdJwt; +using static WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models.IssuerMetadata; + +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Issuer; + +public class IssuerMetadataTests +{ + [Fact] + public void Can_Decode_From_Json() + { + // Arrange + var sample = IssuerMetadataSample.EncodedAsJson; + + // Act + ValidIssuerMetadata(sample).Match( + // Assert + sut => + { + new Uri(sut.CredentialIssuer.ToString()).Should().Be(IssuerMetadataSample.CredentialIssuer); + new Uri(sut.CredentialEndpoint.ToString()).Should().Be(IssuerMetadataSample.CredentialEndpoint); + + var mdocConfiguration = sut + .CredentialConfigurationsSupported[IssuerMetadataSample.MdocConfigurationId] + .AsT1; + + mdocConfiguration.Format.Should().Be(MdocConfigurationSample.Format); + mdocConfiguration.DocType.Should().Be(MdocConfigurationSample.DocType); + + var sdJwtConfiguration = sut + .CredentialConfigurationsSupported[IssuerMetadataSample.SdJwtConfigurationId] + .AsT0; + + sdJwtConfiguration.Format.Should().Be(SdJwtConfigurationSample.Format); + sdJwtConfiguration.Vct.Should().Be(SdJwtConfigurationSample.Vct); + }, + _ => Assert.Fail("IssuerMetadata must be valid")); + } + + [Fact] + public void Can_Encode_To_Json() + { + var decoded = IssuerMetadataSample.Decoded; + + var sut = JObject.FromObject(decoded).RemoveNulls(); + + sut.Should().BeEquivalentTo(IssuerMetadataSample.EncodedAsJson); + } + + [Fact] + public void Can_Decode_And_Encode_From_Json() + { + // Arrange + var sample = IssuerMetadataSample.EncodedAsJson; + + // Act + ValidIssuerMetadata(sample).Match( + // Assert + sut => + { + var encoded = JObject.FromObject(sut).RemoveNulls(); + encoded.Should().BeEquivalentTo(sample); + }, + _ => Assert.Fail("IssuerMetadata must be valid")); + } + + [Fact] + public void Can_Decode_From_Persisted_Json() + { + var sample = IssuerMetadataSample.EncodedAsJson; + + var sut = JsonConvert.DeserializeObject(sample.ToString())!; + + sut.CredentialIssuer.ToString().Should().Be(IssuerMetadataSample.CredentialIssuer.ToStringWithoutTrail()); + } +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/IssuerMetadataTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/IssuerMetadataTests.cs index 1cb8bdaa..22d08d58 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/IssuerMetadataTests.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/IssuerMetadataTests.cs @@ -1,54 +1,54 @@ -using FluentAssertions; -using Hyperledger.Aries.Extensions; -using Newtonsoft.Json.Linq; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; -using static WalletFramework.Oid4Vc.Tests.Samples; - - -namespace WalletFramework.Oid4Vc.Tests.Oid4Vci -{ - public class IssuerMetadataTests - { - [Fact] - public void Additional_Or_Unrecognized_Fields_Are_Ignored_During_Deserialization() - { - var json = IssuerMetadataJson; - var jObject = JObject.Parse(json); - jObject["Additional_Field"] = "Additional_Field"; - - var sut = jObject.ToObject(); - - sut.Should().BeOfType(); - sut!.CredentialIssuer.Should().Be(CredentialIssuer); - sut.CredentialEndpoint.Should().Be(CredentialEndpoint); - } - - [Theory] - [InlineData("credential_issuer")] - [InlineData("credential_endpoint")] - [InlineData("credential_configurations_supported")] - public void Deserialization_Fails_When_Required_Fields_Are_Missing(string fieldName) - { - // Arrange - var json = IssuerMetadataJson; - - var jObject = JObject.Parse(json); - - jObject[fieldName] = null; - - Assert.Throws(() => jObject.ToObject()); - } - - [Fact] - public void Valid_Json_Deserializes_To_Model() - { - var json = IssuerMetadataJson; - - var sut = json.ToObject(); - - sut.Should().BeOfType(); - sut.CredentialIssuer.Should().Be(CredentialIssuer); - sut.CredentialEndpoint.Should().Be(CredentialEndpoint); - } - } -} +// using FluentAssertions; +// using Hyperledger.Aries.Extensions; +// using Newtonsoft.Json.Linq; +// using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; +// using static WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.IssuerMetadataSample; +// +// +// namespace WalletFramework.Oid4Vc.Tests.Oid4Vci +// { +// public class IssuerMetadataTests +// { +// [Fact] +// public void Additional_Or_Unrecognized_Fields_Are_Ignored_During_Deserialization() +// { +// var json = IssuerMetadataJson; +// var jObject = JObject.Parse(json); +// jObject["Additional_Field"] = "Additional_Field"; +// +// var sut = jObject.ToObject(); +// +// sut.Should().BeOfType(); +// sut!.CredentialIssuer.Should().Be(CredentialIssuer); +// sut.CredentialEndpoint.Should().Be(CredentialEndpoint); +// } +// +// [Theory] +// [InlineData("credential_issuer")] +// [InlineData("credential_endpoint")] +// [InlineData("credential_configurations_supported")] +// public void Deserialization_Fails_When_Required_Fields_Are_Missing(string fieldName) +// { +// // Arrange +// var json = IssuerMetadataJson; +// +// var jObject = JObject.Parse(json); +// +// jObject[fieldName] = null; +// +// Assert.Throws(() => jObject.ToObject()); +// } +// +// [Fact] +// public void Valid_Json_Deserializes_To_Model() +// { +// var json = IssuerMetadataJson; +// +// var sut = json.ToObject(); +// +// sut.Should().BeOfType(); +// sut.CredentialIssuer.Should().Be(CredentialIssuer); +// sut.CredentialEndpoint.Should().Be(CredentialEndpoint); +// } +// } +// } diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Localization/LocaleTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Localization/LocaleTests.cs new file mode 100644 index 00000000..59c157f1 --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Localization/LocaleTests.cs @@ -0,0 +1,75 @@ +using FluentAssertions; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Localization; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Localization.Samples; +using static System.Collections.Immutable.ImmutableDictionary; + +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Localization; + +public class LocaleTests +{ + [Theory] + [InlineData("en-US", "en-US")] + [InlineData("en", "en-US")] + [InlineData("de-DE", "de-DE")] + [InlineData("de", "de-DE")] + public void Can_Create_From_Valid_Input(string input, string expected) => Locale.ValidLocale(input).Match( + sut => + { + sut.ToString().Should().Be(expected); + }, + _ => Assert.Fail("Locale is invalid")); + + [Theory] + [InlineData("Peter")] + [InlineData("")] + [InlineData("english")] + public void Invalid_Input_Is_Not_Allowed(string invalidInput) => Locale.ValidLocale(invalidInput).Match( + _ => Assert.Fail("Invalid input must not be able to create locale"), + _ => { }); + + [Fact] + public void Can_Find_Matching_Locale_In_A_Dictionary() + { + var english = LocaleSample.English; + var dictionary = CreateRange(new[] + { + KeyValuePair.Create(LocaleSample.German, 1), + KeyValuePair.Create(english, 0) + }); + + var sut = dictionary.FindOrDefault(english); + + sut.Should().Be(0); + } + + [Fact] + public void Get_English_As_Fallback_When_No_Matching_Locale_Is_Found_In_A_Dictionary() + { + var dictionary = CreateRange(new[] + { + KeyValuePair.Create(LocaleSample.German, 1), + KeyValuePair.Create(LocaleSample.English, 0) + } + ); + + var sut = dictionary.FindOrDefault(LocaleSample.Japanese); + + sut.Should().Be(0); + } + + [Fact] + public void Get_First_Entry_As_Fallback_When_No_Matching_Locale_And_No_English_Is_Found_In_A_Dictionary() + { + var dictionary = CreateRange(new[] + { + KeyValuePair.Create(LocaleSample.German, 1), + KeyValuePair.Create(LocaleSample.Japanese, 2) + } + ); + + var sut = dictionary.FindOrDefault(LocaleSample.Korean); + + sut.Should().BeGreaterOrEqualTo(1); + } +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Localization/Samples/LocaleSample.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Localization/Samples/LocaleSample.cs new file mode 100644 index 00000000..f5e2c642 --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Localization/Samples/LocaleSample.cs @@ -0,0 +1,15 @@ +using WalletFramework.Core.Functional; +using WalletFramework.Core.Localization; + +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Localization.Samples; + +public static class LocaleSample +{ + public static Locale English => Locale.ValidLocale("en-US").UnwrapOrThrow(new InvalidOperationException("The default locale is corrupt.")); + + public static Locale German => Locale.ValidLocale("de-DE").UnwrapOrThrow(new InvalidOperationException("German locale is corrupt.")); + + public static Locale Japanese => Locale.ValidLocale("ja-JP").UnwrapOrThrow(new InvalidOperationException("Japanese locale is corrupt.")); + + public static Locale Korean => Locale.ValidLocale("ko-KR").UnwrapOrThrow(new InvalidOperationException("Korean locale is corrupt.")); +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/CredentialOfferSample.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/CredentialOfferSample.cs new file mode 100644 index 00000000..6bdd38b2 --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/CredentialOfferSample.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json.Linq; + +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples; + +public static class CredentialOfferSample +{ + public const string CredentialIssuer = "https://credential-issuer.example.com"; + + public const string UniversityDegreeCredential = "UniversityDegreeCredential"; + + public const string OrgIso1801351Mdl = "org.iso.18013.5.1.mDL"; + + public const string PreAuthorizedCode = "oaKazRN8I0IbtZ0C7JuMn5"; + + public const int Length = 4; + + public const string Description = "Please provide the one-time code that was sent via e-mail"; + + public const string InputMode = "numeric"; + + public static JObject PreAuth => new() + { + ["credential_issuer"] = CredentialIssuer, + ["credential_configuration_ids"] = new JArray + { + UniversityDegreeCredential, + OrgIso1801351Mdl + }, + ["grants"] = new JObject + { + ["urn:ietf:params:oauth:grant-type:pre-authorized_code"] = new JObject + { + ["pre-authorized_code"] = PreAuthorizedCode, + ["tx_code"] = new JObject + { + ["length"] = Length, + ["input_mode"] = InputMode, + ["description"] = Description + } + } + } + }; +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/IssuerMetadataSample.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/IssuerMetadataSample.cs new file mode 100644 index 00000000..6c4ab17c --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/IssuerMetadataSample.cs @@ -0,0 +1,60 @@ +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Core.Uri; +using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; +using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.Mdoc; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.SdJwt; + +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples; + +public static class IssuerMetadataSample +{ + public static Uri CredentialEndpoint => new(CredentialIssuer + "/credential"); + + public static Uri CredentialIssuer => new("https://test-issuer.de"); + + public static CredentialConfigurationId MdocConfigurationId => CredentialConfigurationId + .ValidCredentialConfigurationId(MdocConfigurationSample.DocType.ToString()) + .UnwrapOrThrow(new InvalidOperationException()); + + public static CredentialConfigurationId SdJwtConfigurationId => CredentialConfigurationId + .ValidCredentialConfigurationId(SdJwtConfigurationSample.Scope.ToString()) + .UnwrapOrThrow(new InvalidOperationException()); + + public static JObject EncodedAsJson => new() + { + ["credential_issuer"] = CredentialIssuer.ToStringWithoutTrail(), + ["credential_endpoint"] = CredentialEndpoint.ToStringWithoutTrail(), + ["display"] = new JArray + { + new JObject + { + ["name"] = "Test Company GmbH", + ["logo"] = new JObject + { + { "uri", "https://test-issuer.com/logo.png" } + }, + ["locale"] = "en-US" + }, + new JObject + { + ["name"] = "Test Company GmbH", + ["logo"] = new JObject + { + { "uri", "https://test-issuer.com/logo.png" } + }, + ["locale"] = "de-DE" + } + }, + ["authorization_servers"] = new JArray { "https://test-issuer.com/authorizationserver" }, + ["credential_configurations_supported"] = new JObject + { + [SdJwtConfigurationId] = SdJwtConfigurationSample.Valid, + [MdocConfigurationId] = MdocConfigurationSample.Valid + } + }; + + public static IssuerMetadata Decoded => + IssuerMetadata.ValidIssuerMetadata(EncodedAsJson).UnwrapOrThrow(new InvalidOperationException()); +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/Mdoc/MdocConfigurationSample.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/Mdoc/MdocConfigurationSample.cs new file mode 100644 index 00000000..4036c1f7 --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/Mdoc/MdocConfigurationSample.cs @@ -0,0 +1,99 @@ +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.MdocLib; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Localization.Samples; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.CryptographicCurve; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.CryptographicSuite; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.ElementDisplay; +using static WalletFramework.MdocLib.ElementIdentifier; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.ElementName; +using static WalletFramework.MdocLib.DocType; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Format; +using static WalletFramework.MdocLib.NameSpace; + +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.Mdoc; + +public static class MdocConfigurationSample +{ + public const bool OneTimeUse = true; + + public const uint BatchSize = 10; + + public static CryptographicCurve CryptoCurve => + ValidCryptographicCurve(1).UnwrapOrThrow(new InvalidOperationException()); + + public static CryptographicSuite CryptoSuite => + ValidCryptographicSuite("ES256").UnwrapOrThrow(new InvalidOperationException()); + + public static DocType DocType => + ValidDoctype("org.iso.18013.5.1.mDL").UnwrapOrThrow(new InvalidOperationException()); + + public static ElementDisplay EnglishDisplay => OptionalElementDisplay(EnglishDisplayJson).ToNullable() ?? + throw new InvalidOperationException(); + + public static ElementDisplay JapaneseDisplay => OptionalElementDisplay(JapaneseDisplayJson).ToNullable() ?? + throw new InvalidOperationException(); + + public static ElementIdentifier GivenName => + ValidElementIdentifier("given_name").UnwrapOrThrow(new InvalidOperationException()); + + public static Format Format => ValidFormat("mso_mdoc").UnwrapOrThrow(new InvalidOperationException()); + + public static JObject Valid => new() + { + ["format"] = Format.ToString(), + ["doctype"] = DocType.ToString(), + ["policy"] = new JObject + { + ["one_time_use"] = OneTimeUse, + ["batch_size"] = BatchSize + }, + ["cryptographic_suites_supported"] = new JArray { CryptoSuite.ToString() }, + ["cryptographic_curves_supported"] = new JArray { CryptoCurve.ToString() }, + ["claims"] = new JObject + { + [NameSpace] = new JObject + { + [GivenName] = new JObject + { + ["display"] = new JArray + { + new JObject + { + ["name"] = EnglishName.ToString(), + ["locale"] = LocaleSample.English.ToString() + }, + new JObject + { + ["name"] = JapaneseName.ToString(), + ["locale"] = LocaleSample.Japanese.ToString() + } + } + } + } + } + }; + + public static NameSpace NameSpace => + ValidNameSpace("org.iso.18013.5.1").UnwrapOrThrow(new InvalidOperationException()); + + private static ElementName EnglishName => + OptionalElementName("Given Name").ToNullable() ?? throw new InvalidOperationException(); + + private static ElementName JapaneseName => + OptionalElementName("名前").ToNullable() ?? throw new InvalidOperationException(); + + private static JObject EnglishDisplayJson => new() + { + ["name"] = EnglishName.ToString(), + ["locale"] = LocaleSample.English.ToString() + }; + + private static JObject JapaneseDisplayJson => new() + { + ["name"] = JapaneseName.ToString(), + ["locale"] = LocaleSample.Japanese.ToString() + }; +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/SdJwt/SdJwtConfigurationSample.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/SdJwt/SdJwtConfigurationSample.cs new file mode 100644 index 00000000..c73b3b20 --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/SdJwt/SdJwtConfigurationSample.cs @@ -0,0 +1,95 @@ +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; +using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; +using WalletFramework.SdJwtVc.Models; + +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.SdJwt; + +public static class SdJwtConfigurationSample +{ + public static Format Format => Format + .ValidFormat("vc+sd-jwt") + .UnwrapOrThrow(new InvalidOperationException()); + + public static Vct Vct => Vct + .ValidVct("https://test-issuer.com/VerifiedEMail") + .UnwrapOrThrow(new InvalidOperationException()); + + public static Scope Scope => Scope + .OptionalScope("VerifiedEMailSdJwtVc") + .ToNullable() ?? throw new InvalidOperationException(); + + public static JObject Valid => new() + { + ["format"] = Format.ToString(), + ["scope"] = Scope.ToString(), + ["cryptographic_binding_methods_supported"] = new JArray { "jwk" }, + ["credential_signing_alg_values_supported"] = new JArray { "ES256" }, + ["display"] = new JArray + { + new JObject + { + ["name"] = "Verified e-mail adress", + ["logo"] = new JObject + { + ["uri"] = "https://test-issuer.com/credential-logo.png" + }, + ["background_color"] = "#12107c", + ["text_color"] = "#FFFFFF", + ["locale"] = "en-US" + } + }, + ["vct"] = Vct.ToString(), + ["claims"] = new JObject + { + ["given_name"] = new JObject + { + ["display"] = new JArray + { + new JObject + { + ["locale"] = "de-DE", + ["name"] = "Vorname" + }, + new JObject + { + ["locale"] = "en-US", + ["name"] = "Given name" + } + } + }, + ["family_name"] = new JObject + { + ["display"] = new JArray + { + new JObject + { + ["locale"] = "de-DE", + ["name"] = "Nachname" + }, + new JObject + { + ["locale"] = "en-US", + ["name"] = "Surname" + } + } + }, + ["email"] = new JObject + { + ["display"] = new JArray + { + new JObject + { + ["locale"] = "de-DE", + ["name"] = "E-Mail Adresse" + }, + new JObject + { + ["locale"] = "en-US", + ["name"] = "e-Mail address" + } + } + } + } + }; +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Services/Oid4VciClientServiceTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Services/Oid4VciClientServiceTests.cs deleted file mode 100644 index 225f7663..00000000 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Services/Oid4VciClientServiceTests.cs +++ /dev/null @@ -1,216 +0,0 @@ -using System.Net; -using FluentAssertions; -using Moq; -using Moq.Protected; -using Newtonsoft.Json; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Authorization; -using WalletFramework.Oid4Vc.Oid4Vci.Models.CredentialResponse; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential.Attributes; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; -using WalletFramework.Oid4Vc.Oid4Vci.Services; -using WalletFramework.Oid4Vc.Oid4Vci.Services.Oid4VciClientService; -using WalletFramework.SdJwtVc.KeyStore.Services; - -namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Services -{ - public class Oid4VciClientServiceTests - { - private const string AuthServerMetadataWithoutDpop = - "{\"issuer\":\"https://issuer.io\",\"token_endpoint\":\"https://issuer.io/token\",\"token_endpoint_auth_methods_supported\":[\"urn:ietf:params:oauth:client-assertion-type:verifiable-presentation\"],\"response_types_supported\":[\"urn:ietf:params:oauth:grant-type:pre-authorized_code\"]}\n"; - - private const string AuthServerMetadataWithDpop = - "{\"issuer\":\"https://issuer.io\",\"token_endpoint\":\"https://issuer.io/token\",\"token_endpoint_auth_methods_supported\":[\"urn:ietf:params:oauth:client-assertion-type:verifiable-presentation\"],\"response_types_supported\":[\"urn:ietf:params:oauth:grant-type:pre-authorized_code\"],\"dpop_signing_alg_values_supported\":[\"ES256\"]}\n"; - - private const string IssuerMetadata = - "{\"credential_configurations_supported\":{\"VerifiedEmail\":{\"vct\":\"VerifiedEmail\",\"claims\":{},\"format\":\"vc+sdjwt\"}},\"credential_endpoint\":\"https://issuer.io/credential\",\"credential_issuer\":\"https://issuer.io\"}"; - - private const string PreAuthorizedCode = "1234"; - - private const string TokenResponseWithoutDpopSupport = - "{\"access_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ\",\"token_type\":\"bearer\",\"expires_in\": 86400,\"c_nonce\": \"tZignsnFbp\",\"c_nonce_expires_in\":86400}"; - - private const string TokenResponseWithDpopSupport = - "{\"access_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp..sHQ\",\"token_type\":\"DPoP\",\"expires_in\": 86400,\"c_nonce\": \"tZignsnFbp\",\"c_nonce_expires_in\":86400}"; - - private const string DPopBadRequestTokenResponse = "{\"error\":\"use_dpop_nonce\"}"; - - private const string Vct = "VerifiedEmail"; - - private const string DPopNonce = "someRadnomNonceStringFromIssuer"; - - private const string CredentialResponse = "{\"format\":\"vc+sd-jwt\",\"credential\":\"eyJhbGciOiAiRVMyNTYifQ.eyJfc2QiOlsiT0dfT2lCMk5ZS0JzTVhIOFVVb2luREhUT1h5VER1Z3JPdE94RFI2NF9ZcyIsIlQzbHRYQUFtODNJTXRUYkRTb1J2d1g2Tk10em1scV9ZWG9Vd1EwZDY0NEUiXSwiaXNzIjoiaHR0cHM6Ly9pc3N1ZXIuaW8vIiwiaWF0IjoxNTE2MjM5MDIyLCJ0eXBlIjoiVmVyaWZpZWRFbWFpbCIsImV4cCI6MTUxNjI0NzAyMiwiX3NkX2FsZyI6InNoYS0yNTYiLCJjbmYiOnsiandrIjp7Imt0eSI6IkVDIiwiY3J2IjoiUC0yNTYiLCJ4IjoiVENBRVIxOVp2dTNPSEY0ajRXNHZmU1ZvSElQMUlMaWxEbHM3dkNlR2VtYyIsInkiOiJaeGppV1diWk1RR0hWV0tWUTRoYlNJaXJzVmZ1ZWNDRTZ0NGpUOUYySFpRIn19LCJhbGciOiJFUzI1NiJ9.OVSoCqHZLgAPaYK27gJx6J1ejwskP62xIHryqc1ZJYOR8yZdicSF4KXBk5qgocWZdiqEsri5Q3sW69xIfbmXSA~WyJseVMxN1ZzenNGb3doaFBnY3VuOTFRIiwgImV4cCIsIDE1NDE0OTQ3MjRd~WyJaRmNwSWxTNlJ5eWV2U3JTeFdJbDZRIiwgImdpdmVuX25hbWUiLCAiSm9obiJd~WyJVSHVVVUNlOWZzNUdody1mZ0JJWi13IiwgImZhbWlseV9uYW1lIiwgIkRvZSJd~WyJ3ZnR5YkpzYktzVWJDay1XaWpaQ3RRIiwgImVtYWlsIiwgInRlc3RAZXhhbXBsZS5jb20iXQ\"}"; - - private const string KeyBindingJwtKeyId = "someKbJwtKeyId"; - - private const string DPopJwtKeyId = "someDpopJwtKeyId"; - - private const string KbJwtMock = "someKeyBindingJwtMock"; - - private const string TransactionCode = "someTransactionCode"; - - private readonly HttpResponseMessage _credentialResponse = new HttpResponseMessage() - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(CredentialResponse) - }; - - private readonly HttpResponseMessage _tokenResponseWithoutDpopSupport = new HttpResponseMessage() - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(TokenResponseWithoutDpopSupport) - }; - - private readonly HttpResponseMessage _tokenResponseWithDpopSupport = new HttpResponseMessage() - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(TokenResponseWithDpopSupport) - }; - - private readonly HttpResponseMessage _dPopBadRequestTokenResponse = new HttpResponseMessage() - { - StatusCode = HttpStatusCode.BadRequest, - Content = new StringContent(DPopBadRequestTokenResponse), - Headers = {{"DPoP-Nonce", DPopNonce}}, - }; - - private readonly Mock _httpMessageHandlerMock = new Mock(); - private readonly Mock _httpClientFactoryMock = new Mock(); - private readonly Mock _authorizationRecordService = new Mock(); - private readonly Mock _keyStoreMock = new Mock(); - private Oid4VciClientService _oid4VciClientService; - - private readonly OidIssuerMetadata _issuerMetadata = new( - credentialConfigurationsSupported: new Dictionary() - { - { - "VerifiedEmail", new OidCredentialMetadata - { - Format = "vc+sdjwt", - Vct = Vct, - Claims = new Dictionary() - } - } - }, - display: null, - credentialIssuer: "https://issuer.io", - credentialEndpoint: "https://issuer.io/credential", - authorizationServer: null - ); - - private readonly AuthorizationServerMetadata _authorizationServerMetadataWithDpop = - new AuthorizationServerMetadata() - { - Issuer = "https://issuer.io", - TokenEndpoint = "https://issuer.io/token", - TokenEndpointAuthMethodsSupported = new string[] {"urn:ietf:params:oauth:client-assertion-type:verifiable-presentation"}, - ResponseTypesSupported = new string[] {"urn:ietf:params:oauth:grant-type:pre-authorized_code"}, - DPopSigningAlgValuesSupported = new string[] {"ES256"} - }; - - private readonly AuthorizationServerMetadata _authorizationServerMetadataWithoutDpop = - new AuthorizationServerMetadata() - { - Issuer = "https://issuer.io", - TokenEndpoint = "https://issuer.io/token", - TokenEndpointAuthMethodsSupported = new string[] {"urn:ietf:params:oauth:client-assertion-type:verifiable-presentation"}, - ResponseTypesSupported = new string[] {"urn:ietf:params:oauth:grant-type:pre-authorized_code"} - }; - - //TODO: Add tests for Authorization Code Flow when Storage is replaced (indy-sdk) - - [Fact] - public async Task CanRequestCredentialWithoutDPopInPreAuthFlowAsync() - { - //Arrange - SetupKeyStoreGenerateKeySequence(KeyBindingJwtKeyId); - _keyStoreMock.Setup(j => - j.GenerateKbProofOfPossessionAsync(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(),It.IsAny(), It.IsAny())) - .ReturnsAsync(KbJwtMock); - - SetupHttpClientSequence(_tokenResponseWithoutDpopSupport, _credentialResponse); - - var expectedCredentialResponse = JsonConvert.DeserializeObject(CredentialResponse); - - var metadataSet = new MetadataSet(_issuerMetadata, _authorizationServerMetadataWithoutDpop); - - // Act - var actualCredentialResponse = - await _oid4VciClientService.RequestCredentialAsync( - metadataSet, - _issuerMetadata.CredentialConfigurationsSupported.First().Value, - PreAuthorizedCode, - TransactionCode - ); - - //Assert - actualCredentialResponse[0].Item1.Should().BeEquivalentTo(expectedCredentialResponse); - actualCredentialResponse[0].Item2.Should().BeEquivalentTo(KeyBindingJwtKeyId); - } - - [Fact] - public async Task CanRequestCredentialWithDPoPInPreAuthFlowAsync() - { - //Arrange - const string dPopJwtMock = "someDPopJwtMock"; - - SetupKeyStoreGenerateKeySequence(DPopJwtKeyId, KeyBindingJwtKeyId); - _keyStoreMock.Setup(j => - j.GenerateKbProofOfPossessionAsync(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(),It.IsAny(), It.IsAny())) - .ReturnsAsync(KbJwtMock); - _keyStoreMock.Setup(j => - j.GenerateDPopProofOfPossessionAsync(It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny())) - .ReturnsAsync(dPopJwtMock); - - SetupHttpClientSequence( - _dPopBadRequestTokenResponse, - _tokenResponseWithDpopSupport, - _credentialResponse); - - var expectedCredentialResponse = JsonConvert.DeserializeObject(CredentialResponse); - - var metadataSet = new MetadataSet(_issuerMetadata, _authorizationServerMetadataWithDpop); - - //Act - var actualCredentialResponse = - await _oid4VciClientService.RequestCredentialAsync( - metadataSet, - _issuerMetadata.CredentialConfigurationsSupported.First().Value, - PreAuthorizedCode, - TransactionCode - ); - - //Assert - actualCredentialResponse[0].Item1.Should().BeEquivalentTo(expectedCredentialResponse); - actualCredentialResponse[0].Item2.Should().BeEquivalentTo(KeyBindingJwtKeyId); - } - - private void SetupHttpClientSequence(params HttpResponseMessage[] responses) - { - var responseQueue = new Queue(responses); - - _httpMessageHandlerMock.Protected() - .Setup>("SendAsync", ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(() => responseQueue.Dequeue()); - - var httpClient = new HttpClient(_httpMessageHandlerMock.Object); - _httpClientFactoryMock.Setup(f => f.CreateClient(It.IsAny())).Returns(httpClient); - - _oid4VciClientService = - new Oid4VciClientService(_httpClientFactoryMock.Object, _authorizationRecordService.Object, _keyStoreMock.Object); - } - - private void SetupKeyStoreGenerateKeySequence(params string[] responses) - { - var responseQueue = new Queue(responses); - - _keyStoreMock.Setup(j => j.GenerateKey(It.IsAny())) - .ReturnsAsync(() => responseQueue.Dequeue()); - } - } -} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vp/Services/Oid4VpClientServiceTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vp/Services/Oid4VpClientServiceTests.cs index 169da62a..b85b7549 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vp/Services/Oid4VpClientServiceTests.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vp/Services/Oid4VpClientServiceTests.cs @@ -1,188 +1,165 @@ using System.Net; +using FluentAssertions; using Hyperledger.Aries.Storage; using Hyperledger.TestHarness.Mock; using Microsoft.Extensions.Logging; using Moq; using Moq.Protected; using SD_JWT.Roles.Implementation; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential.Attributes; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; +using WalletFramework.Core.Cryptography.Abstractions; +using WalletFramework.Core.Cryptography.Models; +using WalletFramework.Oid4Vc.Oid4Vp.Models; using WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Services; using WalletFramework.Oid4Vc.Oid4Vp.Services; -using WalletFramework.SdJwtVc.KeyStore.Services; +using WalletFramework.SdJwtVc.Models.Records; using WalletFramework.SdJwtVc.Services.SdJwtVcHolderService; -namespace WalletFramework.Oid4Vc.Tests.Oid4Vp.Services +namespace WalletFramework.Oid4Vc.Tests.Oid4Vp.Services; + +public class Oid4VpClientServiceTests : IAsyncLifetime { - public class Oid4VpClientServiceTests : IAsyncLifetime - { - private const string AuthRequestWithRequestUri = - "haip://?client_id=https%3A%2F%2Fnc-sd-jwt.lambda.d3f.me%2Findex.php%2Fapps%2Fssi_login%2Foidc%2Fcallback&request_uri=https%3A%2F%2Fnc-sd-jwt.lambda.d3f.me%2Findex.php%2Fapps%2Fssi_login%2Foidc%2Frequestobject%2F4ba20ad0cb08545830aa549ab4305c03"; + private const string AuthRequestWithRequestUri = + "haip://?client_id=https%3A%2F%2Fnc-sd-jwt.lambda.d3f.me%2Findex.php%2Fapps%2Fssi_login%2Foidc%2Fcallback&request_uri=https%3A%2F%2Fnc-sd-jwt.lambda.d3f.me%2Findex.php%2Fapps%2Fssi_login%2Foidc%2Frequestobject%2F4ba20ad0cb08545830aa549ab4305c03"; - private const string CombinedIssuance = - "eyJ4NWMiOlsiTUlJQ09qQ0NBZUdnQXdJQkFnSUJBekFLQmdncWhrak9QUVFEQWpCak1Rc3dDUVlEVlFRR0V3SkVSVEVQTUEwR0ExVUVCd3dHUW1WeWJHbHVNUjB3R3dZRFZRUUtEQlJDZFc1a1pYTmtjblZqYTJWeVpXa2dSMjFpU0RFS01BZ0dBMVVFQ3d3QlNURVlNQllHQTFVRUF3d1BTVVIxYm1sdmJpQlVaWE4wSUVOQk1CNFhEVEl6TURjeE9ERXlOVE16TlZvWERUSTRNRGN4TmpFeU5UTXpOVm93V1RFTE1Ba0dBMVVFQmhNQ1JFVXhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SOHdIUVlEVlFRRERCWldaWEpwWm1sbFpDQkZMVTFoYVd3Z1NYTnpkV1Z5TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFOGoxOEsyZTRjZGRkdjRzaGRFUE84Z251MTJnM242RDFtRC9KU09TcEdDZlc5YUdoaU92bHpPck5icGRzTGVlWjVtclV3SXpra3BrMUhPVnZwSTNwVXFPQmp6Q0JqREFkQmdOVkhRNEVGZ1FVTFo4eWFCbDJJUVJWeCtrTGY4d3ZmRFpIY1pRd0RBWURWUjBUQVFIL0JBSXdBREFPQmdOVkhROEJBZjhFQkFNQ0I0QXdMQVlEVlIwUkJDVXdJNEloYVhOemRXVnlMVzl3Wlc1cFpEUjJZeTV6YzJrdWRHbHlMbUoxWkhKMUxtUmxNQjhHQTFVZEl3UVlNQmFBRkUrVzZ6N2FqVHVtZXgrWWNGYm9OclZlQzJ0Uk1Bb0dDQ3FHU000OUJBTUNBMGNBTUVRQ0lDZU5KYi85OENkV3RPdEtrREs0bm1WSGV4N0ZJclJQMlBRY3lmOVIzUGdPQWlCUHNkeENsakZXcTdxUGFOdUthUzhnTjRqZEkyVXUrNlNKaWZLZGp6SDdsQT09IiwiTUlJQ0xUQ0NBZFNnQXdJQkFnSVVNWVVIaEdEOWhVL2MwRW82bVc4cmpqZUordDB3Q2dZSUtvWkl6ajBFQXdJd1l6RUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWNNQmtKbGNteHBiakVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneENqQUlCZ05WQkFzTUFVa3hHREFXQmdOVkJBTU1EMGxFZFc1cGIyNGdWR1Z6ZENCRFFUQWVGdzB5TXpBM01UTXdPVEkxTWpoYUZ3MHpNekEzTVRBd09USTFNamhhTUdNeEN6QUpCZ05WQkFZVEFrUkZNUTh3RFFZRFZRUUhEQVpDWlhKc2FXNHhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SZ3dGZ1lEVlFRRERBOUpSSFZ1YVc5dUlGUmxjM1FnUTBFd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTRUh6OFlqckZ5VE5IR0x2TzE0RUF4bTl5aDhiS09na1V6WVdjQzFjdnJKbjVKZ0hZSE14WmJOTU8xM0VoMEVyMjczOFFRT2dlUm9aTUlUYW9ka2ZOU28yWXdaREFkQmdOVkhRNEVGZ1FVVDViclB0cU5PNlo3SDVod1Z1ZzJ0VjRMYTFFd0h3WURWUjBqQkJnd0ZvQVVUNWJyUHRxTk82WjdINWh3VnVnMnRWNExhMUV3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBT0JnTlZIUThCQWY4RUJBTUNBWVl3Q2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnWTBEZXJkQ3h0NHpHUFluOHlOckR4SVdDSkhwenE0QmRqZHNWTjJvMUdSVUNJQjBLQTdiRzFGVkIxSWlLOGQ1N1FBTCtQRzlYNWxkS0c3RWtvQW1oV1ZLZSJdLCJraWQiOiJNR3d3WjZSbE1HTXhDekFKQmdOVkJBWVRBa1JGTVE4d0RRWURWUVFIREFaQ1pYSnNhVzR4SFRBYkJnTlZCQW9NRkVKMWJtUmxjMlJ5ZFdOclpYSmxhU0JIYldKSU1Rb3dDQVlEVlFRTERBRkpNUmd3RmdZRFZRUUREQTlKUkhWdWFXOXVJRlJsYzNRZ1EwRUNBUU09IiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJfc2QiOlsibmJkd2Z0NE9QdmcycFFlaVFYOHdoc2hnZ0VVTTdBY29mdHRWUE95ejJpdyIsIkxoQjF2dE9WM1ZHd3V6QmhKWnhoUUd5OUNSY0l0dC1QSmkydDRvRk83X28iLCJZNEl2Uk4yY2VDU2V6aXZKRjREMHFDc0JQNW81eUZVdDJiXy1YRkFXTGZjIiwieU9kNkRJbGFDUERXTG9xLUJfY2JQWTY4dFZmV18wU25NRGQzeU5qRDIxRSJdLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLW9wZW5pZDR2Yy5zc2kudGlyLmJ1ZHJ1LmRlIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IjN1ZVRuN3VYLTZpSmxJYllOU2xrM0NSa3pwVDZGYldNMkxjdDZhdy1HZEEiLCJ5IjoiWlFlZnFJVnlzOG1MT05PZXBSclNsOWhXVGhGai1HTUVWR0pGUVk1TXQ2TSJ9fSwidHlwZSI6IlZlcmlmaWVkRU1haWwiLCJleHAiOjE2OTcyODIwOTgsImlhdCI6MTY5NjQxODA5OH0.Sbj1LaWpz45iqsdS8NFaLFgZ7G5hj1ofYLlO4rTI-jHELD6ORMGe1LVHe7IiOr_DNCDDde0ScGIEZKRiNCHEfA~WyJZTVVoZzh3Q2RHUGJjV245NW9MbUtBIiwiZW1haWwiLCJqb3RlbWVrMzYxQGZlc2dyaWQuY29tIl0"; + private const string CombinedIssuance = + "eyJ4NWMiOlsiTUlJQ09qQ0NBZUdnQXdJQkFnSUJBekFLQmdncWhrak9QUVFEQWpCak1Rc3dDUVlEVlFRR0V3SkVSVEVQTUEwR0ExVUVCd3dHUW1WeWJHbHVNUjB3R3dZRFZRUUtEQlJDZFc1a1pYTmtjblZqYTJWeVpXa2dSMjFpU0RFS01BZ0dBMVVFQ3d3QlNURVlNQllHQTFVRUF3d1BTVVIxYm1sdmJpQlVaWE4wSUVOQk1CNFhEVEl6TURjeE9ERXlOVE16TlZvWERUSTRNRGN4TmpFeU5UTXpOVm93V1RFTE1Ba0dBMVVFQmhNQ1JFVXhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SOHdIUVlEVlFRRERCWldaWEpwWm1sbFpDQkZMVTFoYVd3Z1NYTnpkV1Z5TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFOGoxOEsyZTRjZGRkdjRzaGRFUE84Z251MTJnM242RDFtRC9KU09TcEdDZlc5YUdoaU92bHpPck5icGRzTGVlWjVtclV3SXpra3BrMUhPVnZwSTNwVXFPQmp6Q0JqREFkQmdOVkhRNEVGZ1FVTFo4eWFCbDJJUVJWeCtrTGY4d3ZmRFpIY1pRd0RBWURWUjBUQVFIL0JBSXdBREFPQmdOVkhROEJBZjhFQkFNQ0I0QXdMQVlEVlIwUkJDVXdJNEloYVhOemRXVnlMVzl3Wlc1cFpEUjJZeTV6YzJrdWRHbHlMbUoxWkhKMUxtUmxNQjhHQTFVZEl3UVlNQmFBRkUrVzZ6N2FqVHVtZXgrWWNGYm9OclZlQzJ0Uk1Bb0dDQ3FHU000OUJBTUNBMGNBTUVRQ0lDZU5KYi85OENkV3RPdEtrREs0bm1WSGV4N0ZJclJQMlBRY3lmOVIzUGdPQWlCUHNkeENsakZXcTdxUGFOdUthUzhnTjRqZEkyVXUrNlNKaWZLZGp6SDdsQT09IiwiTUlJQ0xUQ0NBZFNnQXdJQkFnSVVNWVVIaEdEOWhVL2MwRW82bVc4cmpqZUordDB3Q2dZSUtvWkl6ajBFQXdJd1l6RUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWNNQmtKbGNteHBiakVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneENqQUlCZ05WQkFzTUFVa3hHREFXQmdOVkJBTU1EMGxFZFc1cGIyNGdWR1Z6ZENCRFFUQWVGdzB5TXpBM01UTXdPVEkxTWpoYUZ3MHpNekEzTVRBd09USTFNamhhTUdNeEN6QUpCZ05WQkFZVEFrUkZNUTh3RFFZRFZRUUhEQVpDWlhKc2FXNHhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SZ3dGZ1lEVlFRRERBOUpSSFZ1YVc5dUlGUmxjM1FnUTBFd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTRUh6OFlqckZ5VE5IR0x2TzE0RUF4bTl5aDhiS09na1V6WVdjQzFjdnJKbjVKZ0hZSE14WmJOTU8xM0VoMEVyMjczOFFRT2dlUm9aTUlUYW9ka2ZOU28yWXdaREFkQmdOVkhRNEVGZ1FVVDViclB0cU5PNlo3SDVod1Z1ZzJ0VjRMYTFFd0h3WURWUjBqQkJnd0ZvQVVUNWJyUHRxTk82WjdINWh3VnVnMnRWNExhMUV3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBT0JnTlZIUThCQWY4RUJBTUNBWVl3Q2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnWTBEZXJkQ3h0NHpHUFluOHlOckR4SVdDSkhwenE0QmRqZHNWTjJvMUdSVUNJQjBLQTdiRzFGVkIxSWlLOGQ1N1FBTCtQRzlYNWxkS0c3RWtvQW1oV1ZLZSJdLCJraWQiOiJNR3d3WjZSbE1HTXhDekFKQmdOVkJBWVRBa1JGTVE4d0RRWURWUVFIREFaQ1pYSnNhVzR4SFRBYkJnTlZCQW9NRkVKMWJtUmxjMlJ5ZFdOclpYSmxhU0JIYldKSU1Rb3dDQVlEVlFRTERBRkpNUmd3RmdZRFZRUUREQTlKUkhWdWFXOXVJRlJsYzNRZ1EwRUNBUU09IiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJfc2QiOlsibmJkd2Z0NE9QdmcycFFlaVFYOHdoc2hnZ0VVTTdBY29mdHRWUE95ejJpdyIsIkxoQjF2dE9WM1ZHd3V6QmhKWnhoUUd5OUNSY0l0dC1QSmkydDRvRk83X28iLCJZNEl2Uk4yY2VDU2V6aXZKRjREMHFDc0JQNW81eUZVdDJiXy1YRkFXTGZjIiwieU9kNkRJbGFDUERXTG9xLUJfY2JQWTY4dFZmV18wU25NRGQzeU5qRDIxRSJdLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLW9wZW5pZDR2Yy5zc2kudGlyLmJ1ZHJ1LmRlIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IjN1ZVRuN3VYLTZpSmxJYllOU2xrM0NSa3pwVDZGYldNMkxjdDZhdy1HZEEiLCJ5IjoiWlFlZnFJVnlzOG1MT05PZXBSclNsOWhXVGhGai1HTUVWR0pGUVk1TXQ2TSJ9fSwidHlwZSI6IlZlcmlmaWVkRU1haWwiLCJleHAiOjE2OTcyODIwOTgsImlhdCI6MTY5NjQxODA5OH0.Sbj1LaWpz45iqsdS8NFaLFgZ7G5hj1ofYLlO4rTI-jHELD6ORMGe1LVHe7IiOr_DNCDDde0ScGIEZKRiNCHEfA~WyJZTVVoZzh3Q2RHUGJjV245NW9MbUtBIiwiZW1haWwiLCJqb3RlbWVrMzYxQGZlc2dyaWQuY29tIl0"; - private const string ExpectedRedirectUrl = - "https://client.example.org/cb#response_code=091535f699ea575c7937fa5f0f454aee"; + private const string ExpectedRedirectUrl = + "https://client.example.org/cb#response_code=091535f699ea575c7937fa5f0f454aee"; - private const string KeyBindingJwtMock = - "eyJhbGciOiJFUzI1NiIsInR5cCI6ImtiK2p3dCJ9.eyJhdWQiOiJodHRwczovL3ZlcmlmaWVyLnNzaS50aXIuYnVkcnUuZGUvcHJlc2VudGF0aW9uL2F1dGhvcml6YXRpb24tcmVzcG9uc2UiLCJub25jZSI6IkxIc1lGRnlpMnNXQzZIM3ZSWVFsNFQiLCJpYXQiOjE2OTY0MjcwNzR9.Kxj1e7ucZeAnFnfOjo05QnW-DYeEprciDqkOhe6fhXIWprEYd1NJ6a0gpZJ66oTJsv49ExvDOKTLOzt6R75gcg"; + private const string KeyBindingJwtMock = + "eyJhbGciOiJFUzI1NiIsInR5cCI6ImtiK2p3dCJ9.eyJhdWQiOiJodHRwczovL3ZlcmlmaWVyLnNzaS50aXIuYnVkcnUuZGUvcHJlc2VudGF0aW9uL2F1dGhvcml6YXRpb24tcmVzcG9uc2UiLCJub25jZSI6IkxIc1lGRnlpMnNXQzZIM3ZSWVFsNFQiLCJpYXQiOjE2OTY0MjcwNzR9.Kxj1e7ucZeAnFnfOjo05QnW-DYeEprciDqkOhe6fhXIWprEYd1NJ6a0gpZJ66oTJsv49ExvDOKTLOzt6R75gcg"; - private const string KeyId = "KeyId"; + private const string KeyId = "KeyId"; - private const string RequestUriResponse = - "eyJ4NWMiOlsiTUlJQ0x6Q0NBZFdnQXdJQkFnSUJCREFLQmdncWhrak9QUVFEQWpCak1Rc3dDUVlEVlFRR0V3SkVSVEVQTUEwR0ExVUVCd3dHUW1WeWJHbHVNUjB3R3dZRFZRUUtEQlJDZFc1a1pYTmtjblZqYTJWeVpXa2dSMjFpU0RFS01BZ0dBMVVFQ3d3QlNURVlNQllHQTFVRUF3d1BTVVIxYm1sdmJpQlVaWE4wSUVOQk1CNFhEVEl6TURnd016QTROREkwTkZvWERUSTRNRGd3TVRBNE5ESTBORm93VlRFTE1Ba0dBMVVFQmhNQ1JFVXhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1Sc3dHUVlEVlFRRERCSlBjR1Z1U1dRMFZsQWdWbVZ5YVdacFpYSXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUnNoUzVDaVBrSzVXRUN1RHpybmN0SXBwYm1nc1lkOURzT1lEcElFeFpFczFmUWNOeXZrQjVFZU5Xc2MwU0ExUU5xd3dHVzRndUZLZzBJZjFKR0R4VWZvNEdITUlHRU1CMEdBMVVkRGdRV0JCUmZMQVBzeG1Mc3AxblEvRk12RkkzN0MzQmxZREFNQmdOVkhSTUJBZjhFQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBa0JnTlZIUkVFSFRBYmdobDJaWEpwWm1sbGNpNXpjMmt1ZEdseUxtSjFaSEoxTG1SbE1COEdBMVVkSXdRWU1CYUFGRStXNno3YWpUdW1leCtZY0Zib05yVmVDMnRSTUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSUNWZURUMnNkZHhySEMrZ0ZJTUVmc3huc0lXRmdIdnZlZnBuWXZrb0RjbHdBaUVBMlFnRVRHV3hIWUVObWxsNDA2VUNwYnFRb1kzMzJPbE9qdDUwWjc2WHBtQT0iLCJNSUlDTFRDQ0FkU2dBd0lCQWdJVU1ZVUhoR0Q5aFUvYzBFbzZtVzhyamplSit0MHdDZ1lJS29aSXpqMEVBd0l3WXpFTE1Ba0dBMVVFQmhNQ1JFVXhEekFOQmdOVkJBY01Ca0psY214cGJqRWRNQnNHQTFVRUNnd1VRblZ1WkdWelpISjFZMnRsY21WcElFZHRZa2d4Q2pBSUJnTlZCQXNNQVVreEdEQVdCZ05WQkFNTUQwbEVkVzVwYjI0Z1ZHVnpkQ0JEUVRBZUZ3MHlNekEzTVRNd09USTFNamhhRncwek16QTNNVEF3T1RJMU1qaGFNR014Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUW93Q0FZRFZRUUxEQUZKTVJnd0ZnWURWUVFEREE5SlJIVnVhVzl1SUZSbGMzUWdRMEV3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVNFSHo4WWpyRnlUTkhHTHZPMTRFQXhtOXloOGJLT2drVXpZV2NDMWN2ckpuNUpnSFlITXhaYk5NTzEzRWgwRXIyNzM4UVFPZ2VSb1pNSVRhb2RrZk5TbzJZd1pEQWRCZ05WSFE0RUZnUVVUNWJyUHRxTk82WjdINWh3VnVnMnRWNExhMUV3SHdZRFZSMGpCQmd3Rm9BVVQ1YnJQdHFOTzZaN0g1aHdWdWcydFY0TGExRXdFZ1lEVlIwVEFRSC9CQWd3QmdFQi93SUJBREFPQmdOVkhROEJBZjhFQkFNQ0FZWXdDZ1lJS29aSXpqMEVBd0lEUndBd1JBSWdZMERlcmRDeHQ0ekdQWW44eU5yRHhJV0NKSHB6cTRCZGpkc1ZOMm8xR1JVQ0lCMEtBN2JHMUZWQjFJaUs4ZDU3UUFMK1BHOVg1bGRLRzdFa29BbWhXVktlIl0sImtpZCI6Ik1Hd3daNlJsTUdNeEN6QUpCZ05WQkFZVEFrUkZNUTh3RFFZRFZRUUhEQVpDWlhKc2FXNHhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SZ3dGZ1lEVlFRRERBOUpSSFZ1YVc5dUlGUmxjM1FnUTBFQ0FRUT0iLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJyZXNwb25zZV90eXBlIjoidnBfdG9rZW4iLCJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJpZCI6IjE1ZDQwNjU0LWM2NTgtNDkzOC1hYzA3LWVjYjQxYzlhZmIxMCIsImlucHV0X2Rlc2NyaXB0b3JzIjpbeyJpZCI6IjUwYjZlNGYzLTYyMmEtNDk3NC1iMzMwLTVlNzIwZWM5MjJiZiIsImZvcm1hdCI6eyJ2YytzZC1qd3QiOnsicHJvb2ZfdHlwZSI6WyJKc29uV2ViU2lnbmF0dXJlMjAyMCJdfX0sImNvbnN0cmFpbnRzIjp7ImxpbWl0X2Rpc2Nsb3N1cmUiOiJyZXF1aXJlZCIsImZpZWxkcyI6W3sicGF0aCI6WyIkLnR5cGUiXSwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJjb25zdCI6IlZlcmlmaWVkRU1haWwifX0seyJwYXRoIjpbIiQuZW1haWwiXX1dfX1dfSwicmVzcG9uc2VfdXJpIjoiaHR0cHM6Ly92ZXJpZmllci5zc2kudGlyLmJ1ZHJ1LmRlL3ByZXNlbnRhdGlvbi9hdXRob3JpemF0aW9uLXJlc3BvbnNlIiwibm9uY2UiOiJZZjg4dGRlZzhZTTkyM3E0aFFBRzlPIiwiY2xpZW50X2lkIjoiaHR0cHM6Ly92ZXJpZmllci5zc2kudGlyLmJ1ZHJ1LmRlL3ByZXNlbnRhdGlvbi9hdXRob3JpemF0aW9uLXJlc3BvbnNlIiwicmVzcG9uc2VfbW9kZSI6ImRpcmVjdF9wb3N0In0.sdeLcG6Ta4ozfbDuHBr2Vq-Ro2WpdUIhJWy3BgazyvrgkQw27uTFGioPWXNCruK5H5E5nvHS420u5tv0671tjg"; + private const string RequestUriResponse = + "eyJ4NWMiOlsiTUlJQ0x6Q0NBZFdnQXdJQkFnSUJCREFLQmdncWhrak9QUVFEQWpCak1Rc3dDUVlEVlFRR0V3SkVSVEVQTUEwR0ExVUVCd3dHUW1WeWJHbHVNUjB3R3dZRFZRUUtEQlJDZFc1a1pYTmtjblZqYTJWeVpXa2dSMjFpU0RFS01BZ0dBMVVFQ3d3QlNURVlNQllHQTFVRUF3d1BTVVIxYm1sdmJpQlVaWE4wSUVOQk1CNFhEVEl6TURnd016QTROREkwTkZvWERUSTRNRGd3TVRBNE5ESTBORm93VlRFTE1Ba0dBMVVFQmhNQ1JFVXhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1Sc3dHUVlEVlFRRERCSlBjR1Z1U1dRMFZsQWdWbVZ5YVdacFpYSXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUnNoUzVDaVBrSzVXRUN1RHpybmN0SXBwYm1nc1lkOURzT1lEcElFeFpFczFmUWNOeXZrQjVFZU5Xc2MwU0ExUU5xd3dHVzRndUZLZzBJZjFKR0R4VWZvNEdITUlHRU1CMEdBMVVkRGdRV0JCUmZMQVBzeG1Mc3AxblEvRk12RkkzN0MzQmxZREFNQmdOVkhSTUJBZjhFQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBa0JnTlZIUkVFSFRBYmdobDJaWEpwWm1sbGNpNXpjMmt1ZEdseUxtSjFaSEoxTG1SbE1COEdBMVVkSXdRWU1CYUFGRStXNno3YWpUdW1leCtZY0Zib05yVmVDMnRSTUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSUNWZURUMnNkZHhySEMrZ0ZJTUVmc3huc0lXRmdIdnZlZnBuWXZrb0RjbHdBaUVBMlFnRVRHV3hIWUVObWxsNDA2VUNwYnFRb1kzMzJPbE9qdDUwWjc2WHBtQT0iLCJNSUlDTFRDQ0FkU2dBd0lCQWdJVU1ZVUhoR0Q5aFUvYzBFbzZtVzhyamplSit0MHdDZ1lJS29aSXpqMEVBd0l3WXpFTE1Ba0dBMVVFQmhNQ1JFVXhEekFOQmdOVkJBY01Ca0psY214cGJqRWRNQnNHQTFVRUNnd1VRblZ1WkdWelpISjFZMnRsY21WcElFZHRZa2d4Q2pBSUJnTlZCQXNNQVVreEdEQVdCZ05WQkFNTUQwbEVkVzVwYjI0Z1ZHVnpkQ0JEUVRBZUZ3MHlNekEzTVRNd09USTFNamhhRncwek16QTNNVEF3T1RJMU1qaGFNR014Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUW93Q0FZRFZRUUxEQUZKTVJnd0ZnWURWUVFEREE5SlJIVnVhVzl1SUZSbGMzUWdRMEV3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVNFSHo4WWpyRnlUTkhHTHZPMTRFQXhtOXloOGJLT2drVXpZV2NDMWN2ckpuNUpnSFlITXhaYk5NTzEzRWgwRXIyNzM4UVFPZ2VSb1pNSVRhb2RrZk5TbzJZd1pEQWRCZ05WSFE0RUZnUVVUNWJyUHRxTk82WjdINWh3VnVnMnRWNExhMUV3SHdZRFZSMGpCQmd3Rm9BVVQ1YnJQdHFOTzZaN0g1aHdWdWcydFY0TGExRXdFZ1lEVlIwVEFRSC9CQWd3QmdFQi93SUJBREFPQmdOVkhROEJBZjhFQkFNQ0FZWXdDZ1lJS29aSXpqMEVBd0lEUndBd1JBSWdZMERlcmRDeHQ0ekdQWW44eU5yRHhJV0NKSHB6cTRCZGpkc1ZOMm8xR1JVQ0lCMEtBN2JHMUZWQjFJaUs4ZDU3UUFMK1BHOVg1bGRLRzdFa29BbWhXVktlIl0sImtpZCI6Ik1Hd3daNlJsTUdNeEN6QUpCZ05WQkFZVEFrUkZNUTh3RFFZRFZRUUhEQVpDWlhKc2FXNHhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SZ3dGZ1lEVlFRRERBOUpSSFZ1YVc5dUlGUmxjM1FnUTBFQ0FRUT0iLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJyZXNwb25zZV90eXBlIjoidnBfdG9rZW4iLCJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJpZCI6IjE1ZDQwNjU0LWM2NTgtNDkzOC1hYzA3LWVjYjQxYzlhZmIxMCIsImlucHV0X2Rlc2NyaXB0b3JzIjpbeyJpZCI6IjUwYjZlNGYzLTYyMmEtNDk3NC1iMzMwLTVlNzIwZWM5MjJiZiIsImZvcm1hdCI6eyJ2YytzZC1qd3QiOnsicHJvb2ZfdHlwZSI6WyJKc29uV2ViU2lnbmF0dXJlMjAyMCJdfX0sImNvbnN0cmFpbnRzIjp7ImxpbWl0X2Rpc2Nsb3N1cmUiOiJyZXF1aXJlZCIsImZpZWxkcyI6W3sicGF0aCI6WyIkLnR5cGUiXSwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJjb25zdCI6IlZlcmlmaWVkRU1haWwifX0seyJwYXRoIjpbIiQuZW1haWwiXX1dfX1dfSwicmVzcG9uc2VfdXJpIjoiaHR0cHM6Ly92ZXJpZmllci5zc2kudGlyLmJ1ZHJ1LmRlL3ByZXNlbnRhdGlvbi9hdXRob3JpemF0aW9uLXJlc3BvbnNlIiwibm9uY2UiOiJZZjg4dGRlZzhZTTkyM3E0aFFBRzlPIiwiY2xpZW50X2lkIjoiaHR0cHM6Ly92ZXJpZmllci5zc2kudGlyLmJ1ZHJ1LmRlL3ByZXNlbnRhdGlvbi9hdXRob3JpemF0aW9uLXJlc3BvbnNlIiwicmVzcG9uc2VfbW9kZSI6ImRpcmVjdF9wb3N0In0.sdeLcG6Ta4ozfbDuHBr2Vq-Ro2WpdUIhJWy3BgazyvrgkQw27uTFGioPWXNCruK5H5E5nvHS420u5tv0671tjg"; - private const string Vct = "VerifiedEmail"; + private const string Vct = "VerifiedEmail"; - public Oid4VpClientServiceTests() - { - var holder = new Holder(); - var walletRecordService = new DefaultWalletRecordService(); - var pexService = new PexService(); - _sdJwtVcHolderService = new DefaultSdJwtVcHolderService(holder, _keyStoreMock.Object, walletRecordService); - _oid4VpHaipClient = new Oid4VpHaipClient(_httpClientFactoryMock.Object, pexService); - _oid4VpRecordService = new Oid4VpRecordService(walletRecordService); - - _oid4VpClientService = new Oid4VpClientService( - _httpClientFactoryMock.Object, - _sdJwtVcHolderService, - pexService, - _oid4VpHaipClient, - _loggerMock.Object, - _oid4VpRecordService - ); + public Oid4VpClientServiceTests() + { + var holder = new Holder(); + var walletRecordService = new DefaultWalletRecordService(); + var pexService = new PexService(); + + _sdJwtVcHolderService = new SdJwtVcHolderService(holder, _keyStoreMock.Object, walletRecordService); + _oid4VpHaipClient = new Oid4VpHaipClient(_httpClientFactoryMock.Object, pexService); + _oid4VpRecordService = new Oid4VpRecordService(walletRecordService); + + _oid4VpClientService = new Oid4VpClientService( + _httpClientFactoryMock.Object, + _sdJwtVcHolderService, + pexService, + _oid4VpHaipClient, + _loggerMock.Object, + _oid4VpRecordService + ); - _keyStoreMock.Setup(keyStore => - keyStore.GenerateKbProofOfPossessionAsync( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny() - ) + _keyStoreMock.Setup(keyStore => + keyStore.GenerateKbProofOfPossessionAsync( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny() ) - .ReturnsAsync(KeyBindingJwtMock); - } - - private readonly DefaultSdJwtVcHolderService _sdJwtVcHolderService; - - private readonly Mock _httpMessageHandlerMock = new Mock(); - private readonly Mock _httpClientFactoryMock = new Mock(); - private readonly Mock> _loggerMock = new Mock>(); - private readonly Mock _keyStoreMock = new Mock(); - private MockAgent? _agent1; - private readonly MockAgentRouter _router = new MockAgentRouter(); - - private readonly Oid4VpClientService _oid4VpClientService; - private readonly Oid4VpHaipClient _oid4VpHaipClient; - private readonly Oid4VpRecordService _oid4VpRecordService; - - private readonly OidIssuerMetadata _oidIssuerMetadata = new( - credentialConfigurationsSupported: new Dictionary() - { - { - "VerifiedEmail", new OidCredentialMetadata - { - Format = "vc+sdjwt", - Vct = Vct, - Claims = new Dictionary() - } - } - }, - display: null, - credentialEndpoint: "https://issuer.io/credential", - credentialIssuer: "https://issuer.io", - authorizationServer: null - ); + ) + .ReturnsAsync(KeyBindingJwtMock); + } - private readonly WalletConfiguration _config1 = new WalletConfiguration() { Id = Guid.NewGuid().ToString() }; - private readonly WalletCredentials _cred = new WalletCredentials() { Key = "2" }; - - // Todo: Fix this test - // [Fact] - // public async Task CanExecuteOpenId4VpFlow() - // { - // //Arrange - // SetupHttpClient(RequestUriResponse); - // - // await _sdJwtVcHolderService.StoreAsync( - // _agent1.Context, - // CombinedIssuance, - // KeyId, - // _oidIssuerMetadata, - // "VerifiedEmail" - // ); - // - // //Act - // var (authorizationRequest, credentials) = - // await _oid4VpClientService.ProcessAuthorizationRequestAsync( - // _agent1.Context, - // new Uri(AuthRequestWithRequestUri) - // ); - // - // var selectedCandidates = new SelectedCredential - // { - // InputDescriptorId = credentials.First().InputDescriptorId, - // Credential = credentials.First().Credentials.First() - // }; - // - // SetupHttpClient( - // "{'redirect_uri':'https://client.example.org/cb#response_code=091535f699ea575c7937fa5f0f454aee'}" - // ); - // - // var response = await _oid4VpClientService.SendAuthorizationResponseAsync( - // _agent1.Context, - // authorizationRequest, - // new[] { selectedCandidates } - // ); - // - // // Assert - // credentials.Length.Should().Be(1); - // - // response.Should().BeEquivalentTo(new Uri(ExpectedRedirectUrl)); - // - // (await _oid4VpRecordService.ListAsync(_agent1.Context)).Count.Should().Be(1); - // } - - public async Task DisposeAsync() - { - await _agent1.Dispose(); - } + private readonly SdJwtVcHolderService _sdJwtVcHolderService; + + private readonly Mock _httpMessageHandlerMock = new(); + private readonly Mock _httpClientFactoryMock = new(); + private readonly Mock> _loggerMock = new(); + private readonly Mock _keyStoreMock = new(); + private readonly MockAgentRouter _router = new(); + private MockAgent? _agent1; + + private readonly Oid4VpClientService _oid4VpClientService; + private readonly Oid4VpHaipClient _oid4VpHaipClient; + private readonly Oid4VpRecordService _oid4VpRecordService; + private readonly WalletConfiguration _config1 = new() { Id = Guid.NewGuid().ToString() }; + private readonly WalletCredentials _cred = new() { Key = "2" }; + + [Fact] + public async Task CanExecuteOpenId4VpFlow() + { + //Arrange + SetupHttpClient(RequestUriResponse); - public async Task InitializeAsync() + var sdJwt = new SdJwtRecord(); + + await _sdJwtVcHolderService.SaveAsync(_agent1.Context, sdJwt); + + //Act + var (authorizationRequest, credentials) = + await _oid4VpClientService.ProcessAuthorizationRequestAsync( + _agent1.Context, + new Uri(AuthRequestWithRequestUri) + ); + + var selectedCandidates = new SelectedCredential { - _agent1 = - await MockUtils.CreateAsync( - "agent1", - _config1, - _cred, - new MockAgentHttpHandler( - cb => _router.RouteMessage(cb.name, cb.data) - ) - ); + InputDescriptorId = credentials.First().InputDescriptorId, + Credential = credentials.First().Credentials.First() + }; + + SetupHttpClient( + "{'redirect_uri':'https://client.example.org/cb#response_code=091535f699ea575c7937fa5f0f454aee'}" + ); + + var response = await _oid4VpClientService.SendAuthorizationResponseAsync( + _agent1.Context, + authorizationRequest, + new[] { selectedCandidates } + ); + + // Assert + credentials.Length.Should().Be(1); + + response.Should().BeEquivalentTo(new Uri(ExpectedRedirectUrl)); - _router.RegisterAgent(_agent1); - } + (await _oid4VpRecordService.ListAsync(_agent1.Context)).Count.Should().Be(1); + } + + public async Task DisposeAsync() + { + await _agent1.Dispose(); + } - private void SetupHttpClient(string response) + public async Task InitializeAsync() + { + _agent1 = + await MockUtils.CreateAsync( + "agent1", + _config1, + _cred, + new MockAgentHttpHandler( + cb => _router.RouteMessage(cb.name, cb.data) + ) + ); + + _router.RegisterAgent(_agent1); + } + + private void SetupHttpClient(string response) + { + var httpResponseMessage = new HttpResponseMessage { - var httpResponseMessage = new HttpResponseMessage - { - StatusCode = HttpStatusCode.OK, - Content = new StringContent(response) - }; - - _httpMessageHandlerMock.Protected() - .Setup>("SendAsync", ItExpr.IsAny(), - ItExpr.IsAny()) - .ReturnsAsync(() => httpResponseMessage); - - var httpClient = new HttpClient(_httpMessageHandlerMock.Object); - _httpClientFactoryMock.Setup(f => f.CreateClient(It.IsAny())).Returns(httpClient); - } + StatusCode = HttpStatusCode.OK, + Content = new StringContent(response) + }; + + _httpMessageHandlerMock.Protected() + .Setup>("SendAsync", ItExpr.IsAny(), + ItExpr.IsAny()) + .ReturnsAsync(() => httpResponseMessage); + + var httpClient = new HttpClient(_httpMessageHandlerMock.Object); + _httpClientFactoryMock.Setup(f => f.CreateClient(It.IsAny())).Returns(httpClient); } } diff --git a/test/WalletFramework.Oid4Vc.Tests/PresentationExchange/Services/PexServiceTests.cs b/test/WalletFramework.Oid4Vc.Tests/PresentationExchange/Services/PexServiceTests.cs index 4a99affe..66978c9b 100644 --- a/test/WalletFramework.Oid4Vc.Tests/PresentationExchange/Services/PexServiceTests.cs +++ b/test/WalletFramework.Oid4Vc.Tests/PresentationExchange/Services/PexServiceTests.cs @@ -1,13 +1,14 @@ using FluentAssertions; using Hyperledger.Aries.Storage.Models.Interfaces; -using Hyperledger.Aries.Tests.Extensions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SD_JWT.Roles.Implementation; +using WalletFramework.Core.Cryptography.Models; using WalletFramework.Oid4Vc.Oid4Vp.Exceptions; using WalletFramework.Oid4Vc.Oid4Vp.Models; using WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models; using WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Services; +using WalletFramework.Oid4Vc.Tests.Extensions; using WalletFramework.Oid4Vc.Tests.PresentationExchange.Models; using WalletFramework.SdJwtVc.Models.Credential; using WalletFramework.SdJwtVc.Models.Credential.Attributes; @@ -17,7 +18,7 @@ namespace WalletFramework.Oid4Vc.Tests.PresentationExchange.Services { public class PexServiceTests { - private readonly PexService _pexService = new PexService(); + private readonly PexService _pexService = new(); [Fact] public async Task Can_Create_Presentation_Submission() @@ -58,6 +59,7 @@ public async Task Can_Create_Presentation_Submission() [Fact] public async Task Can_Get_Credential_Candidates_For_Input_Descriptors() { + // Arrange var driverLicenseCredential = CreateCredential(CredentialExamples.DriverCredential); var driverLicenseCredentialClone = CreateCredential(CredentialExamples.DriverCredential); var universityCredential = CreateCredential(CredentialExamples.UniversityCredential); @@ -84,17 +86,19 @@ public async Task Can_Get_Credential_Candidates_For_Input_Descriptors() var expected = new List { - new CredentialCandidates( - driverLicenseInputDescriptor.Id, - new List { driverLicenseCredential, driverLicenseCredentialClone }), - new CredentialCandidates( - universityInputDescriptor.Id, new List { universityCredential }) + new(driverLicenseInputDescriptor.Id, + new List + { + driverLicenseCredential, + driverLicenseCredentialClone + }), + new(universityInputDescriptor.Id, new List { universityCredential }) }; - var sdJwtVcHolderService = CreatePexService(); + var pexService = CreatePexService(); // Act - var credentialCandidatesArray = await sdJwtVcHolderService.FindCredentialCandidates( + var credentialCandidatesArray = await pexService.FindCredentialCandidates( new[] { driverLicenseCredential, driverLicenseCredentialClone, universityCredential @@ -267,13 +271,18 @@ private static IPexService CreatePexService() private static SdJwtRecord CreateCredential(JObject payload) { + // Arrange const string jwk = "{\"kty\":\"EC\",\"d\":\"1_2Dagk1gvTIOX-DLPe7GHNsGLJMc7biySNA-so7TXE\",\"use\":\"sig\",\"crv\":\"P-256\",\"x\":\"X6sZhH_kFp_oKYiPXW-LvUyAv9mHp1xYcpAK3yy0wGY\",\"y\":\"p0URU7tgWbh42miznti0NVKM36fpJBbIfnF8ZCYGryE\",\"alg\":\"ES256\"}"; - var issuedSdJwt = new Issuer().IssueCredential(payload, jwk); + var keyId = KeyId.CreateKeyId(); + + var record = new SdJwtRecord( + issuedSdJwt.IssuanceFormat, + new Dictionary(), + new List(), + new Dictionary(), + keyId); - var record = new SdJwtRecord(issuedSdJwt.IssuanceFormat, new Dictionary(), - new List(), new Dictionary(), "0"); - return record; } diff --git a/test/WalletFramework.Oid4Vc.Tests/Samples.cs b/test/WalletFramework.Oid4Vc.Tests/Samples.cs deleted file mode 100644 index 2e3351ec..00000000 --- a/test/WalletFramework.Oid4Vc.Tests/Samples.cs +++ /dev/null @@ -1,166 +0,0 @@ -using Newtonsoft.Json.Linq; - -namespace WalletFramework.Oid4Vc.Tests -{ - public static class Samples - { - public const string CredentialEndpoint = CredentialIssuer + "/credential"; - public const string CredentialIssuer = "https://test-issuer.de"; - - public static string AuthorizationRequestJson => - new JObject - { - ["response_uri"] = "https://lissi-test-verifier.de/authorization-response", - ["client_id_scheme"] = "x509_san_dns", - ["response_type"] = "vp_token", - ["presentation_definition"] = new JObject - { - ["id"] = "e325164b-5699-4deb-b3ee-e7b2d75e5034", - ["input_descriptors"] = new JArray - { - new JObject - { - ["id"] = "64863101-5049-407f-97e6-f6eb2deed16d", - ["format"] = new JObject - { - ["vc+sd-jwt"] = new JObject() - }, - ["constraints"] = new JObject - { - ["fields"] = new JArray - { - new JObject - { - ["path"] = new JArray { "$.vct" }, - ["filter"] = new JObject - { - ["type"] = "string", - ["const"] = "https://lissi-test.de/VerifiedEMail" - } - }, - new JObject - { - ["path"] = new JArray { "$.email" } - } - }, - ["limit_disclosure"] = "required" - } - } - } - }, - ["state"] = "e325164b-5699-4deb-b3ee-e7b2d75e5034", - ["nonce"] = "kkxicbKxPSViBh97jqSB6r", - ["client_id"] = "lissi-test-verifier.de", - ["response_mode"] = "direct_post" - } - .ToString(); - - public static string IssuerMetadataJson => - new JObject - { - ["credential_issuer"] = CredentialIssuer, - ["credential_endpoint"] = CredentialEndpoint, - ["display"] = new JArray - { - new JObject - { - ["name"] = "Test Company GmbH", - ["logo"] = new JObject - { - {"uri", "https://test-issuer.com/logo.png"} - }, - ["locale"] = "en-US" - }, - new JObject - { - ["name"] = "Test Company GmbH", - ["logo"] = new JObject - { - {"uri", "https://test-issuer.com/logo.png"} - }, - ["locale"] = "de-DE" - } - }, - ["credential_configurations_supported"] = new JObject - { - ["VerifiedEMailSdJwtVc"] = new JObject - { - ["format"] = "vc+sd-jwt", - ["scope"] = "VerifiedEMailSdJwtVc", - ["cryptographic_binding_methods_supported"] = new JArray { "jwk" }, - ["cryptographic_suites_supported"] = new JArray { "ES256" }, - ["display"] = new JArray - { - new JObject - { - ["name"] = "Verified e-mail adress", - ["logo"] = new JObject - { - ["url"] = "https:/test-issuer.com/credential-logo.png" - }, - ["background_color"] = "#12107c", - ["text_color"] = "#FFFFFF", - ["locale"] = "en-US" - } - }, - ["credential_definition"] = new JObject - { - ["vct"] = "https://test-issuer.com/VerifiedEMail", - ["claims"] = new JObject - { - ["given_name"] = new JObject - { - ["display"] = new JArray - { - new JObject - { - ["locale"] = "de-DE", - ["name"] = "Vorname" - }, - new JObject - { - ["locale"] = "en-US", - ["name"] = "Given name" - } - } - }, - ["family_name"] = new JObject - { - ["display"] = new JArray - { - new JObject - { - ["locale"] = "de-DE", - ["name"] = "Nachname" - }, - new JObject - { - ["locale"] = "en-US", - ["name"] = "Surname" - } - } - }, - ["email"] = new JObject - { - ["display"] = new JArray - { - new JObject - { - ["locale"] = "de-DE", - ["name"] = "E-Mail Adresse" - }, - new JObject - { - ["locale"] = "en-US", - ["name"] = "e-Mail address" - } - } - } - } - } - } - } - } - .ToString(); - } -} diff --git a/test/WalletFramework.Oid4Vc.Tests/WalletFramework.Oid4Vc.Tests.csproj b/test/WalletFramework.Oid4Vc.Tests/WalletFramework.Oid4Vc.Tests.csproj index 522cbd71..a0c376ea 100644 --- a/test/WalletFramework.Oid4Vc.Tests/WalletFramework.Oid4Vc.Tests.csproj +++ b/test/WalletFramework.Oid4Vc.Tests/WalletFramework.Oid4Vc.Tests.csproj @@ -10,7 +10,7 @@ - + @@ -37,4 +37,9 @@ + + + + + diff --git a/test/WalletFramework.SdJwtVc.Tests/SdJwtRecordTests.cs b/test/WalletFramework.SdJwtVc.Tests/SdJwtRecordTests.cs index c7bc6392..0c0dbe43 100644 --- a/test/WalletFramework.SdJwtVc.Tests/SdJwtRecordTests.cs +++ b/test/WalletFramework.SdJwtVc.Tests/SdJwtRecordTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using WalletFramework.Core.Cryptography.Models; using WalletFramework.SdJwtVc.Models.Credential; using WalletFramework.SdJwtVc.Models.Credential.Attributes; using WalletFramework.SdJwtVc.Models.Records; @@ -10,8 +11,15 @@ public class SdJwtRecordTests [Fact] public void CanCreateSdJwtRecord() { - string encodedSdJwt = "eyJraWQiOiJiZmFmYjkzMy1iNzQ4LTQ3ODYtODc1Ny0zYzg0ZWFlNmUzZGUiLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJfc2QiOlsiUVBEUFFCbEEzdk9QaU9qR0lRRXBOc1l5S2Zjd2M1T3dDUlV5eWY2QTlRbyIsIk9LMWJpZXUwR0RIZWVRc2lzRkxOcUdmX0Z4eW5HT0dTNHl5Q2dZeFVhTkEiLCJUSkJ4ajBGSmdTQlUxMzVDSDRacFJieTRfVG4tNWR4TFJBX0paRnNscXhjIiwiaFBjV0phVkRJdDlDZ1E3bWxzNmFSVFR6bHZ0NmlMYzlUWFRJZ2VuZDFWayIsIkNhZm9TdzRiMWdsV196ckdyN3lodFFyQ3RIYW51NG15MVBxTGtXQkx5aFkiXSwibmJmIjoxNzA2NTQyNjgxLCJ2Y3QiOiJJRC1DYXJkIiwiX3NkX2FsZyI6InNoYS0yNTYiLCJpc3MiOiJodHRwczovL2U4MGMtMjE3LTExMS0xMDgtMTc0Lm5ncm9rLWZyZWUuYXBwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6Img2VUtiVXQ1SW4yTzVwUzUxYXRWaERuTDl0SGR4S3lkMTZXTG94R2dFQzQiLCJ5IjoiMDdIX05RcmlxRmxSb0JjVk5ZVW5aS2wwQ1A0U0NiN3RxU0NWWFNDTWh0ayJ9fSwiZXhwIjoxNzM4MjUxNDgxLCJpYXQiOjE3MTY5OTA0MDAsInN0YXR1cyI6eyJpZHgiOjYsInVyaSI6Imh0dHBzOi8vZTgwYy0yMTctMTExLTEwOC0xNzQubmdyb2stZnJlZS5hcHAvc3RhdHVzLWxpc3RzP3JlZ2lzdHJ5SWQ9YmQ1MDllMzYtNTQzNy00Zjg4LTkzYTUtNDEzNDA3ZjZiZDhmIn19.-3GEPOjEn4bopEGyy8ho_kFSfQVmkkZiFKMebtiZE6EsyRnunJtA46M_SwHQjmSm-73zIeRX7L7Rpszm8dkFhQ~WyJfSU1WWFVtc052bm9YTDR3NVRPSFpnIiwiYWRkcmVzcyIseyJzdHJlZXRfYWRkcmVzcyI6IjQyIE1hcmtldCBTdHJlZXQiLCJwb3N0YWxfY29kZSI6IjEyMzQ1In1d~WyJ3RzkzbExRRFBDUVgxTUtCYW5mVkVRIiwibGFzdF9uYW1lIiwiRG9lIl0~WyJFa1h0a0JHZXd2dkthRXlzTWhyVGJnIiwibmF0aW9uYWxpdGllcyIsWyJCcml0aXNoIiwiQmV0ZWxnZXVzaWFuIl1d~WyJzQlh2dVQxRHhaN0NrMTdJUXQzWWd3IiwiZmlyc3RfbmFtZSIsIkpvaG4iXQ~WyJoRWphWTA2WmFsNUZTS0pXSm9kUjZnIiwiZGVncmVlcyIsW3sidW5pdmVyc2l0eSI6IlVuaXZlcnNpdHkgb2YgQmV0ZWxnZXVzZSIsInR5cGUiOiJCYWNoZWxvciBvZiBTY2llbmNlIn0seyJ1bml2ZXJzaXR5IjoiVW5pdmVyc2l0eSBvZiBCZXRlbGdldXNlIiwidHlwZSI6Ik1hc3RlciBvZiBTY2llbmNlIn1dXQ~"; - var record = new SdJwtRecord(encodedSdJwt, new Dictionary(), new List(), new Dictionary(), "0"); + const string encodedSdJwt = "eyJraWQiOiJiZmFmYjkzMy1iNzQ4LTQ3ODYtODc1Ny0zYzg0ZWFlNmUzZGUiLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJfc2QiOlsiUVBEUFFCbEEzdk9QaU9qR0lRRXBOc1l5S2Zjd2M1T3dDUlV5eWY2QTlRbyIsIk9LMWJpZXUwR0RIZWVRc2lzRkxOcUdmX0Z4eW5HT0dTNHl5Q2dZeFVhTkEiLCJUSkJ4ajBGSmdTQlUxMzVDSDRacFJieTRfVG4tNWR4TFJBX0paRnNscXhjIiwiaFBjV0phVkRJdDlDZ1E3bWxzNmFSVFR6bHZ0NmlMYzlUWFRJZ2VuZDFWayIsIkNhZm9TdzRiMWdsV196ckdyN3lodFFyQ3RIYW51NG15MVBxTGtXQkx5aFkiXSwibmJmIjoxNzA2NTQyNjgxLCJ2Y3QiOiJJRC1DYXJkIiwiX3NkX2FsZyI6InNoYS0yNTYiLCJpc3MiOiJodHRwczovL2U4MGMtMjE3LTExMS0xMDgtMTc0Lm5ncm9rLWZyZWUuYXBwIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6Img2VUtiVXQ1SW4yTzVwUzUxYXRWaERuTDl0SGR4S3lkMTZXTG94R2dFQzQiLCJ5IjoiMDdIX05RcmlxRmxSb0JjVk5ZVW5aS2wwQ1A0U0NiN3RxU0NWWFNDTWh0ayJ9fSwiZXhwIjoxNzM4MjUxNDgxLCJpYXQiOjE3MTY5OTA0MDAsInN0YXR1cyI6eyJpZHgiOjYsInVyaSI6Imh0dHBzOi8vZTgwYy0yMTctMTExLTEwOC0xNzQubmdyb2stZnJlZS5hcHAvc3RhdHVzLWxpc3RzP3JlZ2lzdHJ5SWQ9YmQ1MDllMzYtNTQzNy00Zjg4LTkzYTUtNDEzNDA3ZjZiZDhmIn19.-3GEPOjEn4bopEGyy8ho_kFSfQVmkkZiFKMebtiZE6EsyRnunJtA46M_SwHQjmSm-73zIeRX7L7Rpszm8dkFhQ~WyJfSU1WWFVtc052bm9YTDR3NVRPSFpnIiwiYWRkcmVzcyIseyJzdHJlZXRfYWRkcmVzcyI6IjQyIE1hcmtldCBTdHJlZXQiLCJwb3N0YWxfY29kZSI6IjEyMzQ1In1d~WyJ3RzkzbExRRFBDUVgxTUtCYW5mVkVRIiwibGFzdF9uYW1lIiwiRG9lIl0~WyJFa1h0a0JHZXd2dkthRXlzTWhyVGJnIiwibmF0aW9uYWxpdGllcyIsWyJCcml0aXNoIiwiQmV0ZWxnZXVzaWFuIl1d~WyJzQlh2dVQxRHhaN0NrMTdJUXQzWWd3IiwiZmlyc3RfbmFtZSIsIkpvaG4iXQ~WyJoRWphWTA2WmFsNUZTS0pXSm9kUjZnIiwiZGVncmVlcyIsW3sidW5pdmVyc2l0eSI6IlVuaXZlcnNpdHkgb2YgQmV0ZWxnZXVzZSIsInR5cGUiOiJCYWNoZWxvciBvZiBTY2llbmNlIn0seyJ1bml2ZXJzaXR5IjoiVW5pdmVyc2l0eSBvZiBCZXRlbGdldXNlIiwidHlwZSI6Ik1hc3RlciBvZiBTY2llbmNlIn1dXQ~"; + var keyId = KeyId.CreateKeyId(); + + var record = new SdJwtRecord( + encodedSdJwt, + new Dictionary(), + new List(), + new Dictionary(), + keyId); record.Claims.Count.Should().Be(10); } diff --git a/test/WalletFramework.SdJwtVc.Tests/SdJwtVcHolderServiceTests.cs b/test/WalletFramework.SdJwtVc.Tests/SdJwtVcHolderServiceTests.cs index 62ab1340..abf3b7e2 100644 --- a/test/WalletFramework.SdJwtVc.Tests/SdJwtVcHolderServiceTests.cs +++ b/test/WalletFramework.SdJwtVc.Tests/SdJwtVcHolderServiceTests.cs @@ -3,7 +3,8 @@ using SD_JWT.Models; using SD_JWT.Roles; using SD_JWT.Roles.Implementation; -using WalletFramework.SdJwtVc.KeyStore.Services; +using WalletFramework.Core.Cryptography.Abstractions; +using WalletFramework.Core.Cryptography.Models; using WalletFramework.SdJwtVc.Models.Credential; using WalletFramework.SdJwtVc.Models.Credential.Attributes; using WalletFramework.SdJwtVc.Models.Records; @@ -13,7 +14,7 @@ namespace WalletFramework.SdJwtVc.Tests; public class SdJwtVcHolderServiceTests { - private readonly DefaultSdJwtVcHolderService _service; + private readonly SdJwtVcHolderService _service; public SdJwtVcHolderServiceTests() { @@ -21,7 +22,7 @@ public SdJwtVcHolderServiceTests() IHolder holder = new Holder(); IKeyStore keyStoreMock = new Mock().Object; IWalletRecordService walletRecordServiceMock = new Mock().Object; - _service = new DefaultSdJwtVcHolderService(holder, keyStoreMock, walletRecordServiceMock); + _service = new SdJwtVcHolderService(holder, keyStoreMock, walletRecordServiceMock); } // https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-08.html#appendix-A.3-4 @@ -30,7 +31,8 @@ public async Task Can_Create_Presentation_For_Example_4A() { const string issuedSdJwt = "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogInZjK3NkLWp3dCJ9.eyJfc2QiOiBbIjBIWm1uU0lQejMzN2tTV2U3QzM0bC0tODhnekppLWVCSjJWel9ISndBVGciLCAiOVpicGxDN1RkRVc3cWFsNkJCWmxNdHFKZG1lRU9pWGV2ZEpsb1hWSmRSUSIsICJJMDBmY0ZVb0RYQ3VjcDV5eTJ1anFQc3NEVkdhV05pVWxpTnpfYXdEMGdjIiwgIklFQllTSkdOaFhJbHJRbzU4eWtYbTJaeDN5bGw5WmxUdFRvUG8xN1FRaVkiLCAiTGFpNklVNmQ3R1FhZ1hSN0F2R1RyblhnU2xkM3o4RUlnX2Z2M2ZPWjFXZyIsICJodkRYaHdtR2NKUXNCQ0EyT3RqdUxBY3dBTXBEc2FVMG5rb3ZjS09xV05FIiwgImlrdXVyOFE0azhxM1ZjeUE3ZEMtbU5qWkJrUmVEVFUtQ0c0bmlURTdPVFUiLCAicXZ6TkxqMnZoOW80U0VYT2ZNaVlEdXZUeWtkc1dDTmcwd1RkbHIwQUVJTSIsICJ3elcxNWJoQ2t2a3N4VnZ1SjhSRjN4aThpNjRsbjFqb183NkJDMm9hMXVnIiwgInpPZUJYaHh2SVM0WnptUWNMbHhLdUVBT0dHQnlqT3FhMXoySW9WeF9ZRFEiXSwgImlzcyI6ICJodHRwczovL2lzc3Vlci5leGFtcGxlLmNvbSIsICJpYXQiOiAxNjgzMDAwMDAwLCAiZXhwIjogMTg4MzAwMDAwMCwgInZjdCI6ICJodHRwczovL2JtaS5idW5kLmV4YW1wbGUvY3JlZGVudGlhbC9waWQvMS4wIiwgImFnZV9lcXVhbF9vcl9vdmVyIjogeyJfc2QiOiBbIkZjOElfMDdMT2NnUHdyREpLUXlJR085N3dWc09wbE1Makh2UkM0UjQtV2ciLCAiWEx0TGphZFVXYzl6Tl85aE1KUm9xeTQ2VXNDS2IxSXNoWnV1cVVGS1NDQSIsICJhb0NDenNDN3A0cWhaSUFoX2lkUkNTQ2E2NDF1eWNuYzh6UGZOV3o4bngwIiwgImYxLVAwQTJkS1dhdnYxdUZuTVgyQTctRVh4dmhveHY1YUhodUVJTi1XNjQiLCAiazVoeTJyMDE4dnJzSmpvLVZqZDZnNnl0N0Fhb25Lb25uaXVKOXplbDNqbyIsICJxcDdaX0t5MVlpcDBzWWdETzN6VnVnMk1GdVBOakh4a3NCRG5KWjRhSS1jIl19LCAiX3NkX2FsZyI6ICJzaGEtMjU2IiwgImNuZiI6IHsiandrIjogeyJrdHkiOiAiRUMiLCAiY3J2IjogIlAtMjU2IiwgIngiOiAiVENBRVIxOVp2dTNPSEY0ajRXNHZmU1ZvSElQMUlMaWxEbHM3dkNlR2VtYyIsICJ5IjogIlp4amlXV2JaTVFHSFZXS1ZRNGhiU0lpcnNWZnVlY0NFNnQ0alQ5RjJIWlEifX19.jeF9GjGbjCr0NND0SbkV4HeSpsysixALFScJl4bYkIykXhF6cRtqni64_d7X6Ef8Rx80rfsgXe0H7TdiSoIJOw~WyIyR0xDNDJzS1F2ZUNmR2ZyeU5STjl3IiwgImdpdmVuX25hbWUiLCAiRXJpa2EiXQ~WyJlbHVWNU9nM2dTTklJOEVZbnN4QV9BIiwgImZhbWlseV9uYW1lIiwgIk11c3Rlcm1hbm4iXQ~WyI2SWo3dE0tYTVpVlBHYm9TNXRtdlZBIiwgImJpcnRoZGF0ZSIsICIxOTYzLTA4LTEyIl0~WyJlSThaV205UW5LUHBOUGVOZW5IZGhRIiwgInNvdXJjZV9kb2N1bWVudF90eXBlIiwgImlkX2NhcmQiXQ~WyJRZ19PNjR6cUF4ZTQxMmExMDhpcm9BIiwgInN0cmVldF9hZGRyZXNzIiwgIkhlaWRlc3RyYVx1MDBkZmUgMTciXQ~WyJBSngtMDk1VlBycFR0TjRRTU9xUk9BIiwgImxvY2FsaXR5IiwgIktcdTAwZjZsbiJd~WyJQYzMzSk0yTGNoY1VfbEhnZ3ZfdWZRIiwgInBvc3RhbF9jb2RlIiwgIjUxMTQ3Il0~WyJHMDJOU3JRZmpGWFE3SW8wOXN5YWpBIiwgImNvdW50cnkiLCAiREUiXQ~WyJsa2x4RjVqTVlsR1RQVW92TU5JdkNBIiwgImFkZHJlc3MiLCB7Il9zZCI6IFsiWEZjN3pYUG03enpWZE15d20yRXVCZmxrYTVISHF2ZjhVcF9zek5HcXZpZyIsICJiZDFFVnpnTm9wVWs0RVczX2VRMm4zX05VNGl1WE9IdjlYYkdITjNnMVRFIiwgImZfRlFZZ3ZRV3Z5VnFObklYc0FSbE55ZTdZR3A4RTc3Z1JBamFxLXd2bnciLCAidjRra2JfcFAxamx2VWJTanR5YzVicWNXeUEtaThYTHZoVllZN1pUMHRiMCJdfV0~WyJuUHVvUW5rUkZxM0JJZUFtN0FuWEZBIiwgIm5hdGlvbmFsaXRpZXMiLCBbIkRFIl1d~WyI1YlBzMUlxdVpOYTBoa2FGenp6Wk53IiwgImdlbmRlciIsICJmZW1hbGUiXQ~WyI1YTJXMF9OcmxFWnpmcW1rXzdQcS13IiwgImJpcnRoX2ZhbWlseV9uYW1lIiwgIkdhYmxlciJd~WyJ5MXNWVTV3ZGZKYWhWZGd3UGdTN1JRIiwgImxvY2FsaXR5IiwgIkJlcmxpbiJd~WyJIYlE0WDhzclZXM1FEeG5JSmRxeU9BIiwgInBsYWNlX29mX2JpcnRoIiwgeyJfc2QiOiBbIldwaEhvSUR5b1diQXBEQzR6YnV3UjQweGwweExoRENfY3Y0dHNTNzFyRUEiXSwgImNvdW50cnkiOiAiREUifV0~WyJDOUdTb3VqdmlKcXVFZ1lmb2pDYjFBIiwgImFsc29fa25vd25fYXMiLCAiU2Nod2VzdGVyIEFnbmVzIl0~WyJreDVrRjE3Vi14MEptd1V4OXZndnR3IiwgIjEyIiwgdHJ1ZV0~WyJIM28xdXN3UDc2MEZpMnllR2RWQ0VRIiwgIjE0IiwgdHJ1ZV0~WyJPQktsVFZsdkxnLUFkd3FZR2JQOFpBIiwgIjE2IiwgdHJ1ZV0~WyJNMEpiNTd0NDF1YnJrU3V5ckRUM3hBIiwgIjE4IiwgdHJ1ZV0~WyJEc210S05ncFY0ZEFIcGpyY2Fvc0F3IiwgIjIxIiwgdHJ1ZV0~WyJlSzVvNXBIZmd1cFBwbHRqMXFoQUp3IiwgIjY1IiwgZmFsc2Vd~"; // Arrange - var sdJwtRecord = new SdJwtRecord(issuedSdJwt, new Dictionary(), new List(), new Dictionary(), "0"); + var keyId = KeyId.CreateKeyId(); + var sdJwtRecord = new SdJwtRecord(issuedSdJwt, new Dictionary(), new List(), new Dictionary(), keyId); var claimsToDisclose = new[] { "given_name", "address.street_address", "nationalities[0]" }; // Act diff --git a/test/WalletFramework.SdJwtVc.Tests/WalletFramework.SdJwtVc.Tests.csproj b/test/WalletFramework.SdJwtVc.Tests/WalletFramework.SdJwtVc.Tests.csproj index 13ba623b..abea3547 100644 --- a/test/WalletFramework.SdJwtVc.Tests/WalletFramework.SdJwtVc.Tests.csproj +++ b/test/WalletFramework.SdJwtVc.Tests/WalletFramework.SdJwtVc.Tests.csproj @@ -10,7 +10,7 @@ - + From ee4bcf3a552821dd3c71b3f098e0cfe815ece0d6 Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 17 Jul 2024 09:20:03 +0200 Subject: [PATCH 2/5] separate integration tests Signed-off-by: Kevin --- .../WalletFramework.Oid4Vc.csproj | 3 ++ src/WalletFramework.sln | 7 +++++ .../Oid4Vp}/Oid4VpClientServiceTests.cs | 28 ++++++----------- .../Usings.cs | 1 + .../WalletFramework.Integration.Tests.csproj | 30 +++++++++++++++++++ .../WalletFramework.MdocLib.Tests.csproj | 7 +++-- .../WalletFramework.MdocVc.Tests.csproj | 2 +- .../Oid4Vci/Issuer/IssuerMetadataTests.cs | 6 ++-- .../WalletFramework.Oid4Vc.Tests.csproj | 6 ++-- .../WalletFramework.SdJwtVc.Tests.csproj | 6 ++-- 10 files changed, 65 insertions(+), 31 deletions(-) rename test/{WalletFramework.Oid4Vc.Tests/Oid4Vp/Services => WalletFramework.Integration.Tests/WalletFramework.Integration.Tests/Oid4Vp}/Oid4VpClientServiceTests.cs (71%) create mode 100644 test/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests/Usings.cs create mode 100644 test/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests.csproj diff --git a/src/WalletFramework.Oid4Vc/WalletFramework.Oid4Vc.csproj b/src/WalletFramework.Oid4Vc/WalletFramework.Oid4Vc.csproj index 842e193b..ecc08872 100644 --- a/src/WalletFramework.Oid4Vc/WalletFramework.Oid4Vc.csproj +++ b/src/WalletFramework.Oid4Vc/WalletFramework.Oid4Vc.csproj @@ -10,6 +10,9 @@ <_Parameter1>WalletFramework.Oid4Vc.Tests + + <_Parameter1>WalletFramework.Integration.Tests + diff --git a/src/WalletFramework.sln b/src/WalletFramework.sln index 56766f62..42a3c01b 100644 --- a/src/WalletFramework.sln +++ b/src/WalletFramework.sln @@ -51,6 +51,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{873772C5-6 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mdoc", "Mdoc", "{A1DD69B3-DC35-43CF-AE14-D751722F074A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WalletFramework.Integration.Tests", "..\test\WalletFramework.Integration.Tests\WalletFramework.Integration.Tests\WalletFramework.Integration.Tests.csproj", "{70DB749B-255A-4B71-8B76-BAD6B091DA7C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -129,6 +131,10 @@ Global {F6B3A24B-CDA2-4CC1-9F68-380203355099}.Debug|Any CPU.Build.0 = Debug|Any CPU {F6B3A24B-CDA2-4CC1-9F68-380203355099}.Release|Any CPU.ActiveCfg = Release|Any CPU {F6B3A24B-CDA2-4CC1-9F68-380203355099}.Release|Any CPU.Build.0 = Release|Any CPU + {70DB749B-255A-4B71-8B76-BAD6B091DA7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70DB749B-255A-4B71-8B76-BAD6B091DA7C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70DB749B-255A-4B71-8B76-BAD6B091DA7C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70DB749B-255A-4B71-8B76-BAD6B091DA7C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -153,6 +159,7 @@ Global {A7DC0511-DE0A-4EC2-8CC1-197FB81D7A76} = {02ADBA96-A50C-44F0-A9D9-FA0629AA2DF4} {F6B3A24B-CDA2-4CC1-9F68-380203355099} = {873772C5-60B9-442B-B06E-C279919B963C} {0EDD27CB-967F-4451-81AE-309E7F534F1C} = {A1DD69B3-DC35-43CF-AE14-D751722F074A} + {70DB749B-255A-4B71-8B76-BAD6B091DA7C} = {02ADBA96-A50C-44F0-A9D9-FA0629AA2DF4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4FFA80F9-ADC6-40DB-BBD1-A522B8A68560} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vp/Services/Oid4VpClientServiceTests.cs b/test/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests/Oid4Vp/Oid4VpClientServiceTests.cs similarity index 71% rename from test/WalletFramework.Oid4Vc.Tests/Oid4Vp/Services/Oid4VpClientServiceTests.cs rename to test/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests/Oid4Vp/Oid4VpClientServiceTests.cs index b85b7549..f2154033 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vp/Services/Oid4VpClientServiceTests.cs +++ b/test/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests/Oid4Vp/Oid4VpClientServiceTests.cs @@ -14,30 +14,22 @@ using WalletFramework.SdJwtVc.Models.Records; using WalletFramework.SdJwtVc.Services.SdJwtVcHolderService; -namespace WalletFramework.Oid4Vc.Tests.Oid4Vp.Services; +namespace WalletFramework.Integration.Tests.Oid4Vp; public class Oid4VpClientServiceTests : IAsyncLifetime { private const string AuthRequestWithRequestUri = "haip://?client_id=https%3A%2F%2Fnc-sd-jwt.lambda.d3f.me%2Findex.php%2Fapps%2Fssi_login%2Foidc%2Fcallback&request_uri=https%3A%2F%2Fnc-sd-jwt.lambda.d3f.me%2Findex.php%2Fapps%2Fssi_login%2Foidc%2Frequestobject%2F4ba20ad0cb08545830aa549ab4305c03"; - private const string CombinedIssuance = - "eyJ4NWMiOlsiTUlJQ09qQ0NBZUdnQXdJQkFnSUJBekFLQmdncWhrak9QUVFEQWpCak1Rc3dDUVlEVlFRR0V3SkVSVEVQTUEwR0ExVUVCd3dHUW1WeWJHbHVNUjB3R3dZRFZRUUtEQlJDZFc1a1pYTmtjblZqYTJWeVpXa2dSMjFpU0RFS01BZ0dBMVVFQ3d3QlNURVlNQllHQTFVRUF3d1BTVVIxYm1sdmJpQlVaWE4wSUVOQk1CNFhEVEl6TURjeE9ERXlOVE16TlZvWERUSTRNRGN4TmpFeU5UTXpOVm93V1RFTE1Ba0dBMVVFQmhNQ1JFVXhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SOHdIUVlEVlFRRERCWldaWEpwWm1sbFpDQkZMVTFoYVd3Z1NYTnpkV1Z5TUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFOGoxOEsyZTRjZGRkdjRzaGRFUE84Z251MTJnM242RDFtRC9KU09TcEdDZlc5YUdoaU92bHpPck5icGRzTGVlWjVtclV3SXpra3BrMUhPVnZwSTNwVXFPQmp6Q0JqREFkQmdOVkhRNEVGZ1FVTFo4eWFCbDJJUVJWeCtrTGY4d3ZmRFpIY1pRd0RBWURWUjBUQVFIL0JBSXdBREFPQmdOVkhROEJBZjhFQkFNQ0I0QXdMQVlEVlIwUkJDVXdJNEloYVhOemRXVnlMVzl3Wlc1cFpEUjJZeTV6YzJrdWRHbHlMbUoxWkhKMUxtUmxNQjhHQTFVZEl3UVlNQmFBRkUrVzZ6N2FqVHVtZXgrWWNGYm9OclZlQzJ0Uk1Bb0dDQ3FHU000OUJBTUNBMGNBTUVRQ0lDZU5KYi85OENkV3RPdEtrREs0bm1WSGV4N0ZJclJQMlBRY3lmOVIzUGdPQWlCUHNkeENsakZXcTdxUGFOdUthUzhnTjRqZEkyVXUrNlNKaWZLZGp6SDdsQT09IiwiTUlJQ0xUQ0NBZFNnQXdJQkFnSVVNWVVIaEdEOWhVL2MwRW82bVc4cmpqZUordDB3Q2dZSUtvWkl6ajBFQXdJd1l6RUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWNNQmtKbGNteHBiakVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneENqQUlCZ05WQkFzTUFVa3hHREFXQmdOVkJBTU1EMGxFZFc1cGIyNGdWR1Z6ZENCRFFUQWVGdzB5TXpBM01UTXdPVEkxTWpoYUZ3MHpNekEzTVRBd09USTFNamhhTUdNeEN6QUpCZ05WQkFZVEFrUkZNUTh3RFFZRFZRUUhEQVpDWlhKc2FXNHhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SZ3dGZ1lEVlFRRERBOUpSSFZ1YVc5dUlGUmxjM1FnUTBFd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTRUh6OFlqckZ5VE5IR0x2TzE0RUF4bTl5aDhiS09na1V6WVdjQzFjdnJKbjVKZ0hZSE14WmJOTU8xM0VoMEVyMjczOFFRT2dlUm9aTUlUYW9ka2ZOU28yWXdaREFkQmdOVkhRNEVGZ1FVVDViclB0cU5PNlo3SDVod1Z1ZzJ0VjRMYTFFd0h3WURWUjBqQkJnd0ZvQVVUNWJyUHRxTk82WjdINWh3VnVnMnRWNExhMUV3RWdZRFZSMFRBUUgvQkFnd0JnRUIvd0lCQURBT0JnTlZIUThCQWY4RUJBTUNBWVl3Q2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnWTBEZXJkQ3h0NHpHUFluOHlOckR4SVdDSkhwenE0QmRqZHNWTjJvMUdSVUNJQjBLQTdiRzFGVkIxSWlLOGQ1N1FBTCtQRzlYNWxkS0c3RWtvQW1oV1ZLZSJdLCJraWQiOiJNR3d3WjZSbE1HTXhDekFKQmdOVkJBWVRBa1JGTVE4d0RRWURWUVFIREFaQ1pYSnNhVzR4SFRBYkJnTlZCQW9NRkVKMWJtUmxjMlJ5ZFdOclpYSmxhU0JIYldKSU1Rb3dDQVlEVlFRTERBRkpNUmd3RmdZRFZRUUREQTlKUkhWdWFXOXVJRlJsYzNRZ1EwRUNBUU09IiwidHlwIjoidmMrc2Qtand0IiwiYWxnIjoiRVMyNTYifQ.eyJfc2QiOlsibmJkd2Z0NE9QdmcycFFlaVFYOHdoc2hnZ0VVTTdBY29mdHRWUE95ejJpdyIsIkxoQjF2dE9WM1ZHd3V6QmhKWnhoUUd5OUNSY0l0dC1QSmkydDRvRk83X28iLCJZNEl2Uk4yY2VDU2V6aXZKRjREMHFDc0JQNW81eUZVdDJiXy1YRkFXTGZjIiwieU9kNkRJbGFDUERXTG9xLUJfY2JQWTY4dFZmV18wU25NRGQzeU5qRDIxRSJdLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6Imh0dHBzOi8vaXNzdWVyLW9wZW5pZDR2Yy5zc2kudGlyLmJ1ZHJ1LmRlIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IjN1ZVRuN3VYLTZpSmxJYllOU2xrM0NSa3pwVDZGYldNMkxjdDZhdy1HZEEiLCJ5IjoiWlFlZnFJVnlzOG1MT05PZXBSclNsOWhXVGhGai1HTUVWR0pGUVk1TXQ2TSJ9fSwidHlwZSI6IlZlcmlmaWVkRU1haWwiLCJleHAiOjE2OTcyODIwOTgsImlhdCI6MTY5NjQxODA5OH0.Sbj1LaWpz45iqsdS8NFaLFgZ7G5hj1ofYLlO4rTI-jHELD6ORMGe1LVHe7IiOr_DNCDDde0ScGIEZKRiNCHEfA~WyJZTVVoZzh3Q2RHUGJjV245NW9MbUtBIiwiZW1haWwiLCJqb3RlbWVrMzYxQGZlc2dyaWQuY29tIl0"; - private const string ExpectedRedirectUrl = "https://client.example.org/cb#response_code=091535f699ea575c7937fa5f0f454aee"; private const string KeyBindingJwtMock = "eyJhbGciOiJFUzI1NiIsInR5cCI6ImtiK2p3dCJ9.eyJhdWQiOiJodHRwczovL3ZlcmlmaWVyLnNzaS50aXIuYnVkcnUuZGUvcHJlc2VudGF0aW9uL2F1dGhvcml6YXRpb24tcmVzcG9uc2UiLCJub25jZSI6IkxIc1lGRnlpMnNXQzZIM3ZSWVFsNFQiLCJpYXQiOjE2OTY0MjcwNzR9.Kxj1e7ucZeAnFnfOjo05QnW-DYeEprciDqkOhe6fhXIWprEYd1NJ6a0gpZJ66oTJsv49ExvDOKTLOzt6R75gcg"; - private const string KeyId = "KeyId"; - private const string RequestUriResponse = "eyJ4NWMiOlsiTUlJQ0x6Q0NBZFdnQXdJQkFnSUJCREFLQmdncWhrak9QUVFEQWpCak1Rc3dDUVlEVlFRR0V3SkVSVEVQTUEwR0ExVUVCd3dHUW1WeWJHbHVNUjB3R3dZRFZRUUtEQlJDZFc1a1pYTmtjblZqYTJWeVpXa2dSMjFpU0RFS01BZ0dBMVVFQ3d3QlNURVlNQllHQTFVRUF3d1BTVVIxYm1sdmJpQlVaWE4wSUVOQk1CNFhEVEl6TURnd016QTROREkwTkZvWERUSTRNRGd3TVRBNE5ESTBORm93VlRFTE1Ba0dBMVVFQmhNQ1JFVXhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1Sc3dHUVlEVlFRRERCSlBjR1Z1U1dRMFZsQWdWbVZ5YVdacFpYSXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBUnNoUzVDaVBrSzVXRUN1RHpybmN0SXBwYm1nc1lkOURzT1lEcElFeFpFczFmUWNOeXZrQjVFZU5Xc2MwU0ExUU5xd3dHVzRndUZLZzBJZjFKR0R4VWZvNEdITUlHRU1CMEdBMVVkRGdRV0JCUmZMQVBzeG1Mc3AxblEvRk12RkkzN0MzQmxZREFNQmdOVkhSTUJBZjhFQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBa0JnTlZIUkVFSFRBYmdobDJaWEpwWm1sbGNpNXpjMmt1ZEdseUxtSjFaSEoxTG1SbE1COEdBMVVkSXdRWU1CYUFGRStXNno3YWpUdW1leCtZY0Zib05yVmVDMnRSTUFvR0NDcUdTTTQ5QkFNQ0EwZ0FNRVVDSUNWZURUMnNkZHhySEMrZ0ZJTUVmc3huc0lXRmdIdnZlZnBuWXZrb0RjbHdBaUVBMlFnRVRHV3hIWUVObWxsNDA2VUNwYnFRb1kzMzJPbE9qdDUwWjc2WHBtQT0iLCJNSUlDTFRDQ0FkU2dBd0lCQWdJVU1ZVUhoR0Q5aFUvYzBFbzZtVzhyamplSit0MHdDZ1lJS29aSXpqMEVBd0l3WXpFTE1Ba0dBMVVFQmhNQ1JFVXhEekFOQmdOVkJBY01Ca0psY214cGJqRWRNQnNHQTFVRUNnd1VRblZ1WkdWelpISjFZMnRsY21WcElFZHRZa2d4Q2pBSUJnTlZCQXNNQVVreEdEQVdCZ05WQkFNTUQwbEVkVzVwYjI0Z1ZHVnpkQ0JEUVRBZUZ3MHlNekEzTVRNd09USTFNamhhRncwek16QTNNVEF3T1RJMU1qaGFNR014Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUW93Q0FZRFZRUUxEQUZKTVJnd0ZnWURWUVFEREE5SlJIVnVhVzl1SUZSbGMzUWdRMEV3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVNFSHo4WWpyRnlUTkhHTHZPMTRFQXhtOXloOGJLT2drVXpZV2NDMWN2ckpuNUpnSFlITXhaYk5NTzEzRWgwRXIyNzM4UVFPZ2VSb1pNSVRhb2RrZk5TbzJZd1pEQWRCZ05WSFE0RUZnUVVUNWJyUHRxTk82WjdINWh3VnVnMnRWNExhMUV3SHdZRFZSMGpCQmd3Rm9BVVQ1YnJQdHFOTzZaN0g1aHdWdWcydFY0TGExRXdFZ1lEVlIwVEFRSC9CQWd3QmdFQi93SUJBREFPQmdOVkhROEJBZjhFQkFNQ0FZWXdDZ1lJS29aSXpqMEVBd0lEUndBd1JBSWdZMERlcmRDeHQ0ekdQWW44eU5yRHhJV0NKSHB6cTRCZGpkc1ZOMm8xR1JVQ0lCMEtBN2JHMUZWQjFJaUs4ZDU3UUFMK1BHOVg1bGRLRzdFa29BbWhXVktlIl0sImtpZCI6Ik1Hd3daNlJsTUdNeEN6QUpCZ05WQkFZVEFrUkZNUTh3RFFZRFZRUUhEQVpDWlhKc2FXNHhIVEFiQmdOVkJBb01GRUoxYm1SbGMyUnlkV05yWlhKbGFTQkhiV0pJTVFvd0NBWURWUVFMREFGSk1SZ3dGZ1lEVlFRRERBOUpSSFZ1YVc5dUlGUmxjM1FnUTBFQ0FRUT0iLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJyZXNwb25zZV90eXBlIjoidnBfdG9rZW4iLCJwcmVzZW50YXRpb25fZGVmaW5pdGlvbiI6eyJpZCI6IjE1ZDQwNjU0LWM2NTgtNDkzOC1hYzA3LWVjYjQxYzlhZmIxMCIsImlucHV0X2Rlc2NyaXB0b3JzIjpbeyJpZCI6IjUwYjZlNGYzLTYyMmEtNDk3NC1iMzMwLTVlNzIwZWM5MjJiZiIsImZvcm1hdCI6eyJ2YytzZC1qd3QiOnsicHJvb2ZfdHlwZSI6WyJKc29uV2ViU2lnbmF0dXJlMjAyMCJdfX0sImNvbnN0cmFpbnRzIjp7ImxpbWl0X2Rpc2Nsb3N1cmUiOiJyZXF1aXJlZCIsImZpZWxkcyI6W3sicGF0aCI6WyIkLnR5cGUiXSwiZmlsdGVyIjp7InR5cGUiOiJzdHJpbmciLCJjb25zdCI6IlZlcmlmaWVkRU1haWwifX0seyJwYXRoIjpbIiQuZW1haWwiXX1dfX1dfSwicmVzcG9uc2VfdXJpIjoiaHR0cHM6Ly92ZXJpZmllci5zc2kudGlyLmJ1ZHJ1LmRlL3ByZXNlbnRhdGlvbi9hdXRob3JpemF0aW9uLXJlc3BvbnNlIiwibm9uY2UiOiJZZjg4dGRlZzhZTTkyM3E0aFFBRzlPIiwiY2xpZW50X2lkIjoiaHR0cHM6Ly92ZXJpZmllci5zc2kudGlyLmJ1ZHJ1LmRlL3ByZXNlbnRhdGlvbi9hdXRob3JpemF0aW9uLXJlc3BvbnNlIiwicmVzcG9uc2VfbW9kZSI6ImRpcmVjdF9wb3N0In0.sdeLcG6Ta4ozfbDuHBr2Vq-Ro2WpdUIhJWy3BgazyvrgkQw27uTFGioPWXNCruK5H5E5nvHS420u5tv0671tjg"; - private const string Vct = "VerifiedEmail"; - - public Oid4VpClientServiceTests() { var holder = new Holder(); @@ -45,14 +37,14 @@ public Oid4VpClientServiceTests() var pexService = new PexService(); _sdJwtVcHolderService = new SdJwtVcHolderService(holder, _keyStoreMock.Object, walletRecordService); - _oid4VpHaipClient = new Oid4VpHaipClient(_httpClientFactoryMock.Object, pexService); + var oid4VpHaipClient = new Oid4VpHaipClient(_httpClientFactoryMock.Object, pexService); _oid4VpRecordService = new Oid4VpRecordService(walletRecordService); _oid4VpClientService = new Oid4VpClientService( _httpClientFactoryMock.Object, _sdJwtVcHolderService, pexService, - _oid4VpHaipClient, + oid4VpHaipClient, _loggerMock.Object, _oid4VpRecordService ); @@ -70,20 +62,18 @@ public Oid4VpClientServiceTests() .ReturnsAsync(KeyBindingJwtMock); } - private readonly SdJwtVcHolderService _sdJwtVcHolderService; - private readonly Mock _httpMessageHandlerMock = new(); private readonly Mock _httpClientFactoryMock = new(); - private readonly Mock> _loggerMock = new(); private readonly Mock _keyStoreMock = new(); + private readonly Mock> _loggerMock = new(); private readonly MockAgentRouter _router = new(); - private MockAgent? _agent1; - private readonly Oid4VpClientService _oid4VpClientService; - private readonly Oid4VpHaipClient _oid4VpHaipClient; private readonly Oid4VpRecordService _oid4VpRecordService; + private readonly SdJwtVcHolderService _sdJwtVcHolderService; private readonly WalletConfiguration _config1 = new() { Id = Guid.NewGuid().ToString() }; private readonly WalletCredentials _cred = new() { Key = "2" }; + + private MockAgent? _agent1; [Fact] public async Task CanExecuteOpenId4VpFlow() @@ -93,7 +83,7 @@ public async Task CanExecuteOpenId4VpFlow() var sdJwt = new SdJwtRecord(); - await _sdJwtVcHolderService.SaveAsync(_agent1.Context, sdJwt); + await _sdJwtVcHolderService.SaveAsync(_agent1!.Context, sdJwt); //Act var (authorizationRequest, credentials) = @@ -128,7 +118,7 @@ await _oid4VpClientService.ProcessAuthorizationRequestAsync( public async Task DisposeAsync() { - await _agent1.Dispose(); + await _agent1!.Dispose(); } public async Task InitializeAsync() diff --git a/test/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests/Usings.cs b/test/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests/Usings.cs new file mode 100644 index 00000000..c802f448 --- /dev/null +++ b/test/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests/Usings.cs @@ -0,0 +1 @@ +global using Xunit; diff --git a/test/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests.csproj b/test/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests.csproj new file mode 100644 index 00000000..339f782f --- /dev/null +++ b/test/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests/WalletFramework.Integration.Tests.csproj @@ -0,0 +1,30 @@ + + + net6.0 + enable + enable + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/test/WalletFramework.MdocLib.Tests/WalletFramework.MdocLib.Tests.csproj b/test/WalletFramework.MdocLib.Tests/WalletFramework.MdocLib.Tests.csproj index 3f30eb8a..bc9e9f46 100644 --- a/test/WalletFramework.MdocLib.Tests/WalletFramework.MdocLib.Tests.csproj +++ b/test/WalletFramework.MdocLib.Tests/WalletFramework.MdocLib.Tests.csproj @@ -13,8 +13,11 @@ - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/test/WalletFramework.MdocVc.Tests/WalletFramework.MdocVc.Tests.csproj b/test/WalletFramework.MdocVc.Tests/WalletFramework.MdocVc.Tests.csproj index 85de78e4..eb9936e4 100644 --- a/test/WalletFramework.MdocVc.Tests/WalletFramework.MdocVc.Tests.csproj +++ b/test/WalletFramework.MdocVc.Tests/WalletFramework.MdocVc.Tests.csproj @@ -14,7 +14,7 @@ - + diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs index 64b4a8e4..62e8cd54 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs @@ -50,8 +50,8 @@ public void Can_Encode_To_Json() { var decoded = IssuerMetadataSample.Decoded; - var sut = JObject.FromObject(decoded).RemoveNulls(); - + var sut = JObject.FromObject(decoded).RemoveNulls().ToObject(); + sut.Should().BeEquivalentTo(IssuerMetadataSample.EncodedAsJson); } @@ -66,7 +66,7 @@ public void Can_Decode_And_Encode_From_Json() // Assert sut => { - var encoded = JObject.FromObject(sut).RemoveNulls(); + var encoded = JObject.FromObject(sut).RemoveNulls().ToObject(); encoded.Should().BeEquivalentTo(sample); }, _ => Assert.Fail("IssuerMetadata must be valid")); diff --git a/test/WalletFramework.Oid4Vc.Tests/WalletFramework.Oid4Vc.Tests.csproj b/test/WalletFramework.Oid4Vc.Tests/WalletFramework.Oid4Vc.Tests.csproj index a0c376ea..c8e8b679 100644 --- a/test/WalletFramework.Oid4Vc.Tests/WalletFramework.Oid4Vc.Tests.csproj +++ b/test/WalletFramework.Oid4Vc.Tests/WalletFramework.Oid4Vc.Tests.csproj @@ -9,15 +9,15 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/test/WalletFramework.SdJwtVc.Tests/WalletFramework.SdJwtVc.Tests.csproj b/test/WalletFramework.SdJwtVc.Tests/WalletFramework.SdJwtVc.Tests.csproj index abea3547..c041938a 100644 --- a/test/WalletFramework.SdJwtVc.Tests/WalletFramework.SdJwtVc.Tests.csproj +++ b/test/WalletFramework.SdJwtVc.Tests/WalletFramework.SdJwtVc.Tests.csproj @@ -9,15 +9,15 @@ - + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 0b04128cde3c505217672bf2bef35585b76bc4df Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 17 Jul 2024 16:05:57 +0200 Subject: [PATCH 3/5] move away from json converters Signed-off-by: Kevin --- src/WalletFramework.Core/Colors/Color.cs | 3 - .../Functional/Validation.cs | 3 + .../Json/Converters/DictJsonConverter.cs | 28 ------ .../Json/Converters/OneOfJsonConverter.cs | 29 ------- .../Json/Converters/OptionJsonConverter.cs | 28 ------ .../Json/Converters/ValueTypeJsonConverter.cs | 48 ---------- .../Localization/Locale.cs | 2 - src/WalletFramework.MdocLib/DocType.cs | 3 - .../IssuerSignedItem.cs | 3 - src/WalletFramework.MdocLib/NameSpace.cs | 3 - src/WalletFramework.MdocVc/ClaimDisplay.cs | 6 -- src/WalletFramework.MdocVc/ClaimName.cs | 3 - src/WalletFramework.MdocVc/MdocDisplay.cs | 85 +++++++++++------- src/WalletFramework.MdocVc/MdocLogo.cs | 3 - src/WalletFramework.MdocVc/MdocName.cs | 3 - src/WalletFramework.MdocVc/MdocRecord.cs | 76 ++++++---------- .../Implementations/AuthFlowSessionStorage.cs | 5 +- .../AuthFlow/Models/AuthorizationData.cs | 53 +++++++++++ .../AuthFlow/Records/AuthFlowSessionRecord.cs | 41 +++++---- .../Models/AuthorizationServerId.cs | 3 - .../Models/CredentialConfiguration.cs | 80 +++++++++++++---- .../Models/CredentialDisplay.cs | 66 ++++++++++---- .../Models/CredentialLogo.cs | 35 ++++++-- .../Models/CredentialName.cs | 3 - .../Models/CryptograhicSigningAlgValue.cs | 3 - .../Models/CryptographicBindingMethod.cs | 3 - .../CredConfiguration/Models/Format.cs | 3 - .../Models/Mdoc/ClaimsMetadata.cs | 32 +++---- .../Models/Mdoc/CryptoGraphicCurve.cs | 3 - .../Models/Mdoc/CryptographicSuite.cs | 3 - .../Models/Mdoc/ElementDisplay.cs | 27 ++++-- .../Models/Mdoc/ElementMetadata.cs | 42 ++++++--- .../Models/Mdoc/ElementName.cs | 3 - .../Models/Mdoc/MdocConfiguration.cs | 79 ++++++++--------- .../CredConfiguration/Models/Mdoc/Policy.cs | 32 +++++-- .../CredConfiguration/Models/ProofTypeId.cs | 3 - .../Models/ProofTypeMetadata.cs | 17 ++++ .../Oid4Vci/CredConfiguration/Models/Scope.cs | 3 - .../Models/SdJwt/SdJwtConfiguration.cs | 34 +++----- .../SupportedCredentialConfiguration.cs | 13 ++- .../Models/CredentialConfigurationId.cs | 12 --- .../CredRequest/Models/CredentialRequest.cs | 2 - .../Oid4Vci/Implementations/MdocStorage.cs | 4 +- .../Issuer/Models/CredentialIssuerId.cs | 3 - .../Oid4Vci/Issuer/Models/IssuerMetadata.cs | 87 ++++++++++--------- .../Oid4Vci/Issuer/Models/IssuerName.cs | 3 - .../Models/Metadata/Issuer/IssuerDisplay.cs | 31 ++++--- .../Models/Metadata/Issuer/IssuerLogo.cs | 26 ++++-- src/WalletFramework.SdJwtVc/Models/Vct.cs | 3 - .../MdocRecordTests.cs | 7 +- .../AuthFlow/AuthFlowSessionRecordTests.cs | 4 +- .../AuthFlow/Samples/AuthFlowSamples.cs | 12 +-- .../Oid4Vci/Issuer/IssuerMetadataTests.cs | 20 ++--- 53 files changed, 561 insertions(+), 565 deletions(-) delete mode 100644 src/WalletFramework.Core/Json/Converters/DictJsonConverter.cs delete mode 100644 src/WalletFramework.Core/Json/Converters/OneOfJsonConverter.cs delete mode 100644 src/WalletFramework.Core/Json/Converters/OptionJsonConverter.cs delete mode 100644 src/WalletFramework.Core/Json/Converters/ValueTypeJsonConverter.cs diff --git a/src/WalletFramework.Core/Colors/Color.cs b/src/WalletFramework.Core/Colors/Color.cs index 6d644a6c..607500e8 100644 --- a/src/WalletFramework.Core/Colors/Color.cs +++ b/src/WalletFramework.Core/Colors/Color.cs @@ -1,11 +1,8 @@ using System.Drawing; using LanguageExt; -using Newtonsoft.Json; -using WalletFramework.Core.Json.Converters; namespace WalletFramework.Core.Colors; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct Color { private System.Drawing.Color Value { get; } diff --git a/src/WalletFramework.Core/Functional/Validation.cs b/src/WalletFramework.Core/Functional/Validation.cs index e77dbae6..989059da 100644 --- a/src/WalletFramework.Core/Functional/Validation.cs +++ b/src/WalletFramework.Core/Functional/Validation.cs @@ -271,6 +271,9 @@ public static T UnwrapOrThrow(this Validation validation, Exception e) => t => t, _ => throw e); + public static T UnwrapOrThrow(this Validation validation) => + validation.UnwrapOrThrow(new InvalidOperationException($"Value of Type `{typeof(T)}` is corrupt")); + public static Validator AggregateValidators(this IEnumerable> validators) => t => { var errors = validators diff --git a/src/WalletFramework.Core/Json/Converters/DictJsonConverter.cs b/src/WalletFramework.Core/Json/Converters/DictJsonConverter.cs deleted file mode 100644 index 699ac9b6..00000000 --- a/src/WalletFramework.Core/Json/Converters/DictJsonConverter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace WalletFramework.Core.Json.Converters; - -public sealed class DictJsonConverter : JsonConverter> -{ - public override bool CanRead => false; - - public override void WriteJson(JsonWriter writer, Dictionary? dict, JsonSerializer serializer) - { - var dictJson = new JObject(); - foreach (var (key, config) in dict!) - { - var x = JObject.FromObject(config!); - dictJson.Add(key!.ToString(), x); - } - serializer.Serialize(writer, dictJson); - } - - public override Dictionary ReadJson( - JsonReader reader, - Type objectType, - Dictionary? existingValue, - bool hasExistingValue, - JsonSerializer serializer) => - throw new NotImplementedException(); -} diff --git a/src/WalletFramework.Core/Json/Converters/OneOfJsonConverter.cs b/src/WalletFramework.Core/Json/Converters/OneOfJsonConverter.cs deleted file mode 100644 index 6ec2cba8..00000000 --- a/src/WalletFramework.Core/Json/Converters/OneOfJsonConverter.cs +++ /dev/null @@ -1,29 +0,0 @@ -using LanguageExt; -using Newtonsoft.Json; -using OneOf; - -namespace WalletFramework.Core.Json.Converters; - -public class OneOfJsonConverter : JsonConverter where TOneOf : OneOfBase -{ - public override void WriteJson(JsonWriter writer, TOneOf? oneOf, JsonSerializer serializer) - { - oneOf!.Match( - t1 => - { - serializer.Serialize(writer, t1); - return Unit.Default; - }, - t2 => - { - serializer.Serialize(writer, t2); - return Unit.Default; - }); - } - - public override TOneOf ReadJson(JsonReader reader, Type objectType, TOneOf? existingValue, bool hasExistingValue, - JsonSerializer serializer) => - throw new NotImplementedException(); - - public override bool CanRead => false; -} diff --git a/src/WalletFramework.Core/Json/Converters/OptionJsonConverter.cs b/src/WalletFramework.Core/Json/Converters/OptionJsonConverter.cs deleted file mode 100644 index 0f5c50ac..00000000 --- a/src/WalletFramework.Core/Json/Converters/OptionJsonConverter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using LanguageExt; -using Newtonsoft.Json; - -namespace WalletFramework.Core.Json.Converters; - -public sealed class OptionJsonConverter : JsonConverter> -{ - public override void WriteJson(JsonWriter writer, Option option, JsonSerializer serializer) - { - option.Match( - t => - { - serializer.Serialize(writer, t); - }, - () => serializer.Serialize(writer, null) - ); - } - - public override Option ReadJson( - JsonReader reader, - Type objectType, - Option existingValue, - bool hasExistingValue, - JsonSerializer serializer) => - throw new NotImplementedException(); - - public override bool CanRead => false; -} diff --git a/src/WalletFramework.Core/Json/Converters/ValueTypeJsonConverter.cs b/src/WalletFramework.Core/Json/Converters/ValueTypeJsonConverter.cs deleted file mode 100644 index 2289d809..00000000 --- a/src/WalletFramework.Core/Json/Converters/ValueTypeJsonConverter.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace WalletFramework.Core.Json.Converters; - -public interface IValueTypeDecoder -{ - public T Decode(JToken token); -} - -public sealed class ValueTypeJsonConverter : JsonConverter -{ - public override bool CanRead => false; - - public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer) - { - var str = value!.ToString(); - writer.WriteValue(str); - } - - public override T ReadJson( - JsonReader reader, - Type objectType, - T? existingValue, - bool hasExistingValue, - JsonSerializer serializer) => throw new NotImplementedException(); -} - -public sealed class ValueTypeJsonConverter : JsonConverter where TDecoder : IValueTypeDecoder -{ - public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer) - { - var str = value!.ToString(); - writer.WriteValue(str); - } - - public override T ReadJson( - JsonReader reader, - Type objectType, - T? existingValue, - bool hasExistingValue, - JsonSerializer serializer) - { - var token = JToken.Load(reader); - var decoder = (TDecoder)Activator.CreateInstance(typeof(TDecoder)); - return decoder.Decode(token); - } -} diff --git a/src/WalletFramework.Core/Localization/Locale.cs b/src/WalletFramework.Core/Localization/Locale.cs index 0af15b7e..c7b75b5f 100644 --- a/src/WalletFramework.Core/Localization/Locale.cs +++ b/src/WalletFramework.Core/Localization/Locale.cs @@ -5,7 +5,6 @@ using WalletFramework.Core.Functional; using WalletFramework.Core.Functional.Errors; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Oid4Vc.Oid4Vci.Errors; namespace WalletFramework.Core.Localization; @@ -15,7 +14,6 @@ namespace WalletFramework.Core.Localization; /// ("en-US"). These are based on RFC 4646: https://www.rfc-editor.org/rfc/rfc4646.html. /// Locales are case-sensitive. /// -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct Locale { private CultureInfo Value { get; } diff --git a/src/WalletFramework.MdocLib/DocType.cs b/src/WalletFramework.MdocLib/DocType.cs index 9d58f9e4..b55420ac 100644 --- a/src/WalletFramework.MdocLib/DocType.cs +++ b/src/WalletFramework.MdocLib/DocType.cs @@ -1,14 +1,11 @@ -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PeterO.Cbor; using WalletFramework.Core.Functional; -using WalletFramework.Core.Json.Converters; using WalletFramework.MdocLib.Common; using static WalletFramework.MdocLib.Common.Constants; namespace WalletFramework.MdocLib; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct DocType { private string Value { get; } diff --git a/src/WalletFramework.MdocLib/IssuerSignedItem.cs b/src/WalletFramework.MdocLib/IssuerSignedItem.cs index c28d9e7a..68242235 100644 --- a/src/WalletFramework.MdocLib/IssuerSignedItem.cs +++ b/src/WalletFramework.MdocLib/IssuerSignedItem.cs @@ -1,9 +1,7 @@ -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OneOf; using PeterO.Cbor; using WalletFramework.Core.Functional; -using WalletFramework.Core.Json.Converters; using WalletFramework.MdocLib.Common; using static WalletFramework.MdocLib.ElementArray; using static WalletFramework.MdocLib.ElementMap; @@ -65,7 +63,6 @@ internal static Validation ValidIssuerSignedItem(CBORObject is }); } -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct ElementIdentifier { public string Value { get; } diff --git a/src/WalletFramework.MdocLib/NameSpace.cs b/src/WalletFramework.MdocLib/NameSpace.cs index 3ceeb3c4..d107d6f2 100644 --- a/src/WalletFramework.MdocLib/NameSpace.cs +++ b/src/WalletFramework.MdocLib/NameSpace.cs @@ -1,13 +1,10 @@ -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PeterO.Cbor; using WalletFramework.Core.Functional; -using WalletFramework.Core.Json.Converters; using WalletFramework.MdocLib.Common; namespace WalletFramework.MdocLib; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct NameSpace { public string Value { get; } diff --git a/src/WalletFramework.MdocVc/ClaimDisplay.cs b/src/WalletFramework.MdocVc/ClaimDisplay.cs index c63f6830..2fdc797a 100644 --- a/src/WalletFramework.MdocVc/ClaimDisplay.cs +++ b/src/WalletFramework.MdocVc/ClaimDisplay.cs @@ -1,16 +1,10 @@ using LanguageExt; -using Newtonsoft.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Core.Localization; namespace WalletFramework.MdocVc; public record ClaimDisplay( - [property: JsonProperty(ClaimDisplayJsonKeys.ClaimName)] - [property: JsonConverter(typeof(OptionJsonConverter))] Option Name, - [property: JsonProperty(ClaimDisplayJsonKeys.Locale)] - [property: JsonConverter(typeof(OptionJsonConverter))] Option Locale); public static class ClaimDisplayJsonKeys diff --git a/src/WalletFramework.MdocVc/ClaimName.cs b/src/WalletFramework.MdocVc/ClaimName.cs index b83096d9..ad7cfa34 100644 --- a/src/WalletFramework.MdocVc/ClaimName.cs +++ b/src/WalletFramework.MdocVc/ClaimName.cs @@ -1,10 +1,7 @@ using LanguageExt; -using Newtonsoft.Json; -using WalletFramework.Core.Json.Converters; namespace WalletFramework.MdocVc; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct ClaimName { private string Value { get; } diff --git a/src/WalletFramework.MdocVc/MdocDisplay.cs b/src/WalletFramework.MdocVc/MdocDisplay.cs index 63b97ff6..e4106413 100644 --- a/src/WalletFramework.MdocVc/MdocDisplay.cs +++ b/src/WalletFramework.MdocVc/MdocDisplay.cs @@ -1,47 +1,30 @@ using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Colors; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Core.Localization; using WalletFramework.MdocLib; namespace WalletFramework.MdocVc; public record MdocDisplay( - [property: JsonProperty(MdocDisplayJsonKeys.Logo)] - [property: JsonConverter(typeof(OptionJsonConverter))] Option Logo, - [property: JsonProperty(MdocDisplayJsonKeys.Name)] - [property: JsonConverter(typeof(OptionJsonConverter))] Option Name, - [property: JsonProperty(MdocDisplayJsonKeys.BackgroundColor)] - [property: JsonConverter(typeof(OptionJsonConverter))] Option BackgroundColor, - [property: JsonProperty(MdocDisplayJsonKeys.TextColor)] - [property: JsonConverter(typeof(OptionJsonConverter))] Option TextColor, - [property: JsonProperty(MdocDisplayJsonKeys.Locale)] - [property: JsonConverter(typeof(OptionJsonConverter))] Option Locale, - [property: JsonProperty(MdocDisplayJsonKeys.ClaimsDisplays)] - [property: JsonConverter(typeof(OptionJsonConverter>>>))] Option>>> ClaimsDisplays); -public static class MdocDisplayJsonKeys -{ - public const string Logo = "logo"; - public const string Name = "name"; - public const string BackgroundColor = "background_color"; - public const string TextColor = "text_color"; - public const string Locale = "locale"; - public const string ClaimsDisplays = "claims_displays"; -} - public static class MdocDisplayFun { + private const string LogoJsonKey = "logo"; + private const string NameJsonKey = "name"; + private const string BackgroundColorJsonKey = "background_color"; + private const string TextColorJsonKey = "text_color"; + private const string LocaleJsonKey = "locale"; + private const string ClaimsDisplaysJsonKey = "claims_displays"; + public static Option GetByLocale(this List displays, Locale locale) { var dict = new Dictionary(); @@ -71,6 +54,48 @@ public static Option GetByLocale(this List displays, L return Option.None; } } + + public static JObject EncodeToJson(this MdocDisplay display) + { + var result = new JObject(); + + display.Logo.IfSome(logo => result.Add(LogoJsonKey, logo.ToString())); + display.Name.IfSome(name => result.Add(NameJsonKey, name.ToString())); + display.BackgroundColor.IfSome(color => result.Add(BackgroundColorJsonKey, color.ToString())); + display.TextColor.IfSome(color => result.Add(TextColorJsonKey, color.ToString())); + display.Locale.IfSome(locale => result.Add(LocaleJsonKey, locale.ToString())); + display.ClaimsDisplays.IfSome(claimsDisplays => + { + var claimsDict = new JObject(); + foreach (var (nameSpace, elementDict) in claimsDisplays) + { + var elements = new JObject(); + foreach (var (elementId, claimDisplays) in elementDict) + { + var displays = new JArray(); + foreach (var claimDisplay in claimDisplays) + { + var claimDisplayJson = new JObject(); + + claimDisplay.Name.IfSome( + name => claimDisplayJson.Add(ClaimDisplayJsonKeys.ClaimName, name.ToString()) + ); + + claimDisplay.Locale.IfSome( + locale => claimDisplayJson.Add(ClaimDisplayJsonKeys.Locale, locale.ToString()) + ); + + displays.Add(claimDisplayJson); + } + elements.Add(elementId.ToString(), displays); + } + claimsDict.Add(nameSpace.ToString(), elements); + } + result.Add(ClaimsDisplaysJsonKey, claimsDict); + }); + + return result; + } public static Option> DecodeFromJson(JArray array) { @@ -87,32 +112,32 @@ from displays in result private static MdocDisplay DecodeFromJson(JObject display) { var logo = - from jToken in display.GetByKey(MdocDisplayJsonKeys.Logo).ToOption() + from jToken in display.GetByKey(LogoJsonKey).ToOption() let uri = new Uri(jToken.ToString()) select new MdocLogo(uri); var mdocName = - from jToken in display.GetByKey(MdocDisplayJsonKeys.Name).ToOption() + from jToken in display.GetByKey(NameJsonKey).ToOption() from name in MdocName.OptionMdocName(jToken.ToString()) select name; var backgroundColor = - from jToken in display.GetByKey(MdocDisplayJsonKeys.BackgroundColor).ToOption() + from jToken in display.GetByKey(BackgroundColorJsonKey).ToOption() from color in Color.OptionColor(jToken.ToString()) select color; var textColor = - from jToken in display.GetByKey(MdocDisplayJsonKeys.TextColor).ToOption() + from jToken in display.GetByKey(TextColorJsonKey).ToOption() from color in Color.OptionColor(jToken.ToString()) select color; var locale = - from jToken in display.GetByKey(MdocDisplayJsonKeys.Locale).ToOption() + from jToken in display.GetByKey(LocaleJsonKey).ToOption() from l in Locale.OptionLocale(jToken.ToString()) select l; var claimsDisplays = - from jToken in display.GetByKey(MdocDisplayJsonKeys.ClaimsDisplays).ToOption() + from jToken in display.GetByKey(ClaimsDisplaysJsonKey).ToOption() from claimsJson in jToken.ToJObject().ToOption() from displays in DecodeClaimsDisplaysFromJson(claimsJson) select displays; diff --git a/src/WalletFramework.MdocVc/MdocLogo.cs b/src/WalletFramework.MdocVc/MdocLogo.cs index e17cb727..6d42f7c6 100644 --- a/src/WalletFramework.MdocVc/MdocLogo.cs +++ b/src/WalletFramework.MdocVc/MdocLogo.cs @@ -1,10 +1,7 @@ -using Newtonsoft.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Core.Uri; namespace WalletFramework.MdocVc; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct MdocLogo { public MdocLogo(Uri value) diff --git a/src/WalletFramework.MdocVc/MdocName.cs b/src/WalletFramework.MdocVc/MdocName.cs index f11ca172..07cd44ff 100644 --- a/src/WalletFramework.MdocVc/MdocName.cs +++ b/src/WalletFramework.MdocVc/MdocName.cs @@ -1,10 +1,7 @@ using LanguageExt; -using Newtonsoft.Json; -using WalletFramework.Core.Json.Converters; namespace WalletFramework.MdocVc; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct MdocName { private string Value { get; } diff --git a/src/WalletFramework.MdocVc/MdocRecord.cs b/src/WalletFramework.MdocVc/MdocRecord.cs index 0a24c47e..4c00f983 100644 --- a/src/WalletFramework.MdocVc/MdocRecord.cs +++ b/src/WalletFramework.MdocVc/MdocRecord.cs @@ -2,16 +2,15 @@ using Hyperledger.Aries.Storage.Models; using Hyperledger.Aries.Storage.Models.Interfaces; using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Credentials; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; using WalletFramework.MdocLib; +using static WalletFramework.MdocVc.MdocRecordFun; namespace WalletFramework.MdocVc; -[JsonConverter(typeof(MdocRecordJsonConverter))] public sealed class MdocRecord : RecordBase, ICredential { public CredentialId CredentialId @@ -47,25 +46,43 @@ public MdocRecord() public static implicit operator Mdoc(MdocRecord record) => record.Mdoc; } -public static class MdocRecordJsonKeys +public static class MdocRecordFun { public const string MdocJsonKey = "mdoc"; - public const string MdocDisplaysKey = "displays"; -} + private const string MdocDisplaysJsonKey = "displays"; + + public static JObject EncodeToJson(this MdocRecord record) + { + var result = new JObject + { + {nameof(RecordBase.Id), record.Id}, + {MdocJsonKey, record.Mdoc.Encode()} + }; -public static class MdocRecordFun -{ + record.Displays.IfSome(displays => + { + var displaysJson = new JArray(); + foreach (var display in displays) + { + displaysJson.Add(display.EncodeToJson()); + } + result.Add(MdocDisplaysJsonKey, displaysJson); + }); + + return result; + } + public static MdocRecord DecodeFromJson(JObject json) { var id = json[nameof(RecordBase.Id)]!.ToString(); - var mdocStr = json[MdocRecordJsonKeys.MdocJsonKey]!.ToString(); + var mdocStr = json[MdocJsonKey]!.ToString(); var mdoc = Mdoc .ValidMdoc(mdocStr) .UnwrapOrThrow(new InvalidOperationException($"The MdocRecord with ID: {id} is corrupt")); var displays = - from jToken in json.GetByKey(MdocRecordJsonKeys.MdocDisplaysKey).ToOption() + from jToken in json.GetByKey(MdocDisplaysJsonKey).ToOption() from jArray in jToken.ToJArray().ToOption() from mdocDisplays in MdocDisplayFun.DecodeFromJson(jArray) select mdocDisplays; @@ -80,44 +97,3 @@ from mdocDisplays in MdocDisplayFun.DecodeFromJson(jArray) public static MdocRecord ToRecord(this Mdoc mdoc, Option> displays) => new(mdoc, displays); } - -public sealed class MdocRecordJsonConverter : JsonConverter -{ - public override void WriteJson(JsonWriter writer, MdocRecord? record, JsonSerializer serializer) - { - writer.WriteStartObject(); - - writer.WritePropertyName(nameof(RecordBase.Id)); - writer.WriteValue(record!.Id); - - writer.WritePropertyName(MdocRecordJsonKeys.MdocJsonKey); - writer.WriteValue(record.Mdoc.Encode()); - - writer.WritePropertyName(MdocRecordJsonKeys.MdocDisplaysKey); - record.Displays.Match( - list => - { - writer.WriteStartArray(); - foreach (var display in list) - { - serializer.Serialize(writer, display); - } - writer.WriteEndArray(); - }, - () => {} - ); - - writer.WriteEndObject(); - } - - public override MdocRecord ReadJson( - JsonReader reader, - Type objectType, - MdocRecord? existingValue, - bool hasExistingValue, - JsonSerializer serializer) - { - var json = JObject.Load(reader); - return MdocRecordFun.DecodeFromJson(json); - } -} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Implementations/AuthFlowSessionStorage.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Implementations/AuthFlowSessionStorage.cs index ca00ebf8..e0cbaa08 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Implementations/AuthFlowSessionStorage.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Implementations/AuthFlowSessionStorage.cs @@ -1,6 +1,5 @@ using Hyperledger.Aries.Agents; using Hyperledger.Aries.Storage; -using LanguageExt; using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Abstractions; using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Records; @@ -34,7 +33,7 @@ public async Task StoreAsync( authorizationCodeParameters, sessionId); - await _recordService.AddAsync(agentContext.Wallet, record); + await _recordService.AddAsync(agentContext.Wallet, record, AuthFlowSessionRecordFun.EncodeToJson); return record.Id; } @@ -42,7 +41,7 @@ public async Task StoreAsync( /// public async Task GetAsync(IAgentContext context, VciSessionId sessionId) { - var record = await _recordService.GetAsync(context.Wallet, sessionId); + var record = await _recordService.GetAsync(context.Wallet, sessionId, AuthFlowSessionRecordFun.DecodeFromJson); return record!; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationData.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationData.cs index 86017ddb..7ae073d5 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationData.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Models/AuthorizationData.cs @@ -1,3 +1,6 @@ +using System.Globalization; +using Newtonsoft.Json.Linq; +using WalletFramework.Core.Functional; using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; @@ -9,3 +12,53 @@ public record AuthorizationData( IssuerMetadata IssuerMetadata, AuthorizationServerMetadata AuthorizationServerMetadata, List CredentialConfigurationIds); + +public static class AuthorizationDataFun +{ + private const string ClientOptionsJsonKey = "client_options"; + private const string IssuerMetadataJsonKey = "issuer_metadata"; + private const string AuthorizationServerMetadataJsonKey = "authorization_server_metadata"; + private const string CredentialConfigurationIdsJsonKey = "credential_configuration_ids"; + + public static JObject EncodeToJson(this AuthorizationData data) + { + var clientOptions = JObject.FromObject(data.ClientOptions); + + var issuerMetadata = data.IssuerMetadata.EncodeToJson(); + + var authServerMetadata = JObject.FromObject(data.AuthorizationServerMetadata); + + var ids = data.CredentialConfigurationIds.Select(id => id.ToString()); + var idsArray = new JArray(ids); + + return new JObject + { + { ClientOptionsJsonKey, clientOptions }, + { IssuerMetadataJsonKey, issuerMetadata }, + { AuthorizationServerMetadataJsonKey, authServerMetadata }, + { CredentialConfigurationIdsJsonKey, idsArray } + }; + } + + public static AuthorizationData DecodeFromJson(JObject json) + { + var clientOptions = json[ClientOptionsJsonKey]!.ToObject()!; + + var issuerMetadata = IssuerMetadata + .ValidIssuerMetadata(json[IssuerMetadataJsonKey]!.ToObject()!) + .UnwrapOrThrow(); + + var authServerMetadata = json[AuthorizationServerMetadataJsonKey]!.ToObject()!; + + var configIds = json[CredentialConfigurationIdsJsonKey]!.Cast().Select(value => + { + var str = value.ToString(CultureInfo.InvariantCulture); + return CredentialConfigurationId + .ValidCredentialConfigurationId(str) + .UnwrapOrThrow(); + + }).ToList(); + + return new AuthorizationData(clientOptions, issuerMetadata, authServerMetadata, configIds); + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Records/AuthFlowSessionRecord.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Records/AuthFlowSessionRecord.cs index 6c5c62f3..26fb9b46 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Records/AuthFlowSessionRecord.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/AuthFlow/Records/AuthFlowSessionRecord.cs @@ -9,7 +9,6 @@ namespace WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Records; /// /// Represents the authorization session record. Used during the VCI Authorization Code Flow to hold session relevant information. /// -[JsonConverter(typeof(AuthFlowSessionRecordJsonConverter))] public sealed class AuthFlowSessionRecord : RecordBase { /// @@ -70,31 +69,35 @@ public AuthFlowSessionRecord( } } -public sealed class AuthFlowSessionRecordJsonConverter : JsonConverter +public static class AuthFlowSessionRecordFun { - public override bool CanWrite => false; + private const string AuthorizationDataJsonKey = "authorization_data"; + private const string AuthorizationCodeParametersJsonKey = "authorization_code_parameters"; - public override void WriteJson(JsonWriter writer, AuthFlowSessionRecord? record, JsonSerializer serializer) - => throw new NotImplementedException(); + public static JObject EncodeToJson(this AuthFlowSessionRecord record) + { + var authorizationData = record.AuthorizationData.EncodeToJson(); + var authorizationCodeParameters = JObject.FromObject(record.AuthorizationCodeParameters); + + return new JObject + { + { nameof(RecordBase.Id), record.Id }, + { AuthorizationDataJsonKey, authorizationData }, + { AuthorizationCodeParametersJsonKey, authorizationCodeParameters } + }; + } - public override AuthFlowSessionRecord ReadJson( - JsonReader reader, - Type objectType, - AuthFlowSessionRecord? existingValue, - bool hasExistingValue, - JsonSerializer serializer) + public static AuthFlowSessionRecord DecodeFromJson(JObject json) { - var json = JObject.Load(reader); + var idJson = json[nameof(RecordBase.Id)]!.ToObject()!; + var id = VciSessionIdFun.DecodeFromJson(idJson); - var id = VciSessionIdFun.DecodeFromJson(json[nameof(RecordBase.Id)]!.ToObject()!); - var authCodeParameters = JsonConvert.DeserializeObject( - json[nameof(AuthorizationCodeParameters)]!.ToString() + json[AuthorizationCodeParametersJsonKey]!.ToString() ); - - var authorizationData = JsonConvert.DeserializeObject( - json[nameof(AuthorizationData)]!.ToString() - )!; + + var authorizationData = AuthorizationDataFun + .DecodeFromJson(json[AuthorizationDataJsonKey]!.ToObject()!); var result = new AuthFlowSessionRecord(authorizationData, authCodeParameters!, id); diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/AuthorizationServerId.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/AuthorizationServerId.cs index acc7b37d..f25d7c75 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/AuthorizationServerId.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/AuthorizationServerId.cs @@ -1,15 +1,12 @@ using System.Globalization; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Core.Uri; using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Errors; namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct AuthorizationServerId { private Uri Value { get; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialConfiguration.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialConfiguration.cs index ea7ad271..823873bd 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialConfiguration.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialConfiguration.cs @@ -1,9 +1,7 @@ using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using static WalletFramework.Core.Functional.ValidationFun; using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Format; using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Scope; @@ -12,6 +10,7 @@ using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.ProofTypeId; using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.ProofTypeMetadata; using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CredentialDisplay; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CredentialConfigurationFun; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; @@ -23,44 +22,33 @@ public record CredentialConfiguration /// /// Gets the identifier for the format of the credential. /// - [JsonProperty(FormatJsonKey)] public Format Format { get; } /// /// Gets a string indicating the credential that can be issued. /// - [JsonProperty(ScopeJsonKey)] - [JsonConverter(typeof(OptionJsonConverter))] public Option Scope { get; set; } /// /// Gets list of methods that identify how the Credential is bound to the identifier of the End-User who /// possesses the Credential. /// - [JsonProperty(CryptographicBindingMethodsSupportedJsonKey)] - [JsonConverter(typeof(OptionJsonConverter>))] public Option> CryptographicBindingMethodsSupported { get; } /// /// Gets a list of identifiers for the signing algorithms that are supported by the issuer and used /// to sign credentials. /// - [JsonProperty(CredentialSigningAlgValuesSupportedJsonKey)] - [JsonConverter(typeof(OptionJsonConverter>))] public Option> CredentialSigningAlgValuesSupported { get; } /// /// Gets a dictionary which maps a credential type to its supported signing algorithms for key proofs. /// - [JsonProperty(ProofTypesSupportedJsonKey)] - [JsonConverter(typeof(OptionJsonConverter>))] public Option> ProofTypesSupported { get; } /// /// Gets a list of display properties of the supported credential for different languages. /// - [JsonProperty(DisplayJsonKey)] - [JsonConverter(typeof(OptionJsonConverter>))] public Option> Display { get; } private CredentialConfiguration( @@ -122,11 +110,65 @@ from displays in array.TraverseAny(OptionalCredentialDisplay) .Apply(credentialMetadata.GetByKey(ProofTypesSupportedJsonKey).OnSuccess(validProofTypes).ToOption()) .Apply(credentialMetadata.GetByKey(DisplayJsonKey).ToOption().OnSome(optionalCredentialDisplays)); } +} + +public static class CredentialConfigurationFun +{ + public const string FormatJsonKey = "format"; + public const string ScopeJsonKey = "scope"; + public const string CryptographicBindingMethodsSupportedJsonKey = "cryptographic_binding_methods_supported"; + public const string CredentialSigningAlgValuesSupportedJsonKey = "credential_signing_alg_values_supported"; + public const string ProofTypesSupportedJsonKey = "proof_types_supported"; + public const string DisplayJsonKey = "display"; + + public static JObject EncodeToJson(this CredentialConfiguration config) + { + var result = new JObject(); + + result.Add(FormatJsonKey, config.Format.ToString()); + + config.Scope.IfSome(scope => result.Add(ScopeJsonKey, scope.ToString())); - private const string FormatJsonKey = "format"; - private const string ScopeJsonKey = "scope"; - private const string CryptographicBindingMethodsSupportedJsonKey = "cryptographic_binding_methods_supported"; - private const string CredentialSigningAlgValuesSupportedJsonKey = "credential_signing_alg_values_supported"; - private const string ProofTypesSupportedJsonKey = "proof_types_supported"; - private const string DisplayJsonKey = "display"; + config.CryptographicBindingMethodsSupported.IfSome(list => + { + var bindingMethods = new JArray(); + foreach (var method in list) + { + bindingMethods.Add(method.ToString()); + } + result.Add(CryptographicBindingMethodsSupportedJsonKey, bindingMethods); + }); + + config.CredentialSigningAlgValuesSupported.IfSome(list => + { + var signingAlgValues = new JArray(); + foreach (var value in list) + { + signingAlgValues.Add(value.ToString()); + } + result.Add(CredentialSigningAlgValuesSupportedJsonKey, signingAlgValues); + }); + + config.ProofTypesSupported.IfSome(dict => + { + var proofTypes = new JObject(); + foreach (var (key, value) in dict) + { + proofTypes.Add(key.ToString(), value.EncodeToJson()); + } + result.Add(ProofTypesSupportedJsonKey, proofTypes); + }); + + config.Display.IfSome(displays => + { + var displayArray = new JArray(); + foreach (var display in displays) + { + displayArray.Add(display.EncodeToJson()); + } + result.Add(DisplayJsonKey, displayArray); + }); + + return result; + } } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialDisplay.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialDisplay.cs index 63580e48..20c51065 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialDisplay.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialDisplay.cs @@ -1,16 +1,13 @@ -using System.Drawing; using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using WalletFramework.Core.Colors; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Core.Localization; using WalletFramework.SdJwtVc.Models.Credential; using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CredentialName; using static WalletFramework.Core.Localization.Locale; using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CredentialLogo; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CredentialDisplayJsonExtensions; using Color = WalletFramework.Core.Colors.Color; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; @@ -23,36 +20,26 @@ public record CredentialDisplay /// /// Gets the logo associated with this Credential. /// - [JsonProperty("logo")] - [JsonConverter(typeof(OptionJsonConverter))] public Option Logo { get; } /// /// Gets the name of the Credential. /// - [JsonProperty("name")] - [JsonConverter(typeof(OptionJsonConverter))] public Option Name { get; } /// /// Gets the background color for the Credential. /// - [JsonProperty("background_color")] - [JsonConverter(typeof(OptionJsonConverter))] public Option BackgroundColor { get; } /// /// Gets the locale, which represents the specific culture or region. /// - [JsonProperty("locale")] - [JsonConverter(typeof(OptionJsonConverter))] public Option Locale { get; } /// /// Gets the text color for the Credential. /// - [JsonProperty("text_color")] - [JsonConverter(typeof(OptionJsonConverter))] public Option TextColor { get; } private CredentialDisplay( @@ -75,18 +62,18 @@ public static Option OptionalCredentialDisplay(JToken display .OnSome(jObject => { var backgroundColor = jObject - .GetByKey("background_color") + .GetByKey(BackgroundColorJsonKey) .ToOption() .OnSome(color => Color.OptionColor(color.ToString())); var textColor = jObject - .GetByKey("text_color") + .GetByKey(TextColorJsonKey) .ToOption() .OnSome(color => Color.OptionColor(color.ToString())); - var name = jObject.GetByKey("name").ToOption().OnSome(OptionalCredentialName); - var logo = jObject.GetByKey("logo").ToOption().OnSome(OptionalCredentialLogo); - var locale = jObject.GetByKey("locale").OnSuccess(ValidLocale).ToOption(); + var name = jObject.GetByKey(NameJsonKey).ToOption().OnSome(OptionalCredentialName); + var logo = jObject.GetByKey(LogoJsonKey).ToOption().OnSome(OptionalCredentialLogo); + var locale = jObject.GetByKey(LocaleJsonKey).OnSuccess(ValidLocale).ToOption(); if (name.IsNone && logo.IsNone && backgroundColor.IsNone && locale.IsNone && textColor.IsNone) return Option.None; @@ -116,3 +103,44 @@ public static SdJwtDisplay ToSdJwtDisplay(this CredentialDisplay credentialDispl }; } } + +public static class CredentialDisplayJsonExtensions +{ + public const string LogoJsonKey = "logo"; + public const string NameJsonKey = "name"; + public const string BackgroundColorJsonKey = "background_color"; + public const string LocaleJsonKey = "locale"; + public const string TextColorJsonKey = "text_color"; + + public static JObject EncodeToJson(this CredentialDisplay display) + { + var result = new JObject(); + + display.Logo.IfSome(logo => + { + result.Add(LogoJsonKey, logo.EncodeToJson()); + }); + + display.Name.IfSome(name => + { + result.Add(NameJsonKey, name.ToString()); + }); + + display.BackgroundColor.IfSome(color => + { + result.Add(BackgroundColorJsonKey, color.ToString()); + }); + + display.Locale.IfSome(locale => + { + result.Add(LocaleJsonKey, locale.ToString()); + }); + + display.TextColor.IfSome(color => + { + result.Add(TextColorJsonKey, color.ToString()); + }); + + return result; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialLogo.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialLogo.cs index 2a58ee40..99a9d079 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialLogo.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialLogo.cs @@ -1,9 +1,9 @@ using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; +using WalletFramework.Core.Uri; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.CredentialLogoJsonExtensions; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; @@ -15,15 +15,11 @@ public record CredentialLogo /// /// Gets the alternate text that describes the logo image. This is typically used for accessibility purposes. /// - [JsonProperty("alt_text")] - [JsonConverter(typeof(OptionJsonConverter))] public Option AltText { get; } /// /// Gets the URL of the logo image. /// - [JsonProperty("uri")] - [JsonConverter(typeof(OptionJsonConverter))] public Option Uri { get; } private CredentialLogo(Option altText, Option uri) @@ -34,7 +30,7 @@ private CredentialLogo(Option altText, Option uri) public static Option OptionalCredentialLogo(JToken logo) { - var altText = logo.GetByKey("alt_text").ToOption().OnSome(text => + var altText = logo.GetByKey(AltTextJsonKey).ToOption().OnSome(text => { var str = text.ToString(); if (string.IsNullOrWhiteSpace(str)) @@ -43,7 +39,7 @@ public static Option OptionalCredentialLogo(JToken logo) return str; }); - var imageUri = logo.GetByKey("uri").ToOption().OnSome(uri => + var imageUri = logo.GetByKey(UriJsonKey).ToOption().OnSome(uri => { try { @@ -63,3 +59,26 @@ public static Option OptionalCredentialLogo(JToken logo) return new CredentialLogo(altText, imageUri); } } + +public static class CredentialLogoJsonExtensions +{ + public const string AltTextJsonKey = "alt_text"; + public static string UriJsonKey => "uri"; + + public static JObject EncodeToJson(this CredentialLogo logo) + { + var result = new JObject(); + + logo.AltText.IfSome(altText => + { + result.Add(AltTextJsonKey, altText); + }); + + logo.Uri.IfSome(uri => + { + result.Add(UriJsonKey, uri.ToStringWithoutTrail()); + }); + + return result; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialName.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialName.cs index 8bfc5d0f..1a6f4ba0 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialName.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CredentialName.cs @@ -1,14 +1,11 @@ using System.Globalization; using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct CredentialName { private string Value { get; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptograhicSigningAlgValue.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptograhicSigningAlgValue.cs index ded1adeb..477ba37c 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptograhicSigningAlgValue.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptograhicSigningAlgValue.cs @@ -1,15 +1,12 @@ using System.Globalization; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Functional.Errors; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using static WalletFramework.Core.Functional.ValidationFun; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct CryptograhicSigningAlgValue { private string Value { get; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptographicBindingMethod.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptographicBindingMethod.cs index bf1b7f2c..f2516e10 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptographicBindingMethod.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/CryptographicBindingMethod.cs @@ -1,14 +1,11 @@ using System.Globalization; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Functional.Errors; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct CryptographicBindingMethod { private string Value { get; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Format.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Format.cs index 2ef65059..df136e21 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Format.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Format.cs @@ -1,14 +1,11 @@ using System.Globalization; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Errors; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct Format { private string Value { get; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ClaimsMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ClaimsMetadata.cs index c3b30a86..082b9fc8 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ClaimsMetadata.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ClaimsMetadata.cs @@ -1,5 +1,4 @@ using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; @@ -9,7 +8,6 @@ namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; -[JsonConverter(typeof(ClaimsMetadataJsonConverter))] public readonly struct ClaimsMetadata { public Dictionary> Value { get; } @@ -22,28 +20,26 @@ public static Validation ValidClaimsMetadata(JObject claims) => .OnSuccess(dictionary => new ClaimsMetadata(dictionary)); } -public class ClaimsMetadataJsonConverter : JsonConverter +public static class ClaimsMetadataFun { - public override bool CanRead => false; - - public override void WriteJson(JsonWriter writer, ClaimsMetadata claimsMetadata, JsonSerializer serializer) + public static JObject EncodeToJson(this ClaimsMetadata metadata) { - writer.WriteStartObject(); + var result = new JObject(); - var value = JObject.FromObject(claimsMetadata.Value, serializer); - foreach (var property in value.Properties()) + foreach (var (key, elementMetadatas) in metadata.Value) { - property.WriteTo(writer); + var elementsJson = new JObject(); + foreach (var (elementIdentifier, elementMetadata) in elementMetadatas) + { + elementsJson.Add(elementIdentifier, elementMetadata.EncodeToJson()); + } + + result.Add(key.ToString(), elementsJson); } - } - public override ClaimsMetadata ReadJson(JsonReader reader, Type objectType, ClaimsMetadata existingValue, bool hasExistingValue, - JsonSerializer serializer) => - throw new NotImplementedException(); -} - -public static class ClaimsMetadataFun -{ + return result; + } + public static Option>>> ToClaimsDisplays( this ClaimsMetadata claimsMetadata) { diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptoGraphicCurve.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptoGraphicCurve.cs index a076ee4e..6f157659 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptoGraphicCurve.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptoGraphicCurve.cs @@ -1,12 +1,9 @@ -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; -using WalletFramework.Core.Json.Converters; using WalletFramework.MdocLib; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct CryptographicCurve { public CoseLabel Value { get; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptographicSuite.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptographicSuite.cs index 00191e4b..bc697f22 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptographicSuite.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/CryptographicSuite.cs @@ -1,14 +1,11 @@ using System.Globalization; -using System.Text.Json.Serialization; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Functional.Errors; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct CryptographicSuite { // TODO: Validate if Value is part of IANA Registry diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementDisplay.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementDisplay.cs index 40a0b7e5..a43054b6 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementDisplay.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementDisplay.cs @@ -1,21 +1,16 @@ using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Core.Localization; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.ElementDisplayFun; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; public record ElementDisplay { - [JsonProperty("locale")] - [JsonConverter(typeof(OptionJsonConverter))] public Option Locale { get; } - [JsonProperty("name")] - [JsonConverter(typeof(OptionJsonConverter))] public Option Name { get; } private ElementDisplay(Option name, Option locale) @@ -26,11 +21,11 @@ private ElementDisplay(Option name, Option locale) public static Option OptionalElementDisplay(JObject display) { - var name = display.GetByKey("name").Match( + var name = display.GetByKey(NameJsonKey).Match( ElementName.OptionalElementName, _ => Option.None); - var locale = display.GetByKey("locale").Match( + var locale = display.GetByKey(LocaleJsonKey).Match( Core.Localization.Locale.OptionLocale, _ => Option.None); @@ -42,3 +37,19 @@ public static Option OptionalElementDisplay(JObject display) return new ElementDisplay(name, locale); } } + +public static class ElementDisplayFun +{ + public const string LocaleJsonKey = "locale"; + public const string NameJsonKey = "name"; + + public static JObject EncodeToJson(this ElementDisplay display) + { + var result = new JObject(); + + display.Locale.IfSome(locale => result.Add(LocaleJsonKey, locale.ToString())); + display.Name.IfSome(name => result.Add(NameJsonKey, name.ToString())); + + return result; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementMetadata.cs index fe90bb99..cb5c06ba 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementMetadata.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementMetadata.cs @@ -1,21 +1,16 @@ using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.MdocLib; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.ElementMetadataFun; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; public record ElementMetadata { - [JsonProperty("mandatory")] - [JsonConverter(typeof(OptionJsonConverter))] public Option Mandatory { get; } - [JsonProperty("display")] - [JsonConverter(typeof(OptionJsonConverter>))] public Option> Display { get; } private ElementMetadata(Option mandatory, Option> display) @@ -24,9 +19,9 @@ private ElementMetadata(Option mandatory, Option> dis Display = display; } - public static ElementMetadata CreateElementMetadata(JToken metadata) + private static ElementMetadata CreateElementMetadata(JToken metadata) { - var mandatory = metadata.GetByKey("mandatory").Match( + var mandatory = metadata.GetByKey(MandatoryJsonKey).Match( jToken => { var str = jToken.ToString(); @@ -35,7 +30,7 @@ public static ElementMetadata CreateElementMetadata(JToken metadata) _ => Option.None); var validDisplay = - from token in metadata.GetByKey("display") + from token in metadata.GetByKey(DisplayJsonKey) from array in token.ToJArray() select array; @@ -59,5 +54,32 @@ public static Validation> ValidEl .ToJObject() .OnSuccess(o => o.ToValidDictionaryAll( ElementIdentifier.ValidElementIdentifier, - token => ValidationFun.Valid(ElementMetadata.CreateElementMetadata(token)))); + token => ValidationFun.Valid(CreateElementMetadata(token)))); +} + +public static class ElementMetadataFun +{ + public const string MandatoryJsonKey = "mandatory"; + public const string DisplayJsonKey = "display"; + + public static JObject EncodeToJson(this ElementMetadata metadata) + { + var result = new JObject(); + + metadata.Mandatory.IfSome( + mandatory => result.Add(MandatoryJsonKey, mandatory) + ); + + metadata.Display.IfSome(displays => + { + var jArray = new JArray(); + foreach (var display in displays) + { + jArray.Add(display.EncodeToJson()); + } + result.Add(DisplayJsonKey, jArray); + }); + + return result; + } } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementName.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementName.cs index 2d3623a2..908b87ab 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementName.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/ElementName.cs @@ -1,11 +1,8 @@ using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using WalletFramework.Core.Json.Converters; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct ElementName { private string Value { get; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/MdocConfiguration.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/MdocConfiguration.cs index 759148d4..a63b44c3 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/MdocConfiguration.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/MdocConfiguration.cs @@ -1,19 +1,16 @@ using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.MdocLib; using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.CryptographicCurve; using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.CryptographicSuite; using static WalletFramework.MdocLib.DocType; using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.Policy; -using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.MdocConfiguration.MdocConfigurationJsonKeys; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.MdocConfigurationFun; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; -[JsonConverter(typeof(MdocConfigurationJsonConverter))] public record MdocConfiguration { public CredentialConfiguration CredentialConfiguration { get; } @@ -91,56 +88,50 @@ public static Validation ValidMdocConfiguration(JObject confi .Apply(cryptographicCurvesSupported) .Apply(claims); } - - public static class MdocConfigurationJsonKeys - { - public const string DocTypeJsonKey = "doctype"; - public const string PolicyJsonKey = "policy"; - public const string CryptographicSuitesSupportedJsonKey = "cryptographic_suites_supported"; - public const string CryptographicCurvesSupportedJsonKey = "cryptographic_curves_supported"; - public const string ClaimsJsonKey = "claims"; - } } -public class MdocConfigurationJsonConverter : JsonConverter +public static class MdocConfigurationFun { - public override bool CanRead => false; + public const string DocTypeJsonKey = "doctype"; + public const string PolicyJsonKey = "policy"; + public const string CryptographicSuitesSupportedJsonKey = "cryptographic_suites_supported"; + public const string CryptographicCurvesSupportedJsonKey = "cryptographic_curves_supported"; + public const string ClaimsJsonKey = "claims"; - public override void WriteJson(JsonWriter writer, MdocConfiguration? mdocConfig, JsonSerializer serializer) + public static JObject EncodeToJson(this MdocConfiguration mdocConfig) { - writer.WriteStartObject(); - - var credentialConfig = JObject.FromObject(mdocConfig!.CredentialConfiguration); - foreach (var property in credentialConfig.Properties()) - { - property.WriteTo(writer); - } + var configJson = mdocConfig.CredentialConfiguration.EncodeToJson(); - serializer.Converters.Add(new OptionJsonConverter()); - serializer.Converters.Add(new OptionJsonConverter>()); - serializer.Converters.Add(new OptionJsonConverter>()); - serializer.Converters.Add(new OptionJsonConverter()); - serializer.Converters.Add(new ValueTypeJsonConverter()); + configJson.Add(DocTypeJsonKey, mdocConfig.DocType.ToString()); - writer.WritePropertyName(DocTypeJsonKey); - serializer.Serialize(writer, mdocConfig.DocType); + mdocConfig.Policy.IfSome(policy => + configJson.Add(PolicyJsonKey, policy.EncodeToJson()) + ); - writer.WritePropertyName(PolicyJsonKey); - serializer.Serialize(writer, mdocConfig.Policy); - - writer.WritePropertyName(CryptographicSuitesSupportedJsonKey); - serializer.Serialize(writer, mdocConfig.CryptographicSuitesSupported); + mdocConfig.CryptographicSuitesSupported.IfSome(suites => + { + var suitesJson = new JArray(); + foreach (var suite in suites) + { + suitesJson.Add(suite.ToString()); + } + configJson.Add(CryptographicSuitesSupportedJsonKey, suitesJson); + }); - writer.WritePropertyName(CryptographicCurvesSupportedJsonKey); - serializer.Serialize(writer, mdocConfig.CryptographicCurvesSupported); + mdocConfig.CryptographicCurvesSupported.IfSome(curves => + { + var curvesJson = new JArray(); + foreach (var curve in curves) + { + curvesJson.Add(curve.ToString()); + } + configJson.Add(CryptographicCurvesSupportedJsonKey, curvesJson); + }); - writer.WritePropertyName(ClaimsJsonKey); - serializer.Serialize(writer, mdocConfig.Claims); + mdocConfig.Claims.IfSome(claims => + configJson.Add(ClaimsJsonKey, claims.EncodeToJson()) + ); - writer.WriteEndObject(); + return configJson; } - - public override MdocConfiguration ReadJson(JsonReader reader, Type objectType, MdocConfiguration? existingValue, - bool hasExistingValue, JsonSerializer serializer) => - throw new NotImplementedException(); } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/Policy.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/Policy.cs index 565b7dd6..654e0521 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/Policy.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Mdoc/Policy.cs @@ -1,23 +1,19 @@ using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Core.Json.Errors; using WalletFramework.MdocLib.Common; using WalletFramework.MdocVc.Common; using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Errors; +using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.PolicyFun; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; public record Policy { - [JsonProperty("one_time_use")] public bool OneTimeUse { get; } - [JsonProperty("batch_size")] - [JsonConverter(typeof(OptionJsonConverter))] public Option BatchSize { get; } private Policy(bool oneTimeUse, Option batchSize) @@ -41,13 +37,13 @@ public static Validation ValidPolicy(JToken policy) } var oneTimeUse = jObject - .GetByKey("one_time_use") + .GetByKey(OneTimeUseJsonKey) .OnSuccess(token => { var str = token.ToString(); if (string.IsNullOrWhiteSpace(str)) { - return new FieldValueIsNullOrEmptyError("one_time_use").ToInvalid(); + return new FieldValueIsNullOrEmptyError(OneTimeUseJsonKey).ToInvalid(); } else { @@ -63,7 +59,7 @@ public static Validation ValidPolicy(JToken policy) }); var batchSize = jObject - .GetByKey("batch_size") + .GetByKey(BatchSizeJsonKey) .OnSuccess(token => { try @@ -83,3 +79,23 @@ public static Validation ValidPolicy(JToken policy) .Apply(batchSize); } } + +public static class PolicyFun +{ + public const string OneTimeUseJsonKey = "one_time_use"; + public const string BatchSizeJsonKey = "batch_size"; + + public static JObject EncodeToJson(this Policy policy) + { + var policyJson = new JObject + { + { OneTimeUseJsonKey, policy.OneTimeUse } + }; + + policy.BatchSize.IfSome(batchSize => + policyJson.Add(BatchSizeJsonKey, batchSize) + ); + + return policyJson; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeId.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeId.cs index 7ffb7ff0..7bf34c70 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeId.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeId.cs @@ -1,14 +1,11 @@ using System.Globalization; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Errors; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct ProofTypeId { private string Value { get; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeMetadata.cs index 300ef7ac..3c50be38 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeMetadata.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/ProofTypeMetadata.cs @@ -29,3 +29,20 @@ from jArray in jToken.ToJArray() from algValues in jArray.TraverseAny(ValidCryptograhicSigningAlgValue) select new ProofTypeMetadata(algValues.ToList()); } + +public static class ProofTypeMetadataFun +{ + public static JObject EncodeToJson(this ProofTypeMetadata proofTypeMetadata) + { + var result = new JObject(); + + var jArray = new JArray(); + foreach (var algValue in proofTypeMetadata.ProofSigningAlgValuesSupported) + { + jArray.Add(algValue.ToString()); + } + result.Add("proof_signing_alg_values_supported", jArray); + + return result; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Scope.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Scope.cs index 3a6d371c..5466d8d3 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Scope.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/Scope.cs @@ -1,14 +1,11 @@ using System.Globalization; using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct Scope { private string Value { get; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SdJwt/SdJwtConfiguration.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SdJwt/SdJwtConfiguration.cs index 8b0c0668..3e87132e 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SdJwt/SdJwtConfiguration.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SdJwt/SdJwtConfiguration.cs @@ -9,7 +9,6 @@ namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.SdJwt; -[JsonConverter(typeof(SdJwtConfigurationJsonConverter))] public record SdJwtConfiguration { public CredentialConfiguration CredentialConfiguration { get; } @@ -65,33 +64,24 @@ public static class SdJwtConfigurationJsonKeys } } -public class SdJwtConfigurationJsonConverter : JsonConverter +public static class SdJwtConfigurationFun { - public override bool CanRead => false; - - public override void WriteJson(JsonWriter writer, SdJwtConfiguration? sdJwtConfig, JsonSerializer serializer) + public static JObject EncodeToJson(this SdJwtConfiguration config) { - writer.WriteStartObject(); + var credentialConfig = config.CredentialConfiguration.EncodeToJson(); + + credentialConfig.Add(VctJsonName, config.Vct.ToString()); - var credentialConfig = JObject.FromObject(sdJwtConfig!.CredentialConfiguration, serializer); - foreach (var property in credentialConfig.Properties()) + if (config.Claims is not null) { - property.WriteTo(writer); + credentialConfig.Add(ClaimsJsonName, JObject.FromObject(config.Claims)); } - writer.WritePropertyName(ClaimsJsonName); - serializer.Serialize(writer, sdJwtConfig.Claims); - - writer.WritePropertyName(OrderJsonName); - serializer.Serialize(writer, sdJwtConfig.Order); + if (config.Order is not null) + { + credentialConfig.Add(OrderJsonName, JArray.FromObject(config.Order)); + } - writer.WritePropertyName(VctJsonName); - serializer.Serialize(writer, sdJwtConfig.Vct); - - writer.WriteEndObject(); + return credentialConfig; } - - public override SdJwtConfiguration ReadJson(JsonReader reader, Type objectType, SdJwtConfiguration? existingValue, - bool hasExistingValue, JsonSerializer serializer) => - throw new NotImplementedException(); } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SupportedCredentialConfiguration.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SupportedCredentialConfiguration.cs index 826cb54e..9ddcdf29 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SupportedCredentialConfiguration.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SupportedCredentialConfiguration.cs @@ -1,12 +1,10 @@ -using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using OneOf; -using WalletFramework.Core.Json.Converters; using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.SdJwt; namespace WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; -[JsonConverter(typeof(OneOfJsonConverter))] public sealed class SupportedCredentialConfiguration : OneOfBase { public static implicit operator OneOf( @@ -29,3 +27,12 @@ private SupportedCredentialConfiguration(OneOf + config.Match( + sdJwt => sdJwt.EncodeToJson(), + mdoc => mdoc.EncodeToJson() + ); +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialConfigurationId.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialConfigurationId.cs index d0010ec4..db4ea1b6 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialConfigurationId.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredOffer/Models/CredentialConfigurationId.cs @@ -1,14 +1,11 @@ using System.Globalization; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors; namespace WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct CredentialConfigurationId { private string Value { get; } @@ -36,12 +33,3 @@ public static Validation ValidCredentialConfiguration } }); } - -public class CredentialConfigurationIdDecoder : IValueTypeDecoder -{ - public CredentialConfigurationId Decode(JToken token) => - CredentialConfigurationId - .ValidCredentialConfigurationId(token) - .UnwrapOrThrow(new InvalidOperationException("CredentialConfigurationId is corrupt")); -} - diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs index a72c0652..fcb1d2a4 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs @@ -1,6 +1,5 @@ using LanguageExt; using Newtonsoft.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; namespace WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models; @@ -16,7 +15,6 @@ public record CredentialRequest(Option Proof, Format Format) /// Gets the proof of possession of the key material the issued credential shall be bound to. /// [JsonProperty("proof")] - [JsonConverter(typeof(OptionJsonConverter))] public Option Proof { get; } = Proof; /// diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/MdocStorage.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/MdocStorage.cs index 95cfc647..c28fa2fa 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/MdocStorage.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Implementations/MdocStorage.cs @@ -22,7 +22,7 @@ public MdocStorage(IAgentProvider agentProvider, IWalletRecordService recordServ public async Task Add(MdocRecord record) { var context = await _agentProvider.GetContextAsync(); - await _recordService.AddAsync(context.Wallet, record); + await _recordService.AddAsync(context.Wallet, record, MdocRecordFun.EncodeToJson); return Unit.Default; } @@ -55,7 +55,7 @@ public async Task>> List( public async Task Update(MdocRecord record) { var context = await _agentProvider.GetContextAsync(); - await _recordService.Update(context.Wallet, record); + await _recordService.Update(context.Wallet, record, MdocRecordFun.EncodeToJson); return Unit.Default; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/CredentialIssuerId.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/CredentialIssuerId.cs index 12e1f3a3..9c2cc152 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/CredentialIssuerId.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/CredentialIssuerId.cs @@ -1,15 +1,12 @@ using System.Globalization; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Core.Uri; using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Errors; namespace WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct CredentialIssuerId { private Uri Value { get; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerMetadata.cs index 082c0187..3ffb366b 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerMetadata.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerMetadata.cs @@ -1,8 +1,5 @@ using System.Globalization; -using System.Runtime.Serialization.Formatters.Binary; -using Hyperledger.Aries.Extensions; using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; @@ -12,48 +9,40 @@ using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; +using WalletFramework.Core.Uri; using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; using static WalletFramework.Core.Functional.ValidationFun; using static WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models.AuthorizationServerId; using static WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models.CredentialIssuerId; using static WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models.CredentialConfigurationId; using static WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer.IssuerDisplay; +using static WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models.IssuerMetadataJsonExtensions; namespace WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; /// /// Represents the metadata of an OpenID4VCI Credential Issuer. /// -[JsonConverter(typeof(IssuerMetadataJsonConverter))] public record IssuerMetadata { - // Do not change the order of this property (must be last) otherwise the JSON serialization will not - // work properly... /// /// Gets a dictionary which maps a CredentialConfigurationId to its credential metadata. /// - [JsonProperty(CredentialConfigsSupportedJsonKey)] - [JsonConverter(typeof(DictJsonConverter))] public Dictionary CredentialConfigurationsSupported { get; } /// /// Gets a list of display properties of a Credential Issuer for different languages. /// - [JsonProperty(DisplayJsonKey)] - [JsonConverter(typeof(OptionJsonConverter>))] public Option> Display { get; } /// /// Gets the URL of the Credential Issuer's Credential Endpoint. /// - [JsonProperty(CredentialEndpointJsonKey)] public Uri CredentialEndpoint { get; } /// /// Gets the identifier of the Credential Issuer. /// - [JsonProperty(CredentialIssuerJsonKey)] public CredentialIssuerId CredentialIssuer { get; } /// @@ -63,10 +52,8 @@ public record IssuerMetadata /// identifier is used as the OAuth 2.0 Issuer value to obtain the Authorization Server /// metadata. /// - [JsonProperty(AuthorizationServersJsonKey)] - [JsonConverter(typeof(OptionJsonConverter>))] public Option> AuthorizationServers { get; } - + private IssuerMetadata( Dictionary credentialConfigurationsSupported, Option> display, @@ -165,37 +152,55 @@ from serverIds in jArray .Apply(credentialIssuerId) .Apply(authServers); } - - private const string CredentialConfigsSupportedJsonKey = "credential_configurations_supported"; - private const string DisplayJsonKey = "display"; - private const string CredentialEndpointJsonKey = "credential_endpoint"; - private const string CredentialIssuerJsonKey = "credential_issuer"; - private const string AuthorizationServersJsonKey = "authorization_servers"; } -public sealed class IssuerMetadataJsonConverter : JsonConverter +public static class IssuerMetadataJsonExtensions { - public override bool CanWrite => false; - - public override void WriteJson(JsonWriter writer, IssuerMetadata? value, JsonSerializer serializer) => - throw new NotImplementedException(); - - public override IssuerMetadata ReadJson( - JsonReader reader, - Type objectType, - IssuerMetadata? existingValue, - bool hasExistingValue, - JsonSerializer serializer) + public const string CredentialConfigsSupportedJsonKey = "credential_configurations_supported"; + public const string DisplayJsonKey = "display"; + public const string CredentialEndpointJsonKey = "credential_endpoint"; + public const string CredentialIssuerJsonKey = "credential_issuer"; + public const string AuthorizationServersJsonKey = "authorization_servers"; + + public static JObject EncodeToJson(this IssuerMetadata issuerMetadata) { - var json = JObject.Load(reader); + var result = new JObject(); - var result = IssuerMetadata - .ValidIssuerMetadata(json) - .Match( - metadata => metadata, - errors => - throw new InvalidOperationException($"IssuerMetadata is corrupt. Errors: {errors}") + var configsJson = new JObject(); + foreach (var (key, config) in issuerMetadata.CredentialConfigurationsSupported) + { + var configJson = config.Match( + sdJwt => sdJwt.EncodeToJson(), + mdoc => mdoc.EncodeToJson() ); + + configsJson.Add(key.ToString(), configJson); + } + result.Add(CredentialConfigsSupportedJsonKey, configsJson); + + issuerMetadata.Display.IfSome(displays => + { + var displaysJson = new JArray(); + foreach (var display in displays) + { + displaysJson.Add(display.EncodeToJson()); + } + result.Add(DisplayJsonKey, displaysJson); + }); + + // TODO: ValueTypeEncodeFunc? + result.Add(CredentialEndpointJsonKey, issuerMetadata.CredentialEndpoint.ToStringWithoutTrail()); + result.Add(CredentialIssuerJsonKey, issuerMetadata.CredentialIssuer.ToString()); + + var authServersJson = new JArray(); + issuerMetadata.AuthorizationServers.IfSome(servers => + { + foreach (var server in servers) + { + authServersJson.Add(server.ToString()); + } + }); + result.Add(AuthorizationServersJsonKey, authServersJson); return result; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerName.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerName.cs index 86b59bf3..3c531f0d 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerName.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Models/IssuerName.cs @@ -1,11 +1,8 @@ using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using WalletFramework.Core.Json.Converters; namespace WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct IssuerName { private string Value { get; } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerDisplay.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerDisplay.cs index 9c6ce986..a580a47d 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerDisplay.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerDisplay.cs @@ -1,14 +1,13 @@ using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; using WalletFramework.Core.Localization; using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; using static WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models.IssuerName; using static WalletFramework.Core.Localization.Locale; using static WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer.IssuerLogo; +using static WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer.IssuerDisplayFun; namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; @@ -20,22 +19,16 @@ public record IssuerDisplay /// /// Gets the name of the Issuer /// - [JsonProperty("name")] - [JsonConverter(typeof(OptionJsonConverter))] public Option Name { get; } /// /// Gets the locale which represents the specific culture or region /// - [JsonProperty("locale")] - [JsonConverter(typeof(OptionJsonConverter))] public Option Locale { get; } /// /// Gets the logo of the Issuer /// - [JsonProperty("logo")] - [JsonConverter(typeof(OptionJsonConverter))] public Option Logo { get; } private IssuerDisplay( @@ -50,9 +43,9 @@ private IssuerDisplay( public static Option OptionalIssuerDisplay(JToken display) => display.ToJObject().ToOption().OnSome(jObject => { - var name = jObject.GetByKey("name").ToOption().OnSome(OptionIssuerName); - var locale = jObject.GetByKey("locale").OnSuccess(ValidLocale).ToOption(); - var logo = jObject.GetByKey("logo").ToOption().OnSome(OptionalIssuerLogo); + var name = jObject.GetByKey(NameJsonKey).ToOption().OnSome(OptionIssuerName); + var locale = jObject.GetByKey(LocaleJsonKey).OnSuccess(ValidLocale).ToOption(); + var logo = jObject.GetByKey(LogoJsonKey).ToOption().OnSome(OptionalIssuerLogo); if (name.IsNone && locale.IsNone && logo.IsNone) return Option.None; @@ -60,3 +53,19 @@ public static Option OptionalIssuerDisplay(JToken display) => dis return new IssuerDisplay(name, locale, logo); }); } + +public static class IssuerDisplayFun +{ + public const string NameJsonKey = "name"; + public const string LocaleJsonKey = "locale"; + public const string LogoJsonKey = "logo"; + + public static JObject EncodeToJson(this IssuerDisplay display) + { + var json = new JObject(); + display.Name.IfSome(name => json.Add(NameJsonKey, name.ToString())); + display.Locale.IfSome(locale => json.Add(LocaleJsonKey, locale.ToString())); + display.Logo.IfSome(logo => json.Add(LogoJsonKey, logo.EncodeToJson())); + return json; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerLogo.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerLogo.cs index fa4cf257..dc0c8438 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerLogo.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Issuer/IssuerLogo.cs @@ -1,9 +1,9 @@ using LanguageExt; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; +using WalletFramework.Core.Uri; +using static WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer.IssuerLogoFun; namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; @@ -15,15 +15,11 @@ public record IssuerLogo /// /// Gets the alternate text that describes the logo image. This is typically used for accessibility purposes. /// - [JsonProperty("alt_text")] - [JsonConverter(typeof(OptionJsonConverter))] public Option AltText { get; } /// /// Gets the URL of the logo image. /// - [JsonProperty("uri")] - [JsonConverter(typeof(OptionJsonConverter))] public Option Uri { get; } private IssuerLogo( @@ -36,7 +32,7 @@ private IssuerLogo( public static Option OptionalIssuerLogo(JToken logo) => logo.ToJObject().ToOption().OnSome(jObject => { - var altText = jObject.GetByKey("alt_text").ToOption().OnSome(text => + var altText = jObject.GetByKey(AltTextJsonKey).ToOption().OnSome(text => { var str = text.ToString(); if (string.IsNullOrWhiteSpace(str)) @@ -45,7 +41,7 @@ public static Option OptionalIssuerLogo(JToken logo) => logo.ToJObje return str; }); - var imageUri = jObject.GetByKey("uri").ToOption().OnSome(uri => + var imageUri = jObject.GetByKey(UriJsonKey).ToOption().OnSome(uri => { try { @@ -65,3 +61,17 @@ public static Option OptionalIssuerLogo(JToken logo) => logo.ToJObje return new IssuerLogo(altText, imageUri); }); } + +public static class IssuerLogoFun +{ + public const string AltTextJsonKey = "alt_text"; + public const string UriJsonKey = "uri"; + + public static JObject EncodeToJson(this IssuerLogo logo) + { + var json = new JObject(); + logo.AltText.IfSome(altText => json.Add(AltTextJsonKey, altText)); + logo.Uri.IfSome(uri => json.Add(UriJsonKey, uri.ToStringWithoutTrail())); + return json; + } +} diff --git a/src/WalletFramework.SdJwtVc/Models/Vct.cs b/src/WalletFramework.SdJwtVc/Models/Vct.cs index eee00c2b..6c1a0370 100644 --- a/src/WalletFramework.SdJwtVc/Models/Vct.cs +++ b/src/WalletFramework.SdJwtVc/Models/Vct.cs @@ -1,14 +1,11 @@ using System.Globalization; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Functional.Errors; using WalletFramework.Core.Json; -using WalletFramework.Core.Json.Converters; namespace WalletFramework.SdJwtVc.Models; -[JsonConverter(typeof(ValueTypeJsonConverter))] public readonly struct Vct { private string Value { get; } diff --git a/test/WalletFramework.MdocVc.Tests/MdocRecordTests.cs b/test/WalletFramework.MdocVc.Tests/MdocRecordTests.cs index 32c722f0..cb988ae0 100644 --- a/test/WalletFramework.MdocVc.Tests/MdocRecordTests.cs +++ b/test/WalletFramework.MdocVc.Tests/MdocRecordTests.cs @@ -2,7 +2,6 @@ using Hyperledger.Aries.Storage; using LanguageExt; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.MdocLib; using Xunit; @@ -18,10 +17,10 @@ public void Can_Encode_To_Json() var mdoc = Mdoc.ValidMdoc(encodedMdoc).UnwrapOrThrow(new InvalidOperationException("Mdoc sample is corrupt")); var record = mdoc.ToRecord(Option>.None); - var sut = JObject.FromObject(record); + var sut = record.EncodeToJson(); sut[nameof(RecordBase.Id)]!.ToString().Should().Be(record.Id); - sut[MdocRecordJsonKeys.MdocJsonKey]!.ToString().Should().Be(encodedMdoc); + sut[MdocRecordFun.MdocJsonKey]!.ToString().Should().Be(encodedMdoc); } [Fact] @@ -29,7 +28,7 @@ public void Can_Decode_From_Json() { var json = MdocVcSamples.MdocRecordJson; - var sut = JsonConvert.DeserializeObject(json.ToString())!; + var sut = MdocRecordFun.DecodeFromJson(json); sut.Mdoc.DocType.ToString().Should().Be(MdocLib.Tests.Samples.DocType); } diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs index b5bafb9a..55b3a7f5 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs @@ -53,7 +53,7 @@ public void Can_Encode_To_Json() var record = new AuthFlowSessionRecord(authorizationData, authorizationCodeParameters, sessionId); // Act - var recordSut = JObject.FromObject(record); + var recordSut = record.EncodeToJson(); var tagsSut = JObject.FromObject(record.Tags); // Assert @@ -68,7 +68,7 @@ public void Can_Decode_From_Json() var json = AuthFlowSamples.AuthFlowSessionRecordJson; // Act - var record = JsonConvert.DeserializeObject(json.ToString()); + var record = AuthFlowSessionRecordFun.DecodeFromJson(json); // Assert record.Should().NotBeNull(); diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs index 2bf810b1..030ad6a4 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs @@ -7,16 +7,16 @@ public static class AuthFlowSamples { public static JObject AuthFlowSessionRecordJson => new() { - ["AuthorizationData"] = new JObject + ["authorization_data"] = new JObject { - ["ClientOptions"] = new JObject + ["client_options"] = new JObject { ["ClientId"] = "https://test-issuer.com/redirect", ["WalletIssuer"] = "i can write anything", ["RedirectUri"] = "https://test-issuer.com/redirect" }, - ["IssuerMetadata"] = IssuerMetadataSample.EncodedAsJson, - ["AuthorizationServerMetadata"] = new JObject + ["issuer_metadata"] = IssuerMetadataSample.EncodedAsJson, + ["authorization_server_metadata"] = new JObject { ["issuer"] = "i can write anything", ["token_endpoint"] = "i can write anything", @@ -24,9 +24,9 @@ public static class AuthFlowSamples ["authorization_endpoint"] = "i can write anything", ["response_types_supported"] = new JArray("i can write anything"), }, - ["CredentialConfigurationIds"] = new JArray("org.iso.18013.5.1.mDL") + ["credential_configuration_ids"] = new JArray("org.iso.18013.5.1.mDL") }, - ["AuthorizationCodeParameters"] = new JObject + ["authorization_code_parameters"] = new JObject { ["Challenge"] = "hello", ["CodeChallengeMethod"] = "S256", diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs index 62e8cd54..9f70e9a1 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs @@ -48,9 +48,9 @@ public void Can_Decode_From_Json() [Fact] public void Can_Encode_To_Json() { - var decoded = IssuerMetadataSample.Decoded; + var issuerMetadata = IssuerMetadataSample.Decoded; - var sut = JObject.FromObject(decoded).RemoveNulls().ToObject(); + var sut = issuerMetadata.EncodeToJson(); sut.Should().BeEquivalentTo(IssuerMetadataSample.EncodedAsJson); } @@ -64,21 +64,11 @@ public void Can_Decode_And_Encode_From_Json() // Act ValidIssuerMetadata(sample).Match( // Assert - sut => + issuerMetadata => { - var encoded = JObject.FromObject(sut).RemoveNulls().ToObject(); - encoded.Should().BeEquivalentTo(sample); + var sut = issuerMetadata.EncodeToJson(); + sut.Should().BeEquivalentTo(sample); }, _ => Assert.Fail("IssuerMetadata must be valid")); } - - [Fact] - public void Can_Decode_From_Persisted_Json() - { - var sample = IssuerMetadataSample.EncodedAsJson; - - var sut = JsonConvert.DeserializeObject(sample.ToString())!; - - sut.CredentialIssuer.ToString().Should().Be(IssuerMetadataSample.CredentialIssuer.ToStringWithoutTrail()); - } } From c70be2324c8412d8039b7c416b01086d48534025 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 18 Jul 2024 12:23:51 +0200 Subject: [PATCH 4/5] fix credential request json Signed-off-by: Kevin --- .../CredentialRequestService.cs | 2 +- .../CredRequest/Models/CredentialRequest.cs | 24 +++++++-- .../Models/Mdoc/MdocCredentialRequest.cs | 11 +--- .../Models/SdJwt/SdJwtCredentialRequest.cs | 15 ++---- .../AuthFlow/AuthFlowSessionRecordTests.cs | 2 +- .../AuthFlow/Samples/AuthFlowSamples.cs | 2 +- .../{ => Mdoc}/MdocConfigurationTests.cs | 4 +- .../Mdoc/Samples}/MdocConfigurationSample.cs | 2 +- .../Samples}/SdJwtConfigurationSample.cs | 2 +- .../{ => SdJwt}/SdJwtConfigurationTests.cs | 4 +- .../Oid4Vci/CredOffer/CredentialOfferTests.cs | 2 +- .../Samples/CredentialOfferSample.cs | 2 +- .../CredRequest/CredentialRequestTests.cs | 10 ++++ .../Oid4Vci/Issuer/IssuerMetadataTests.cs | 6 +-- .../Samples/IssuerMetadataSample.cs | 6 +-- .../Oid4Vci/IssuerMetadataTests.cs | 54 ------------------- .../WalletFramework.Oid4Vc.Tests.csproj | 5 -- 17 files changed, 53 insertions(+), 100 deletions(-) rename test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/{ => Mdoc}/MdocConfigurationTests.cs (97%) rename test/WalletFramework.Oid4Vc.Tests/Oid4Vci/{Samples/Mdoc => CredConfiguration/Mdoc/Samples}/MdocConfigurationSample.cs (98%) rename test/WalletFramework.Oid4Vc.Tests/Oid4Vci/{Samples/SdJwt => CredConfiguration/SdJwt/Samples}/SdJwtConfigurationSample.cs (97%) rename test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/{ => SdJwt}/SdJwtConfigurationTests.cs (81%) rename test/WalletFramework.Oid4Vc.Tests/Oid4Vci/{ => CredOffer}/Samples/CredentialOfferSample.cs (95%) create mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredRequest/CredentialRequestTests.cs rename test/WalletFramework.Oid4Vc.Tests/Oid4Vci/{ => Issuer}/Samples/IssuerMetadataSample.cs (91%) delete mode 100644 test/WalletFramework.Oid4Vc.Tests/Oid4Vci/IssuerMetadataTests.cs diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Implementations/CredentialRequestService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Implementations/CredentialRequestService.cs index 733c7738..dd9be71c 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Implementations/CredentialRequestService.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Implementations/CredentialRequestService.cs @@ -86,7 +86,7 @@ async Task> ICredentialRequestService.RequestCred clientOptions); var result = new SdJwtCredentialRequest(vciRequest, sdJwt.Vct); - return result.AsJson(); + return result.EncodeToJson(); }, async mdoc => { diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs index fcb1d2a4..56afadd2 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/CredentialRequest.cs @@ -1,5 +1,5 @@ using LanguageExt; -using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; namespace WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models; @@ -14,12 +14,30 @@ public record CredentialRequest(Option Proof, Format Format) /// /// Gets the proof of possession of the key material the issued credential shall be bound to. /// - [JsonProperty("proof")] public Option Proof { get; } = Proof; /// /// Gets the format of the credential to be issued. /// - [JsonProperty("format")] public Format Format { get; } = Format; } + +public static class CredentialRequestFun +{ + private const string ProofJsonKey = "proof"; + private const string FormatJsonKey = "format"; + + public static JObject EncodeToJson(this CredentialRequest request) + { + var result = new JObject(); + + request.Proof.IfSome(proof => + { + result.Add(ProofJsonKey, JObject.FromObject(proof)); + }); + + result.Add(FormatJsonKey, request.Format.ToString()); + + return result; + } +} diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/Mdoc/MdocCredentialRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/Mdoc/MdocCredentialRequest.cs index 15b9f8cc..060fb544 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/Mdoc/MdocCredentialRequest.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/Mdoc/MdocCredentialRequest.cs @@ -27,16 +27,9 @@ public static class MdocCredentialRequestFun { public static string AsJson(this MdocCredentialRequest request) { - var json = new JObject(); - - var vciRequest = JObject.FromObject(request.VciRequest); - foreach (var property in vciRequest.Properties()) - { - json.Add(property); - } + var json = request.VciRequest.EncodeToJson(); - var vct = JToken.FromObject(request.DocType); - json.Add("doctype", vct); + json.Add("doctype", request.DocType.ToString()); return json.ToString(); } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/SdJwt/SdJwtCredentialRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/SdJwt/SdJwtCredentialRequest.cs index 088c48a9..693e3dca 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/SdJwt/SdJwtCredentialRequest.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/SdJwt/SdJwtCredentialRequest.cs @@ -1,4 +1,3 @@ -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.SdJwtVc.Models; @@ -11,7 +10,6 @@ public record SdJwtCredentialRequest /// /// Gets the verifiable credential type (vct). /// - [JsonProperty("vct")] public Vct Vct { get; } internal SdJwtCredentialRequest(CredentialRequest vciRequest, Vct vct) @@ -23,18 +21,11 @@ internal SdJwtCredentialRequest(CredentialRequest vciRequest, Vct vct) public static class SdJwtCredentialRequestFun { - public static string AsJson(this SdJwtCredentialRequest request) + public static string EncodeToJson(this SdJwtCredentialRequest request) { - var json = new JObject(); - - var vciRequest = JObject.FromObject(request.VciRequest); - foreach (var property in vciRequest.Properties()) - { - json.Add(property); - } + var json = request.VciRequest.EncodeToJson(); - var vct = JToken.FromObject(request.Vct); - json.Add("vct", vct); + json.Add("vct", request.Vct.ToString()); return json.ToString(); } diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs index 55b3a7f5..e69c767a 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/AuthFlowSessionRecordTests.cs @@ -9,7 +9,7 @@ using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; using WalletFramework.Oid4Vc.Tests.Oid4Vci.AuthFlow.Samples; -using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Issuer.Samples; namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.AuthFlow; diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs index 030ad6a4..4cdc1406 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/AuthFlow/Samples/AuthFlowSamples.cs @@ -1,5 +1,5 @@ using Newtonsoft.Json.Linq; -using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Issuer.Samples; namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.AuthFlow.Samples; diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/MdocConfigurationTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/Mdoc/MdocConfigurationTests.cs similarity index 97% rename from test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/MdocConfigurationTests.cs rename to test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/Mdoc/MdocConfigurationTests.cs index 30916c93..51e09c4e 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/MdocConfigurationTests.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/Mdoc/MdocConfigurationTests.cs @@ -1,10 +1,10 @@ using FluentAssertions; using WalletFramework.Core.Functional; using WalletFramework.Core.Json.Errors; -using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.Mdoc; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration.Mdoc.Samples; using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc.MdocConfiguration; -namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration; +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration.Mdoc; public class MdocConfigurationTests { diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/Mdoc/MdocConfigurationSample.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/Mdoc/Samples/MdocConfigurationSample.cs similarity index 98% rename from test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/Mdoc/MdocConfigurationSample.cs rename to test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/Mdoc/Samples/MdocConfigurationSample.cs index 4036c1f7..bad19de3 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/Mdoc/MdocConfigurationSample.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/Mdoc/Samples/MdocConfigurationSample.cs @@ -13,7 +13,7 @@ using static WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Format; using static WalletFramework.MdocLib.NameSpace; -namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.Mdoc; +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration.Mdoc.Samples; public static class MdocConfigurationSample { diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/SdJwt/SdJwtConfigurationSample.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwt/Samples/SdJwtConfigurationSample.cs similarity index 97% rename from test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/SdJwt/SdJwtConfigurationSample.cs rename to test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwt/Samples/SdJwtConfigurationSample.cs index c73b3b20..0c66397a 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/SdJwt/SdJwtConfigurationSample.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwt/Samples/SdJwtConfigurationSample.cs @@ -3,7 +3,7 @@ using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models; using WalletFramework.SdJwtVc.Models; -namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.SdJwt; +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration.SdJwt.Samples; public static class SdJwtConfigurationSample { diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwtConfigurationTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwt/SdJwtConfigurationTests.cs similarity index 81% rename from test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwtConfigurationTests.cs rename to test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwt/SdJwtConfigurationTests.cs index eccf508e..a0c88456 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwtConfigurationTests.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredConfiguration/SdJwt/SdJwtConfigurationTests.cs @@ -1,10 +1,10 @@ -namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration; +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration.SdJwt; public class SdJwtConfigurationTests { + // TODO: Implement this [Fact] public void Can_Parse() { - } } diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredOffer/CredentialOfferTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredOffer/CredentialOfferTests.cs index 1d630c3d..24514f10 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredOffer/CredentialOfferTests.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredOffer/CredentialOfferTests.cs @@ -5,7 +5,7 @@ using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Errors; using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; using static WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models.CredentialOffer; -using static WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.CredentialOfferSample; +using static WalletFramework.Oid4Vc.Tests.Oid4Vci.CredOffer.Samples.CredentialOfferSample; namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredOffer; diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/CredentialOfferSample.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredOffer/Samples/CredentialOfferSample.cs similarity index 95% rename from test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/CredentialOfferSample.cs rename to test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredOffer/Samples/CredentialOfferSample.cs index 6bdd38b2..b1ba9a77 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/CredentialOfferSample.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredOffer/Samples/CredentialOfferSample.cs @@ -1,6 +1,6 @@ using Newtonsoft.Json.Linq; -namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples; +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredOffer.Samples; public static class CredentialOfferSample { diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredRequest/CredentialRequestTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredRequest/CredentialRequestTests.cs new file mode 100644 index 00000000..c0c45ee0 --- /dev/null +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/CredRequest/CredentialRequestTests.cs @@ -0,0 +1,10 @@ +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.CredRequest; + +public class CredentialRequestTests +{ + [Fact] + public void Can_Encode_To_Json() + { + + } +} diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs index 9f70e9a1..5c8ad5e9 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/IssuerMetadataTests.cs @@ -5,9 +5,9 @@ using WalletFramework.Core.Json; using WalletFramework.Core.Uri; using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; -using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples; -using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.Mdoc; -using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.SdJwt; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration.Mdoc.Samples; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration.SdJwt.Samples; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.Issuer.Samples; using static WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models.IssuerMetadata; namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Issuer; diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/IssuerMetadataSample.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/Samples/IssuerMetadataSample.cs similarity index 91% rename from test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/IssuerMetadataSample.cs rename to test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/Samples/IssuerMetadataSample.cs index 6c4ab17c..69442908 100644 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Samples/IssuerMetadataSample.cs +++ b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/Issuer/Samples/IssuerMetadataSample.cs @@ -3,10 +3,10 @@ using WalletFramework.Core.Uri; using WalletFramework.Oid4Vc.Oid4Vci.CredOffer.Models; using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; -using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.Mdoc; -using WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.SdJwt; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration.Mdoc.Samples; +using WalletFramework.Oid4Vc.Tests.Oid4Vci.CredConfiguration.SdJwt.Samples; -namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples; +namespace WalletFramework.Oid4Vc.Tests.Oid4Vci.Issuer.Samples; public static class IssuerMetadataSample { diff --git a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/IssuerMetadataTests.cs b/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/IssuerMetadataTests.cs deleted file mode 100644 index 22d08d58..00000000 --- a/test/WalletFramework.Oid4Vc.Tests/Oid4Vci/IssuerMetadataTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -// using FluentAssertions; -// using Hyperledger.Aries.Extensions; -// using Newtonsoft.Json.Linq; -// using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; -// using static WalletFramework.Oid4Vc.Tests.Oid4Vci.Samples.IssuerMetadataSample; -// -// -// namespace WalletFramework.Oid4Vc.Tests.Oid4Vci -// { -// public class IssuerMetadataTests -// { -// [Fact] -// public void Additional_Or_Unrecognized_Fields_Are_Ignored_During_Deserialization() -// { -// var json = IssuerMetadataJson; -// var jObject = JObject.Parse(json); -// jObject["Additional_Field"] = "Additional_Field"; -// -// var sut = jObject.ToObject(); -// -// sut.Should().BeOfType(); -// sut!.CredentialIssuer.Should().Be(CredentialIssuer); -// sut.CredentialEndpoint.Should().Be(CredentialEndpoint); -// } -// -// [Theory] -// [InlineData("credential_issuer")] -// [InlineData("credential_endpoint")] -// [InlineData("credential_configurations_supported")] -// public void Deserialization_Fails_When_Required_Fields_Are_Missing(string fieldName) -// { -// // Arrange -// var json = IssuerMetadataJson; -// -// var jObject = JObject.Parse(json); -// -// jObject[fieldName] = null; -// -// Assert.Throws(() => jObject.ToObject()); -// } -// -// [Fact] -// public void Valid_Json_Deserializes_To_Model() -// { -// var json = IssuerMetadataJson; -// -// var sut = json.ToObject(); -// -// sut.Should().BeOfType(); -// sut.CredentialIssuer.Should().Be(CredentialIssuer); -// sut.CredentialEndpoint.Should().Be(CredentialEndpoint); -// } -// } -// } diff --git a/test/WalletFramework.Oid4Vc.Tests/WalletFramework.Oid4Vc.Tests.csproj b/test/WalletFramework.Oid4Vc.Tests/WalletFramework.Oid4Vc.Tests.csproj index c8e8b679..9c99ced5 100644 --- a/test/WalletFramework.Oid4Vc.Tests/WalletFramework.Oid4Vc.Tests.csproj +++ b/test/WalletFramework.Oid4Vc.Tests/WalletFramework.Oid4Vc.Tests.csproj @@ -37,9 +37,4 @@ - - - - - From 56259b2c9aaad7d018d3e130ba3c01c77fb87e73 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 19 Jul 2024 13:57:21 +0200 Subject: [PATCH 5/5] remove unused Signed-off-by: Kevin --- src/WalletFramework.Core/Json/JsonSettings.cs | 11 - .../FormattingExtensions.cs | 41 ++- src/WalletFramework.Oid4Vc/IsExternalInit.cs | 13 +- .../Authorization/Models/OAuthToken.cs | 1 - .../Models/SdJwt/SdJwtConfiguration.cs | 1 - .../Models/Mdoc/MdocCredentialRequest.cs | 1 - .../Models/SdJwt/SdJwtCredentialRequest.cs | 1 - .../Oid4VciInvalidGrantException.cs | 23 +- .../Abstractions/IIssuerMetadataService.cs | 1 - .../Implementations/IssuerMetadataService.cs | 2 - .../Oid4Vci/Models/Error/ErrorResponse.cs | 41 ++- .../Credential/Attributes/OidClaim.cs | 53 ++- .../OidCredentialAttributeDisplay.cs | 31 +- .../Credential/OidCredentialDefinition.cs | 31 +- .../Models/Metadata/IssuerMetadataSet.cs | 1 - .../Oid4VpNoCredentialCandidateException.cs | 23 +- .../Extensions/JwtSecurityTokenExtensions.cs | 81 +++-- .../Extensions/X509CertificateExtensions.cs | 105 +++--- .../Oid4Vp/Models/AuthorizationRequest.cs | 311 +++++++++--------- .../Oid4Vp/Models/AuthorizationResponse.cs | 41 ++- .../Models/AuthorizationResponseCallback.cs | 25 +- .../Oid4Vp/Models/ClientIdScheme.cs | 117 ++++--- .../Oid4Vp/Models/ClientMetadata.cs | 53 ++- .../Oid4Vp/Models/CredentialCandidates.cs | 59 ++-- .../Models/HaipAuthorizationRequestUri.cs | 63 ++-- .../Oid4Vp/Models/OidPresentationRecord.cs | 125 ++++--- .../Oid4Vp/Models/PresentedClaim.cs | 19 +- .../Oid4Vp/Models/PresentedCredential.cs | 27 +- .../Oid4Vp/Models/SelectedCredential.cs | 27 +- .../PresentationExchange/Models/Descriptor.cs | 53 ++- .../Models/DescriptorMap.cs | 53 ++- .../PresentationExchange/Models/Format.cs | 31 +- .../Models/InputDescriptor.cs | 175 +++++----- .../Models/PresentationDefinition.cs | 103 +++--- .../Models/PresentationSubmission.cs | 41 ++- .../Models/SubmissionRequirement.cs | 101 +++--- .../Services/IPexService.cs | 43 ++- .../Services/PexService.cs | 145 ++++---- .../Oid4Vp/Services/IOid4VpClientService.cs | 51 ++- .../Oid4Vp/Services/IOid4VpRecordService.cs | 105 +++--- .../Oid4Vp/Services/Oid4VpHaipClient.cs | 183 +++++------ .../Oid4Vp/Services/Oid4VpRecordService.cs | 105 +++--- .../SeviceCollectionExtensions.cs | 1 - 43 files changed, 1231 insertions(+), 1287 deletions(-) delete mode 100644 src/WalletFramework.Core/Json/JsonSettings.cs diff --git a/src/WalletFramework.Core/Json/JsonSettings.cs b/src/WalletFramework.Core/Json/JsonSettings.cs deleted file mode 100644 index 71f97261..00000000 --- a/src/WalletFramework.Core/Json/JsonSettings.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Newtonsoft.Json; - -namespace WalletFramework.Core.Json; - -public static class JsonSettings -{ - public static JsonSerializerSettings SerializerSettings => new() - { - NullValueHandling = NullValueHandling.Ignore - }; -} diff --git a/src/WalletFramework.Oid4Vc/FormattingExtensions.cs b/src/WalletFramework.Oid4Vc/FormattingExtensions.cs index 142148b6..e8c5a7a9 100644 --- a/src/WalletFramework.Oid4Vc/FormattingExtensions.cs +++ b/src/WalletFramework.Oid4Vc/FormattingExtensions.cs @@ -1,27 +1,26 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc +namespace WalletFramework.Oid4Vc; + +/// +/// Formatting extensions +/// +public static class FormattingExtensions { /// - /// Formatting extensions + /// Converts an to json string using default converter. /// - public static class FormattingExtensions - { - /// - /// Converts an to json string using default converter. - /// - /// The object. - /// - public static string ToJson(this object obj) => - JsonConvert.SerializeObject(obj, Formatting.None); + /// The object. + /// + public static string ToJson(this object obj) => + JsonConvert.SerializeObject(obj, Formatting.None); - /// - /// Converts an object to json string using the provided - /// - /// The json. - /// Object. - /// SerializerSettings. - public static string ToJson(this object obj, JsonSerializerSettings settings) => - JsonConvert.SerializeObject(obj, settings); - } -} + /// + /// Converts an object to json string using the provided + /// + /// The json. + /// Object. + /// SerializerSettings. + public static string ToJson(this object obj, JsonSerializerSettings settings) => + JsonConvert.SerializeObject(obj, settings); +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/IsExternalInit.cs b/src/WalletFramework.Oid4Vc/IsExternalInit.cs index 225b25ce..17db38c8 100644 --- a/src/WalletFramework.Oid4Vc/IsExternalInit.cs +++ b/src/WalletFramework.Oid4Vc/IsExternalInit.cs @@ -1,9 +1,8 @@ // ReSharper disable once CheckNamespace -namespace System.Runtime.CompilerServices +namespace System.Runtime.CompilerServices; + +// This is needed for the init property setter. This can be removed when updating to a newer C# Version. +// https://stackoverflow.com/questions/64749385/predefined-type-system-runtime-compilerservices-isexternalinit-is-not-defined +internal static class IsExternalInit { - // This is needed for the init property setter. This can be removed when updating to a newer C# Version. - // https://stackoverflow.com/questions/64749385/predefined-type-system-runtime-compilerservices-isexternalinit-is-not-defined - internal static class IsExternalInit - { - } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/OAuthToken.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/OAuthToken.cs index b55c1d24..f8b86ae2 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/OAuthToken.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Authorization/Models/OAuthToken.cs @@ -1,5 +1,4 @@ using Newtonsoft.Json; -using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow; using WalletFramework.Oid4Vc.Oid4Vci.AuthFlow.Models; namespace WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SdJwt/SdJwtConfiguration.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SdJwt/SdJwtConfiguration.cs index 3e87132e..94fad8fd 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SdJwt/SdJwtConfiguration.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredConfiguration/Models/SdJwt/SdJwtConfiguration.cs @@ -1,4 +1,3 @@ -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using WalletFramework.Core.Functional; using WalletFramework.Core.Json; diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/Mdoc/MdocCredentialRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/Mdoc/MdocCredentialRequest.cs index 060fb544..9795247c 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/Mdoc/MdocCredentialRequest.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/Mdoc/MdocCredentialRequest.cs @@ -1,5 +1,4 @@ using LanguageExt; -using Newtonsoft.Json.Linq; using WalletFramework.MdocLib; using WalletFramework.Oid4Vc.Oid4Vci.CredConfiguration.Models.Mdoc; diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/SdJwt/SdJwtCredentialRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/SdJwt/SdJwtCredentialRequest.cs index 693e3dca..6f9dcff9 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/SdJwt/SdJwtCredentialRequest.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/CredRequest/Models/SdJwt/SdJwtCredentialRequest.cs @@ -1,4 +1,3 @@ -using Newtonsoft.Json.Linq; using WalletFramework.SdJwtVc.Models; namespace WalletFramework.Oid4Vc.Oid4Vci.CredRequest.Models.SdJwt; diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Exceptions/Oid4VciInvalidGrantException.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Exceptions/Oid4VciInvalidGrantException.cs index f35d7ade..156c83e6 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Exceptions/Oid4VciInvalidGrantException.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Exceptions/Oid4VciInvalidGrantException.cs @@ -1,19 +1,18 @@ using System.Net; -namespace WalletFramework.Oid4Vc.Oid4Vci.Exceptions +namespace WalletFramework.Oid4Vc.Oid4Vci.Exceptions; + +/// +/// Represents an exception thrown when the grant within the Oid4Vci flow is invalid (e.g. wrong tx_code). +/// +public class Oid4VciInvalidGrantException : Exception { /// - /// Represents an exception thrown when the grant within the Oid4Vci flow is invalid (e.g. wrong tx_code). + /// Initializes a new instance of the class. /// - public class Oid4VciInvalidGrantException : Exception + /// The StatusCode associated with the thrown Exception + public Oid4VciInvalidGrantException(HttpStatusCode statusCode) + : base($"Invalid grant error. Status Code is {statusCode}") { - /// - /// Initializes a new instance of the class. - /// - /// The StatusCode associated with the thrown Exception - public Oid4VciInvalidGrantException(HttpStatusCode statusCode) - : base($"Invalid grant error. Status Code is {statusCode}") - { - } } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Abstractions/IIssuerMetadataService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Abstractions/IIssuerMetadataService.cs index 15dc5b60..c30ead00 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Abstractions/IIssuerMetadataService.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Abstractions/IIssuerMetadataService.cs @@ -1,7 +1,6 @@ using WalletFramework.Core.Functional; using WalletFramework.Core.Localization; using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; -using WalletFramework.Oid4Vc.Oid4Vci.Models; namespace WalletFramework.Oid4Vc.Oid4Vci.Issuer.Abstractions; diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Implementations/IssuerMetadataService.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Implementations/IssuerMetadataService.cs index fb79327d..9655f223 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Implementations/IssuerMetadataService.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Issuer/Implementations/IssuerMetadataService.cs @@ -2,8 +2,6 @@ using WalletFramework.Core.Localization; using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Abstractions; using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; -using WalletFramework.Oid4Vc.Oid4Vci.Models; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; using static WalletFramework.Core.Json.JsonFun; using static WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models.IssuerMetadata; diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Error/ErrorResponse.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Error/ErrorResponse.cs index 1f9a42e5..b16b5c4e 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Error/ErrorResponse.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Error/ErrorResponse.cs @@ -1,28 +1,27 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Error +namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Error; + +/// +/// Represents an error response when the request is invalid or unauthorized. +/// +public class ErrorResponse { /// - /// Represents an error response when the request is invalid or unauthorized. + /// Gets or sets the error code indicating the type of error that occurred. /// - public class ErrorResponse - { - /// - /// Gets or sets the error code indicating the type of error that occurred. - /// - [JsonProperty("error")] - public string Error { get; set; } + [JsonProperty("error")] + public string Error { get; set; } - /// - /// Gets or sets the human-readable text providing additional information about the error. - /// - [JsonProperty("error_description")] - public string ErrorDescription { get; set; } + /// + /// Gets or sets the human-readable text providing additional information about the error. + /// + [JsonProperty("error_description")] + public string ErrorDescription { get; set; } - /// - /// Gets or sets the URI identifying a human-readable web page with information about the error. - /// - [JsonProperty("error_uri")] - public string ErrorUri { get; set; } - } -} + /// + /// Gets or sets the URI identifying a human-readable web page with information about the error. + /// + [JsonProperty("error_uri")] + public string ErrorUri { get; set; } +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/Attributes/OidClaim.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/Attributes/OidClaim.cs index cedb7103..b39b5146 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/Attributes/OidClaim.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/Attributes/OidClaim.cs @@ -1,34 +1,33 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential.Attributes +namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential.Attributes; + +/// +/// Represents the specifics about a claim. +/// +public class OidClaim { /// - /// Represents the specifics about a claim. + /// Gets or sets the list of display properties associated with a specific credential attribute. /// - public class OidClaim - { - /// - /// Gets or sets the list of display properties associated with a specific credential attribute. - /// - /// - /// The list of display properties. Each display property provides information on how the credential attribute should - /// be displayed. - /// - [JsonProperty("display", NullValueHandling = NullValueHandling.Ignore)] - public List? Display { get; set; } + /// + /// The list of display properties. Each display property provides information on how the credential attribute should + /// be displayed. + /// + [JsonProperty("display", NullValueHandling = NullValueHandling.Ignore)] + public List? Display { get; set; } - /// - /// String value determining type of value of the claim. A non-exhaustive list of valid values defined by this - /// specification are string, number, and image media types such as image/jpeg. - /// - [JsonProperty("value_type", NullValueHandling = NullValueHandling.Ignore)] - public string? ValueType { get; set; } + /// + /// String value determining type of value of the claim. A non-exhaustive list of valid values defined by this + /// specification are string, number, and image media types such as image/jpeg. + /// + [JsonProperty("value_type", NullValueHandling = NullValueHandling.Ignore)] + public string? ValueType { get; set; } - /// - /// String value determining type of value of the claim. A non-exhaustive list of valid values defined by this - /// specification are string, number, and image media types such as image/jpeg. - /// - [JsonProperty("mandatory", NullValueHandling = NullValueHandling.Ignore)] - public string? Mandatory { get; set; } - } -} + /// + /// String value determining type of value of the claim. A non-exhaustive list of valid values defined by this + /// specification are string, number, and image media types such as image/jpeg. + /// + [JsonProperty("mandatory", NullValueHandling = NullValueHandling.Ignore)] + public string? Mandatory { get; set; } +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/Attributes/OidCredentialAttributeDisplay.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/Attributes/OidCredentialAttributeDisplay.cs index 4ec3d19c..fe2f0ff9 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/Attributes/OidCredentialAttributeDisplay.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/Attributes/OidCredentialAttributeDisplay.cs @@ -1,22 +1,21 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential.Attributes +namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential.Attributes; + +/// +/// Represents the visual representations for the credential attribute. +/// +public class OidCredentialAttributeDisplay { /// - /// Represents the visual representations for the credential attribute. + /// Gets or sets the name for the credential attribute. /// - public class OidCredentialAttributeDisplay - { - /// - /// Gets or sets the name for the credential attribute. - /// - [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] - public string? Name { get; set; } + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string? Name { get; set; } - /// - /// Gets or sets the locale, which represents the specific culture or region. - /// - [JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)] - public string? Locale { get; set; } - } -} + /// + /// Gets or sets the locale, which represents the specific culture or region. + /// + [JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)] + public string? Locale { get; set; } +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialDefinition.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialDefinition.cs index 04ab096c..1baa11e5 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialDefinition.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/Credential/OidCredentialDefinition.cs @@ -1,23 +1,22 @@ using Newtonsoft.Json; using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential.Attributes; -namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential +namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Credential; + +/// +/// Represents the detailed description of the credential type. +/// +public class OidCredentialDefinition { /// - /// Represents the detailed description of the credential type. + /// Gets or sets the verifiable credential type (vct). /// - public class OidCredentialDefinition - { - /// - /// Gets or sets the verifiable credential type (vct). - /// - [JsonProperty("vct")] - public string Vct { get; set; } = null!; + [JsonProperty("vct")] + public string Vct { get; set; } = null!; - /// - /// Gets or sets the dictionary representing the attributes of the credential in different languages. - /// - [JsonProperty("claims")] - public Dictionary? Claims { get; set; } - } -} + /// + /// Gets or sets the dictionary representing the attributes of the credential in different languages. + /// + [JsonProperty("claims")] + public Dictionary? Claims { get; set; } +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/IssuerMetadataSet.cs b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/IssuerMetadataSet.cs index 405f665f..1ea3f8dd 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/IssuerMetadataSet.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vci/Models/Metadata/IssuerMetadataSet.cs @@ -1,6 +1,5 @@ using WalletFramework.Oid4Vc.Oid4Vci.Authorization.Models; using WalletFramework.Oid4Vc.Oid4Vci.Issuer.Models; -using WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata.Issuer; namespace WalletFramework.Oid4Vc.Oid4Vci.Models.Metadata; diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Exceptions/Oid4VpNoCredentialCandidateException.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Exceptions/Oid4VpNoCredentialCandidateException.cs index b46415f4..7f31e96c 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Exceptions/Oid4VpNoCredentialCandidateException.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Exceptions/Oid4VpNoCredentialCandidateException.cs @@ -1,17 +1,16 @@ -namespace WalletFramework.Oid4Vc.Oid4Vp.Exceptions +namespace WalletFramework.Oid4Vc.Oid4Vp.Exceptions; + +/// +/// Represents an exception thrown when no credential candidate is found that can be used to answer an +/// Authorization Request during the Oid4Vp flow. +/// +public class Oid4VpNoCredentialCandidateException : Exception { /// - /// Represents an exception thrown when no credential candidate is found that can be used to answer an - /// Authorization Request during the Oid4Vp flow. + /// Initializes a new instance of the class. /// - public class Oid4VpNoCredentialCandidateException : Exception + public Oid4VpNoCredentialCandidateException() + : base("No suitable credential candidates found") { - /// - /// Initializes a new instance of the class. - /// - public Oid4VpNoCredentialCandidateException() - : base("No suitable credential candidates found") - { - } } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Extensions/JwtSecurityTokenExtensions.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Extensions/JwtSecurityTokenExtensions.cs index bb85964c..64c9bfa7 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Extensions/JwtSecurityTokenExtensions.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Extensions/JwtSecurityTokenExtensions.cs @@ -7,54 +7,53 @@ using static Org.BouncyCastle.Security.SignerUtilities; using static System.Text.Encoding; -namespace WalletFramework.Oid4Vc.Oid4Vp.Extensions +namespace WalletFramework.Oid4Vc.Oid4Vp.Extensions; + +/// +/// Extension methods for . +/// +public static class JwtSecurityTokenExtensions { /// - /// Extension methods for . + /// Validates the signature of the JWT token using the provided public key. /// - public static class JwtSecurityTokenExtensions + /// The JWT token to validate. + /// The public key to use for validation. + /// True if the signature is valid, otherwise false. + public static bool IsSignatureValid(this JwtSecurityToken token, AsymmetricKeyParameter publicKeyParameters) { - /// - /// Validates the signature of the JWT token using the provided public key. - /// - /// The JWT token to validate. - /// The public key to use for validation. - /// True if the signature is valid, otherwise false. - public static bool IsSignatureValid(this JwtSecurityToken token, AsymmetricKeyParameter publicKeyParameters) - { - var encodedHeaderAndPayload = UTF8.GetBytes(token.EncodedHeader + "." + token.EncodedPayload); + var encodedHeaderAndPayload = UTF8.GetBytes(token.EncodedHeader + "." + token.EncodedPayload); - switch (publicKeyParameters) - { - case RsaKeyParameters: - var rsaSigner = GetSigner("SHA-256withRSA"); - rsaSigner.Init(false, publicKeyParameters); - rsaSigner.BlockUpdate(encodedHeaderAndPayload, 0, encodedHeaderAndPayload.Length); - return rsaSigner.VerifySignature(DecodeBytes(token.RawSignature)); - case ECPublicKeyParameters: - var ecdsaSigner = GetSigner("SHA-256withECDSA"); - ecdsaSigner.Init(false, publicKeyParameters); - ecdsaSigner.BlockUpdate(encodedHeaderAndPayload, 0, encodedHeaderAndPayload.Length); - return ecdsaSigner.VerifySignature(ConvertRawToDerFormat(DecodeBytes(token.RawSignature))); - default: - throw new InvalidOperationException("Unsupported public key type"); - } - } - - private static byte[] ConvertRawToDerFormat(byte[] rawSignature) + switch (publicKeyParameters) { - if (rawSignature.Length != 64) - throw new ArgumentException("Raw signature should be 64 bytes long", nameof(rawSignature)); + case RsaKeyParameters: + var rsaSigner = GetSigner("SHA-256withRSA"); + rsaSigner.Init(false, publicKeyParameters); + rsaSigner.BlockUpdate(encodedHeaderAndPayload, 0, encodedHeaderAndPayload.Length); + return rsaSigner.VerifySignature(DecodeBytes(token.RawSignature)); + case ECPublicKeyParameters: + var ecdsaSigner = GetSigner("SHA-256withECDSA"); + ecdsaSigner.Init(false, publicKeyParameters); + ecdsaSigner.BlockUpdate(encodedHeaderAndPayload, 0, encodedHeaderAndPayload.Length); + return ecdsaSigner.VerifySignature(ConvertRawToDerFormat(DecodeBytes(token.RawSignature))); + default: + throw new InvalidOperationException("Unsupported public key type"); + } + } - var r = new BigInteger(1, rawSignature.Take(32).ToArray()); - var s = new BigInteger(1, rawSignature.Skip(32).ToArray()); + private static byte[] ConvertRawToDerFormat(byte[] rawSignature) + { + if (rawSignature.Length != 64) + throw new ArgumentException("Raw signature should be 64 bytes long", nameof(rawSignature)); - var derSignature = new DerSequence( - new DerInteger(r), - new DerInteger(s) - ).GetDerEncoded(); + var r = new BigInteger(1, rawSignature.Take(32).ToArray()); + var s = new BigInteger(1, rawSignature.Skip(32).ToArray()); - return derSignature; - } + var derSignature = new DerSequence( + new DerInteger(r), + new DerInteger(s) + ).GetDerEncoded(); + + return derSignature; } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Extensions/X509CertificateExtensions.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Extensions/X509CertificateExtensions.cs index 48971463..d08538ea 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Extensions/X509CertificateExtensions.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Extensions/X509CertificateExtensions.cs @@ -1,73 +1,70 @@ using Org.BouncyCastle.Utilities.Collections; -using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Pkix; using Org.BouncyCastle.X509; using Org.BouncyCastle.X509.Store; +namespace WalletFramework.Oid4Vc.Oid4Vp.Extensions; -namespace WalletFramework.Oid4Vc.Oid4Vp.Extensions +/// +/// Extension methods for . +/// +public static class X509CertificateExtensions { /// - /// Extension methods for . + /// Validates the trust chain of the certificate. /// - public static class X509CertificateExtensions + /// The trust chain to validate. + /// True if the trust chain is valid, otherwise false. + public static bool IsTrustChainValid(this List trustChain) { - /// - /// Validates the trust chain of the certificate. - /// - /// The trust chain to validate. - /// True if the trust chain is valid, otherwise false. - public static bool IsTrustChainValid(this List trustChain) - { - var leafCert = trustChain.First(); + var leafCert = trustChain.First(); - var rootCerts = - new HashSet( - trustChain - .Skip(1) - .Where(cert => cert.IssuerDN.Equivalent(cert.SubjectDN)) - .Select(cert => new TrustAnchor(cert, null)) - .ToList() - ); + var rootCerts = + new HashSet( + trustChain + .Skip(1) + .Where(cert => cert.IssuerDN.Equivalent(cert.SubjectDN)) + .Select(cert => new TrustAnchor(cert, null)) + .ToList() + ); - var intermediateCerts = - new HashSet( - trustChain - .Skip(1) - .Where(cert => !cert.IssuerDN.Equivalent(cert.SubjectDN)) - .Append(leafCert) - ); + var intermediateCerts = + new HashSet( + trustChain + .Skip(1) + .Where(cert => !cert.IssuerDN.Equivalent(cert.SubjectDN)) + .Append(leafCert) + ); - var builderParams = - new PkixBuilderParameters( - rootCerts, - new X509CertStoreSelector - { - Certificate = leafCert - } - ) + var builderParams = + new PkixBuilderParameters( + rootCerts, + new X509CertStoreSelector { - //TODO: Check if CRLs (Certificate Revocation Lists) are valid - IsRevocationEnabled = false - }; + Certificate = leafCert + } + ) + { + //TODO: Check if CRLs (Certificate Revocation Lists) are valid + IsRevocationEnabled = false + }; - builderParams.AddStore( - X509StoreFactory.Create( - "Certificate/Collection", - new X509CollectionStoreParameters(intermediateCerts) - ) - ); + builderParams.AddStore( + X509StoreFactory.Create( + "Certificate/Collection", + new X509CollectionStoreParameters(intermediateCerts) + ) + ); - // This throws if validation fails - new PkixCertPathValidator() - .Validate( - new PkixCertPathBuilder() - .Build(builderParams) - .CertPath, - builderParams - ); + // This throws if validation fails + new PkixCertPathValidator() + .Validate( + new PkixCertPathBuilder() + .Build(builderParams) + .CertPath, + builderParams + ); - return true; - } + return true; } } diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationRequest.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationRequest.cs index 918251eb..da0ba48a 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationRequest.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationRequest.cs @@ -4,170 +4,169 @@ using WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models; using static WalletFramework.Oid4Vc.Oid4Vp.Models.ClientIdScheme; -namespace WalletFramework.Oid4Vc.Oid4Vp.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.Models; + +/// +/// Represents the Request of a Verifier to a Holder within the OpenId4VP specification. +/// +public record AuthorizationRequest { + private const string DirectPost = "direct_post"; + + private const string VpToken = "vp_token"; + + /// + /// Gets the client id scheme. + /// + [JsonProperty("client_id_scheme")] + public ClientIdScheme ClientIdScheme { get; } + + /// + /// Gets the presentation definition. Contains the claims that the Verifier wants to receive. + /// + [JsonProperty("presentation_definition")] + public PresentationDefinition PresentationDefinition { get; } + + /// + /// Gets the client id. The Identifier of the Verifier. + /// + [JsonProperty("client_id")] + public string ClientId { get; } + + /// + /// Gets the nonce. Random string for session binding. + /// + [JsonProperty("nonce")] + public string Nonce { get; } + + /// + /// Gets the response mode. Determines where to send the Authorization Response to. + /// + [JsonProperty("response_uri")] + public string ResponseUri { get; } + + /// + /// Gets the client metadata. Contains the Verifier metadata. + /// + [JsonProperty("client_metadata")] + public ClientMetadata? ClientMetadata { get; init; } + + /// + /// Gets the client metadata uri. Can be used to retrieve the verifier metadata. + /// + [JsonProperty("client_metadata_uri")] + public string? ClientMetadataUri { get; } + + /// + /// The scope of the request. + /// + [JsonProperty("scope")] + public string? Scope { get; } + /// - /// Represents the Request of a Verifier to a Holder within the OpenId4VP specification. + /// Gets the state. /// - public record AuthorizationRequest + [JsonProperty("state")] + public string? State { get; } + + /// + /// The X509 certificate of the verifier, this property is only set when ClientIDScheme is X509SanDNS. + /// + public X509Certificate2? X509Certificate { get; init; } + + /// + /// The trust chain of the verifier, this property is only set when ClientIDScheme is X509SanDNS. + /// + public X509Chain? X509TrustChain { get; init; } + + [JsonConstructor] + private AuthorizationRequest( + ClientIdScheme clientIdScheme, + PresentationDefinition presentationDefinition, + string clientId, + string nonce, + string responseUri, + ClientMetadata? clientMetadata, + string? clientMetadataUri, + string? scope, + string? state) { - private const string DirectPost = "direct_post"; - - private const string VpToken = "vp_token"; - - /// - /// Gets the client id scheme. - /// - [JsonProperty("client_id_scheme")] - public ClientIdScheme ClientIdScheme { get; } - - /// - /// Gets the presentation definition. Contains the claims that the Verifier wants to receive. - /// - [JsonProperty("presentation_definition")] - public PresentationDefinition PresentationDefinition { get; } - - /// - /// Gets the client id. The Identifier of the Verifier. - /// - [JsonProperty("client_id")] - public string ClientId { get; } - - /// - /// Gets the nonce. Random string for session binding. - /// - [JsonProperty("nonce")] - public string Nonce { get; } - - /// - /// Gets the response mode. Determines where to send the Authorization Response to. - /// - [JsonProperty("response_uri")] - public string ResponseUri { get; } - - /// - /// Gets the client metadata. Contains the Verifier metadata. - /// - [JsonProperty("client_metadata")] - public ClientMetadata? ClientMetadata { get; init; } - - /// - /// Gets the client metadata uri. Can be used to retrieve the verifier metadata. - /// - [JsonProperty("client_metadata_uri")] - public string? ClientMetadataUri { get; } - - /// - /// The scope of the request. - /// - [JsonProperty("scope")] - public string? Scope { get; } - - /// - /// Gets the state. - /// - [JsonProperty("state")] - public string? State { get; } - - /// - /// The X509 certificate of the verifier, this property is only set when ClientIDScheme is X509SanDNS. - /// - public X509Certificate2? X509Certificate { get; init; } - - /// - /// The trust chain of the verifier, this property is only set when ClientIDScheme is X509SanDNS. - /// - public X509Chain? X509TrustChain { get; init; } - - [JsonConstructor] - private AuthorizationRequest( - ClientIdScheme clientIdScheme, - PresentationDefinition presentationDefinition, - string clientId, - string nonce, - string responseUri, - ClientMetadata? clientMetadata, - string? clientMetadataUri, - string? scope, - string? state) - { - ClientId = clientId; - ClientIdScheme = clientIdScheme; - ClientMetadata = clientMetadata; - ClientMetadataUri = clientMetadataUri; - Nonce = nonce; - PresentationDefinition = presentationDefinition; - ResponseUri = responseUri; - Scope = scope; - State = state; - } + ClientId = clientId; + ClientIdScheme = clientIdScheme; + ClientMetadata = clientMetadata; + ClientMetadataUri = clientMetadataUri; + Nonce = nonce; + PresentationDefinition = presentationDefinition; + ResponseUri = responseUri; + Scope = scope; + State = state; + } - /// - /// Creates a new instance of the class. - /// - /// The json representation of the authorization request. - /// A new instance of the class. - /// Thrown when the request does not match the HAIP. - public static AuthorizationRequest CreateAuthorizationRequest(string authorizationRequestJson) - => CreateAuthorizationRequest(JObject.Parse(authorizationRequestJson)); - - private static AuthorizationRequest CreateAuthorizationRequest(JObject authorizationRequestJson) => - IsHaipConform(authorizationRequestJson) - ? authorizationRequestJson.ToObject() - ?? throw new InvalidOperationException("Could not parse the Authorization Request") - : throw new InvalidOperationException( - "Invalid Authorization Request. The request does not match the HAIP" - ); - - private static bool IsHaipConform(JObject authorizationRequestJson) - { - var responseType = authorizationRequestJson["response_type"]!.ToString(); - var responseUri = authorizationRequestJson["response_uri"]!.ToString(); - var responseMode = authorizationRequestJson["response_mode"]!.ToString(); - var redirectUri = authorizationRequestJson["redirect_uri"]; - var clientIdScheme = authorizationRequestJson["client_id_scheme"]!.ToString(); - var clientId = authorizationRequestJson["client_id"]!.ToString(); - - return - responseType == VpToken - && responseMode == DirectPost - && !string.IsNullOrEmpty(responseUri) - && redirectUri is null - && (clientIdScheme is X509SanDnsScheme or VerifierAttestationScheme - || (clientIdScheme is RedirectUriScheme && clientId == responseUri)); - } + /// + /// Creates a new instance of the class. + /// + /// The json representation of the authorization request. + /// A new instance of the class. + /// Thrown when the request does not match the HAIP. + public static AuthorizationRequest CreateAuthorizationRequest(string authorizationRequestJson) + => CreateAuthorizationRequest(JObject.Parse(authorizationRequestJson)); + + private static AuthorizationRequest CreateAuthorizationRequest(JObject authorizationRequestJson) => + IsHaipConform(authorizationRequestJson) + ? authorizationRequestJson.ToObject() + ?? throw new InvalidOperationException("Could not parse the Authorization Request") + : throw new InvalidOperationException( + "Invalid Authorization Request. The request does not match the HAIP" + ); + + private static bool IsHaipConform(JObject authorizationRequestJson) + { + var responseType = authorizationRequestJson["response_type"]!.ToString(); + var responseUri = authorizationRequestJson["response_uri"]!.ToString(); + var responseMode = authorizationRequestJson["response_mode"]!.ToString(); + var redirectUri = authorizationRequestJson["redirect_uri"]; + var clientIdScheme = authorizationRequestJson["client_id_scheme"]!.ToString(); + var clientId = authorizationRequestJson["client_id"]!.ToString(); + + return + responseType == VpToken + && responseMode == DirectPost + && !string.IsNullOrEmpty(responseUri) + && redirectUri is null + && (clientIdScheme is X509SanDnsScheme or VerifierAttestationScheme + || (clientIdScheme is RedirectUriScheme && clientId == responseUri)); } +} - internal static class AuthorizationRequestExtensions +internal static class AuthorizationRequestExtensions +{ + internal static AuthorizationRequest WithX509( + this AuthorizationRequest authorizationRequest, + RequestObject requestObject) { - internal static AuthorizationRequest WithX509( - this AuthorizationRequest authorizationRequest, - RequestObject requestObject) + var encodedCertificate = requestObject.GetLeafCertificate().GetEncoded(); + + var certificates = + requestObject + .GetCertificates() + .Select(x => x.GetEncoded()) + .Select(x => new X509Certificate2(x)); + + var trustChain = new X509Chain(); + foreach (var element in certificates) { - var encodedCertificate = requestObject.GetLeafCertificate().GetEncoded(); - - var certificates = - requestObject - .GetCertificates() - .Select(x => x.GetEncoded()) - .Select(x => new X509Certificate2(x)); - - var trustChain = new X509Chain(); - foreach (var element in certificates) - { - trustChain.ChainPolicy.ExtraStore.Add(element); - } - - return authorizationRequest with - { - X509Certificate = new X509Certificate2(encodedCertificate), - X509TrustChain = trustChain - }; + trustChain.ChainPolicy.ExtraStore.Add(element); } - - internal static AuthorizationRequest WithClientMetadata( - this AuthorizationRequest authorizationRequest, - ClientMetadata? clientMetadata) - => authorizationRequest with { ClientMetadata = clientMetadata }; + + return authorizationRequest with + { + X509Certificate = new X509Certificate2(encodedCertificate), + X509TrustChain = trustChain + }; } -} + + internal static AuthorizationRequest WithClientMetadata( + this AuthorizationRequest authorizationRequest, + ClientMetadata? clientMetadata) + => authorizationRequest with { ClientMetadata = clientMetadata }; +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationResponse.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationResponse.cs index a90e1fcc..3c12680d 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationResponse.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationResponse.cs @@ -1,28 +1,27 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vp.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.Models; + +/// +/// Represents an OpenID4VP Authorization Response. +/// +public class AuthorizationResponse { /// - /// Represents an OpenID4VP Authorization Response. + /// Gets or sets the VP Token. /// - public class AuthorizationResponse - { - /// - /// Gets or sets the VP Token. - /// - [JsonProperty ("vp_token")] - public string VpToken { get; set; } = null!; + [JsonProperty ("vp_token")] + public string VpToken { get; set; } = null!; - /// - /// Gets or sets the Presentation Submission. - /// - [JsonProperty ("presentation_submission"), ] - public string PresentationSubmission { get; set; } = null!; + /// + /// Gets or sets the Presentation Submission. + /// + [JsonProperty ("presentation_submission"), ] + public string PresentationSubmission { get; set; } = null!; - /// - /// Gets or sets the State. - /// - [JsonProperty ("state", NullValueHandling = NullValueHandling.Ignore)] - public string? State { get; set; } = null!; - } -} + /// + /// Gets or sets the State. + /// + [JsonProperty ("state", NullValueHandling = NullValueHandling.Ignore)] + public string? State { get; set; } = null!; +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationResponseCallback.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationResponseCallback.cs index 996585be..cee712e3 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationResponseCallback.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/AuthorizationResponseCallback.cs @@ -1,20 +1,19 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vp.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.Models; + +internal record AuthorizationResponseCallback { - internal record AuthorizationResponseCallback - { - [JsonProperty("redirect_uri")] - private Uri? RedirectUri { get; } + [JsonProperty("redirect_uri")] + private Uri? RedirectUri { get; } - public static implicit operator Uri? (AuthorizationResponseCallback? response) => response?.RedirectUri; + public static implicit operator Uri? (AuthorizationResponseCallback? response) => response?.RedirectUri; - public static implicit operator AuthorizationResponseCallback (Uri redirectUri) => new (redirectUri); + public static implicit operator AuthorizationResponseCallback (Uri redirectUri) => new (redirectUri); - [JsonConstructor] - private AuthorizationResponseCallback(Uri redirectUri) - { - RedirectUri = redirectUri; - } + [JsonConstructor] + private AuthorizationResponseCallback(Uri redirectUri) + { + RedirectUri = redirectUri; } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/ClientIdScheme.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/ClientIdScheme.cs index 73326cbc..64c34d07 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/ClientIdScheme.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/ClientIdScheme.cs @@ -1,77 +1,76 @@ using static WalletFramework.Oid4Vc.Oid4Vp.Models.ClientIdScheme.ClientIdSchemeValue; -namespace WalletFramework.Oid4Vc.Oid4Vp.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.Models; + +/// +/// The client ID scheme used to obtain and validate metadata of the verifier. +/// +public record ClientIdScheme { /// - /// The client ID scheme used to obtain and validate metadata of the verifier. + /// The client ID scheme value. /// - public record ClientIdScheme + public enum ClientIdSchemeValue { /// - /// The client ID scheme value. - /// - public enum ClientIdSchemeValue - { - /// - /// The X509 SAN DNS client ID scheme. - /// - X509SanDns, - - /// - /// The verifier attestation client ID scheme. - /// - VerifierAttestation, - - /// - /// The Redirect Uri scheme. - /// - RedirectUri - } - - /// - /// The Verifier Attestation scheme. + /// The X509 SAN DNS client ID scheme. /// - public const string VerifierAttestationScheme = "verifier_attestation"; + X509SanDns, /// - /// The X509 SAN DNS scheme. + /// The verifier attestation client ID scheme. /// - public const string X509SanDnsScheme = "x509_san_dns"; - + VerifierAttestation, + /// /// The Redirect Uri scheme. /// - public const string RedirectUriScheme = "redirect_uri"; + RedirectUri + } - /// - /// The client ID scheme value. - /// - public ClientIdSchemeValue Value { get; } + /// + /// The Verifier Attestation scheme. + /// + public const string VerifierAttestationScheme = "verifier_attestation"; - /// - /// Initializes a new instance of the class. - /// - private ClientIdScheme(ClientIdSchemeValue value) => Value = value; + /// + /// The X509 SAN DNS scheme. + /// + public const string X509SanDnsScheme = "x509_san_dns"; + + /// + /// The Redirect Uri scheme. + /// + public const string RedirectUriScheme = "redirect_uri"; - /// - /// Creates a client ID scheme from the specified input. - /// - /// The input to create the client ID scheme from. - /// The client ID scheme created from the input. - /// The client ID scheme is not supported. - public static ClientIdScheme CreateClientIdScheme(string input) => - input switch - { - X509SanDnsScheme => new ClientIdScheme(X509SanDns), - RedirectUriScheme => new ClientIdScheme(RedirectUri), - VerifierAttestationScheme => - throw new NotImplementedException("Verifier Attestation not yet implemented"), - _ => throw new InvalidOperationException($"Client ID Scheme {input} is not supported") - }; + /// + /// The client ID scheme value. + /// + public ClientIdSchemeValue Value { get; } - /// - /// Implicitly converts the input to a client ID scheme. - /// - public static implicit operator ClientIdScheme(string input) => CreateClientIdScheme(input); - } -} + /// + /// Initializes a new instance of the class. + /// + private ClientIdScheme(ClientIdSchemeValue value) => Value = value; + + /// + /// Creates a client ID scheme from the specified input. + /// + /// The input to create the client ID scheme from. + /// The client ID scheme created from the input. + /// The client ID scheme is not supported. + public static ClientIdScheme CreateClientIdScheme(string input) => + input switch + { + X509SanDnsScheme => new ClientIdScheme(X509SanDns), + RedirectUriScheme => new ClientIdScheme(RedirectUri), + VerifierAttestationScheme => + throw new NotImplementedException("Verifier Attestation not yet implemented"), + _ => throw new InvalidOperationException($"Client ID Scheme {input} is not supported") + }; + + /// + /// Implicitly converts the input to a client ID scheme. + /// + public static implicit operator ClientIdScheme(string input) => CreateClientIdScheme(input); +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/ClientMetadata.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/ClientMetadata.cs index a8678cb6..fdbb2d15 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/ClientMetadata.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/ClientMetadata.cs @@ -1,36 +1,35 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vp.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.Models; + +/// +/// Represents the metadata of a client (verifier). +/// +public record ClientMetadata { /// - /// Represents the metadata of a client (verifier). + /// The redirect URIs of the client (verifier). /// - public record ClientMetadata - { - /// - /// The redirect URIs of the client (verifier). - /// - [JsonProperty("redirect_uris")] - public string[] RedirectUris { get; } + [JsonProperty("redirect_uris")] + public string[] RedirectUris { get; } - /// - /// The name of the client (verifier). - /// - [JsonProperty("client_name")] - public string? ClientName { get; } + /// + /// The name of the client (verifier). + /// + [JsonProperty("client_name")] + public string? ClientName { get; } - /// - /// The URI of the logo of the client (verifier). - /// - [JsonProperty("logo_uri")] - public string? LogoUri { get; } + /// + /// The URI of the logo of the client (verifier). + /// + [JsonProperty("logo_uri")] + public string? LogoUri { get; } - [JsonConstructor] - private ClientMetadata(string? clientName, string? logoUri, string[] redirectUris) - { - ClientName = clientName; - LogoUri = logoUri; - RedirectUris = redirectUris; - } + [JsonConstructor] + private ClientMetadata(string? clientName, string? logoUri, string[] redirectUris) + { + ClientName = clientName; + LogoUri = logoUri; + RedirectUris = redirectUris; } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/CredentialCandidates.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/CredentialCandidates.cs index 4c401f86..78fd97d0 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/CredentialCandidates.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/CredentialCandidates.cs @@ -1,39 +1,38 @@ using Hyperledger.Aries.Storage.Models.Interfaces; -namespace WalletFramework.Oid4Vc.Oid4Vp.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.Models; + +/// +/// Represents a list of credential candidates. +/// +public class CredentialCandidates { /// - /// Represents a list of credential candidates. + /// Gets a value indicating whether disclosures should be limited. /// - public class CredentialCandidates - { - /// - /// Gets a value indicating whether disclosures should be limited. - /// - public bool LimitDisclosuresRequired { get; private set; } + public bool LimitDisclosuresRequired { get; private set; } - /// - /// Gets the array of credentials matching the input descriptor. - /// - public ICredential[] Credentials { get; private set; } + /// + /// Gets the array of credentials matching the input descriptor. + /// + public ICredential[] Credentials { get; private set; } - /// - /// Gets the ID of the input descriptor. - /// - public string InputDescriptorId { get; private set; } + /// + /// Gets the ID of the input descriptor. + /// + public string InputDescriptorId { get; private set; } - /// - /// Initializes a new instance of the class. - /// - /// The ID of the input descriptor. - /// The credentials matching the input descriptor. - /// Specifies whether disclosures should be limited. - public CredentialCandidates(string inputDescriptorId, IEnumerable credentials, - bool limitDisclosuresRequired = false) - { - InputDescriptorId = inputDescriptorId; - Credentials = credentials.ToArray(); - LimitDisclosuresRequired = limitDisclosuresRequired; - } + /// + /// Initializes a new instance of the class. + /// + /// The ID of the input descriptor. + /// The credentials matching the input descriptor. + /// Specifies whether disclosures should be limited. + public CredentialCandidates(string inputDescriptorId, IEnumerable credentials, + bool limitDisclosuresRequired = false) + { + InputDescriptorId = inputDescriptorId; + Credentials = credentials.ToArray(); + LimitDisclosuresRequired = limitDisclosuresRequired; } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/HaipAuthorizationRequestUri.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/HaipAuthorizationRequestUri.cs index 4c3df394..d640e5c5 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/HaipAuthorizationRequestUri.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/HaipAuthorizationRequestUri.cs @@ -1,42 +1,41 @@ using Hyperledger.Aries.Utils; -namespace WalletFramework.Oid4Vc.Oid4Vp.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.Models; + +/// +/// This class represents a haip conform OpenID4VP Authorization Request Uri. +/// +public class HaipAuthorizationRequestUri { /// - /// This class represents a haip conform OpenID4VP Authorization Request Uri. + /// Gets or sets the uri of the request. /// - public class HaipAuthorizationRequestUri - { - /// - /// Gets or sets the uri of the request. - /// - public Uri Uri { get; set; } = null!; + public Uri Uri { get; set; } = null!; - /// - /// Gets or sets the value of the request_uri parameter. - /// - public string RequestUri { get; set; } = null!; + /// + /// Gets or sets the value of the request_uri parameter. + /// + public string RequestUri { get; set; } = null!; - /// - /// Validates the hap conformity of an uri and returns a HaipAuthorizationRequestUri. - /// - /// - /// The HaipAuthorizationRequestUri - /// - public static HaipAuthorizationRequestUri FromUri(Uri uri) - { - if (!(uri.Scheme == "haip" | uri.Scheme == "openid4vp")) - throw new InvalidOperationException("Invalid Scheme. Must be haip or openid4vp"); + /// + /// Validates the hap conformity of an uri and returns a HaipAuthorizationRequestUri. + /// + /// + /// The HaipAuthorizationRequestUri + /// + public static HaipAuthorizationRequestUri FromUri(Uri uri) + { + if (!(uri.Scheme == "haip" | uri.Scheme == "openid4vp")) + throw new InvalidOperationException("Invalid Scheme. Must be haip or openid4vp"); - var request = uri.GetQueryParam("request_uri"); - if (string.IsNullOrEmpty(request)) - throw new InvalidOperationException("HAIP requires request_uri parameter"); + var request = uri.GetQueryParam("request_uri"); + if (string.IsNullOrEmpty(request)) + throw new InvalidOperationException("HAIP requires request_uri parameter"); - return new HaipAuthorizationRequestUri() - { - RequestUri = request, - Uri = uri - }; - } + return new HaipAuthorizationRequestUri() + { + RequestUri = request, + Uri = uri + }; } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/OidPresentationRecord.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/OidPresentationRecord.cs index d43d6321..9fbb2c4e 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/OidPresentationRecord.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/OidPresentationRecord.cs @@ -1,78 +1,77 @@ using Hyperledger.Aries.Storage; using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vp.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.Models; + +/// +/// Used to persist OpenId4Vp presentations. +/// +public sealed class OidPresentationRecord : RecordBase { /// - /// Used to persist OpenId4Vp presentations. + /// Gets or sets the credentials the Holder presented to the Verifier. /// - public sealed class OidPresentationRecord : RecordBase - { - /// - /// Gets or sets the credentials the Holder presented to the Verifier. - /// - public PresentedCredential[] PresentedCredentials { get; set; } + public PresentedCredential[] PresentedCredentials { get; set; } - /// - /// Gets or sets the client id and identifies the Verifier. - /// - [JsonIgnore] - public string ClientId - { - get => Get(); - set => Set(value, false); - } + /// + /// Gets or sets the client id and identifies the Verifier. + /// + [JsonIgnore] + public string ClientId + { + get => Get(); + set => Set(value, false); + } - /// - /// Gets or sets the type name. - /// - public override string TypeName => "AF.OidPresentationRecord"; + /// + /// Gets or sets the type name. + /// + public override string TypeName => "AF.OidPresentationRecord"; - /// - /// Gets or sets the metadata of the Verifier. - /// - public ClientMetadata? ClientMetadata { get; set; } + /// + /// Gets or sets the metadata of the Verifier. + /// + public ClientMetadata? ClientMetadata { get; set; } - /// - /// Gets or sets the name of the presentation. - /// - [JsonIgnore] - public string? Name - { - get => Get(); - set => Set(value!, false); - } + /// + /// Gets or sets the name of the presentation. + /// + [JsonIgnore] + public string? Name + { + get => Get(); + set => Set(value!, false); + } #pragma warning disable CS8618 - /// - /// Parameterless Default Constructor. - /// - public OidPresentationRecord() - { - } + /// + /// Parameterless Default Constructor. + /// + public OidPresentationRecord() + { + } #pragma warning restore CS8618 - /// - /// Constructor for Serialization. - /// - /// The credentials the Holder presented to the Verifier. - /// The client id for the Verifier. - /// The id of the record. - /// The metadata of the Verifier. - /// The name of the presentation. - [JsonConstructor] - private OidPresentationRecord( - PresentedCredential[] presentedCredentials, - string clientId, - string id, - ClientMetadata? clientMetadata, - string? name) - { - ClientId = clientId; - ClientMetadata = clientMetadata; - Id = id; - Name = name; - PresentedCredentials = presentedCredentials; - } + /// + /// Constructor for Serialization. + /// + /// The credentials the Holder presented to the Verifier. + /// The client id for the Verifier. + /// The id of the record. + /// The metadata of the Verifier. + /// The name of the presentation. + [JsonConstructor] + private OidPresentationRecord( + PresentedCredential[] presentedCredentials, + string clientId, + string id, + ClientMetadata? clientMetadata, + string? name) + { + ClientId = clientId; + ClientMetadata = clientMetadata; + Id = id; + Name = name; + PresentedCredentials = presentedCredentials; } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/PresentedClaim.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/PresentedClaim.cs index 210409b6..b505ebeb 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/PresentedClaim.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/PresentedClaim.cs @@ -1,13 +1,12 @@ -namespace WalletFramework.Oid4Vc.Oid4Vp.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.Models; + +/// +/// Represents a presented claim +/// +public class PresentedClaim { /// - /// Represents a presented claim + /// Gets or sets the value of the presented claim. /// - public class PresentedClaim - { - /// - /// Gets or sets the value of the presented claim. - /// - public string Value { get; set; } = null!; - } -} + public string Value { get; set; } = null!; +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/PresentedCredential.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/PresentedCredential.cs index 2a1b5719..a89439ab 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/PresentedCredential.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/PresentedCredential.cs @@ -1,18 +1,17 @@ -namespace WalletFramework.Oid4Vc.Oid4Vp.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.Models; + +/// +/// Represents a presented credential. +/// +public class PresentedCredential { /// - /// Represents a presented credential. + /// Gets or Sets the claims of the credential that were presented. /// - public class PresentedCredential - { - /// - /// Gets or Sets the claims of the credential that were presented. - /// - public Dictionary PresentedClaims { get; set; } = null!; + public Dictionary PresentedClaims { get; set; } = null!; - /// - /// Gets or Sets the credential id. - /// - public string CredentialId { get; set; } = null!; - } -} + /// + /// Gets or Sets the credential id. + /// + public string CredentialId { get; set; } = null!; +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/SelectedCredential.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/SelectedCredential.cs index 9a987b96..ece3d602 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Models/SelectedCredential.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Models/SelectedCredential.cs @@ -1,20 +1,19 @@ using Hyperledger.Aries.Storage.Models.Interfaces; -namespace WalletFramework.Oid4Vc.Oid4Vp.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.Models; + +/// +/// Represents a credential that the Holder chose to present to the Verifier. +/// +public class SelectedCredential { /// - /// Represents a credential that the Holder chose to present to the Verifier. + /// Gets or Sets the ID of the input descriptor that is answered. /// - public class SelectedCredential - { - /// - /// Gets or Sets the ID of the input descriptor that is answered. - /// - public string InputDescriptorId { get; set; } = null!; + public string InputDescriptorId { get; set; } = null!; - /// - /// Gets or Sets the Credential that is used to answer the input descriptor. - /// - public ICredential Credential { get; set; } = null!; - } -} + /// + /// Gets or Sets the Credential that is used to answer the input descriptor. + /// + public ICredential Credential { get; set; } = null!; +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/Descriptor.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/Descriptor.cs index a6cde432..d332694f 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/Descriptor.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/Descriptor.cs @@ -1,35 +1,34 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models; + +/// +/// The descriptor. +/// +public class Descriptor { /// - /// The descriptor. + /// This MUST be a string that matches the id property of the Input Descriptor in the Presentation Definition that this Presentation Submission is related to. /// - public class Descriptor - { - /// - /// This MUST be a string that matches the id property of the Input Descriptor in the Presentation Definition that this Presentation Submission is related to. - /// - [JsonProperty("id")] - public string Id { get; set; } = null!; + [JsonProperty("id")] + public string Id { get; set; } = null!; - /// - /// This MUST be a string that matches one of the Claim Format Designation - /// - [JsonProperty("format")] - public string Format { get; set; } = null!; + /// + /// This MUST be a string that matches one of the Claim Format Designation + /// + [JsonProperty("format")] + public string Format { get; set; } = null!; - /// - /// This MUST be a JSONPath string expression. - /// The path property indicates the Claim submitted in relation to the identified Input Descriptor, when executed against the top-level of the object the Presentation Submission is embedded within. - /// - [JsonProperty("path")] - public string Path { get; set; } = null!; + /// + /// This MUST be a JSONPath string expression. + /// The path property indicates the Claim submitted in relation to the identified Input Descriptor, when executed against the top-level of the object the Presentation Submission is embedded within. + /// + [JsonProperty("path")] + public string Path { get; set; } = null!; - /// - /// This indicate the presence of a multi-Claim envelope format. This means the Claim indicated is to be decoded separately from its parent enclosure - /// - [JsonProperty("path_nested", NullValueHandling = NullValueHandling.Ignore)] - public Descriptor? PathNested { get; set; } - } -} + /// + /// This indicate the presence of a multi-Claim envelope format. This means the Claim indicated is to be decoded separately from its parent enclosure + /// + [JsonProperty("path_nested", NullValueHandling = NullValueHandling.Ignore)] + public Descriptor? PathNested { get; set; } +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/DescriptorMap.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/DescriptorMap.cs index b1b2c942..5d8b70ac 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/DescriptorMap.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/DescriptorMap.cs @@ -1,35 +1,34 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models; + +/// +/// The descriptor. +/// +public class DescriptorMap { /// - /// The descriptor. + /// This MUST be a string that matches the id property of the Input Descriptor in the Presentation Definition that this Presentation Submission is related to. /// - public class DescriptorMap - { - /// - /// This MUST be a string that matches the id property of the Input Descriptor in the Presentation Definition that this Presentation Submission is related to. - /// - [JsonProperty("id")] - public string Id { get; set; } = null!; + [JsonProperty("id")] + public string Id { get; set; } = null!; - /// - /// This MUST be a string that matches one of the Claim Format Designation - /// - [JsonProperty("format")] - public string Format { get; set; } = null!; + /// + /// This MUST be a string that matches one of the Claim Format Designation + /// + [JsonProperty("format")] + public string Format { get; set; } = null!; - /// - /// This MUST be a JSONPath string expression. - /// The path property indicates the Claim submitted in relation to the identified Input Descriptor, when executed against the top-level of the object the Presentation Submission is embedded within. - /// - [JsonProperty("path")] - public string Path { get; set; } = null!; + /// + /// This MUST be a JSONPath string expression. + /// The path property indicates the Claim submitted in relation to the identified Input Descriptor, when executed against the top-level of the object the Presentation Submission is embedded within. + /// + [JsonProperty("path")] + public string Path { get; set; } = null!; - /// - /// This indicate the presence of a multi-Claim envelope format. This means the Claim indicated is to be decoded separately from its parent enclosure - /// - [JsonProperty("path_nested", NullValueHandling = NullValueHandling.Ignore)] - public DescriptorMap? PathNested { get; set; } - } -} + /// + /// This indicate the presence of a multi-Claim envelope format. This means the Claim indicated is to be decoded separately from its parent enclosure + /// + [JsonProperty("path_nested", NullValueHandling = NullValueHandling.Ignore)] + public DescriptorMap? PathNested { get; set; } +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/Format.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/Format.cs index c953432c..8e97feeb 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/Format.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/Format.cs @@ -1,22 +1,21 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models; + +/// +/// Represents the claim format, encapsulating supported algorithms. +/// +public class Format { /// - /// Represents the claim format, encapsulating supported algorithms. + /// Gets the names of supported algorithms. /// - public class Format - { - /// - /// Gets the names of supported algorithms. - /// - [JsonProperty("alg")] - public string[] Alg { get; private set; } = null!; + [JsonProperty("alg")] + public string[] Alg { get; private set; } = null!; - /// - /// Gets the names of supported proof types. - /// - [JsonProperty("proof_type")] - public string[] ProofTypes { get; private set; } = null!; - } -} + /// + /// Gets the names of supported proof types. + /// + [JsonProperty("proof_type")] + public string[] ProofTypes { get; private set; } = null!; +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/InputDescriptor.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/InputDescriptor.cs index 27ba9f1a..5d542bcd 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/InputDescriptor.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/InputDescriptor.cs @@ -1,110 +1,109 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models; + +/// +/// Represents details about an input descriptor. This class encapsulates properties for the top-level +/// of an Input Descriptor Object. +/// +public class InputDescriptor { /// - /// Represents details about an input descriptor. This class encapsulates properties for the top-level - /// of an Input Descriptor Object. + /// Gets or sets the constraints for the input descriptor. + /// It defines conditions that must be met for the input. /// - public class InputDescriptor - { - /// - /// Gets or sets the constraints for the input descriptor. - /// It defines conditions that must be met for the input. - /// - [JsonProperty("constraints", Required = Required.Always)] - public Constraints Constraints { get; private set; } = null!; + [JsonProperty("constraints", Required = Required.Always)] + public Constraints Constraints { get; private set; } = null!; - /// - /// Gets or sets the formats of the input descriptor. - /// This property is optional. - /// - [JsonProperty("format")] - public Dictionary Formats { get; private set; } = null!; + /// + /// Gets or sets the formats of the input descriptor. + /// This property is optional. + /// + [JsonProperty("format")] + public Dictionary Formats { get; private set; } = null!; - /// - /// Gets or sets the unique identifier for the input descriptor. - /// This MUST be a string that does not conflict with the id of another Input Descriptor Object - /// in the same Presentation Definition. - /// - [JsonProperty("id", Required = Required.Always)] - public string Id { get; private set; } = null!; + /// + /// Gets or sets the unique identifier for the input descriptor. + /// This MUST be a string that does not conflict with the id of another Input Descriptor Object + /// in the same Presentation Definition. + /// + [JsonProperty("id", Required = Required.Always)] + public string Id { get; private set; } = null!; - /// - /// Gets or sets the human-friendly name that describes what the target schema represents. - /// This property is optional. - /// - [JsonProperty("name")] - public string? Name { get; private set; } + /// + /// Gets or sets the human-friendly name that describes what the target schema represents. + /// This property is optional. + /// + [JsonProperty("name")] + public string? Name { get; private set; } - /// - /// Gets or sets the purpose for which the Claim's data is being requested. - /// This property is optional. - /// - [JsonProperty("purpose")] - public string? Purpose { get; private set; } + /// + /// Gets or sets the purpose for which the Claim's data is being requested. + /// This property is optional. + /// + [JsonProperty("purpose")] + public string? Purpose { get; private set; } - /// - /// Gets the array of groups that the input descriptor belongs to. Needed for Submission Requirement Feature. - /// - [JsonProperty("group")] - public string[]? Group { get; private set; } - } + /// + /// Gets the array of groups that the input descriptor belongs to. Needed for Submission Requirement Feature. + /// + [JsonProperty("group")] + public string[]? Group { get; private set; } +} +/// +/// Represents constraints that are associated with an input descriptor. +/// Defines conditions that the input must meet. +/// +public class Constraints +{ /// - /// Represents constraints that are associated with an input descriptor. - /// Defines conditions that the input must meet. + /// Gets the array of fields. + /// This property is optional. /// - public class Constraints - { - /// - /// Gets the array of fields. - /// This property is optional. - /// - [JsonProperty("fields")] - public Field[]? Fields { get; private set; } + [JsonProperty("fields")] + public Field[]? Fields { get; private set; } - /// - /// Gets the requirement for limit disclosures. - /// This property is optional. - /// - [JsonProperty("limit_disclosure")] - public string? LimitDisclosure { get; private set; } - } + /// + /// Gets the requirement for limit disclosures. + /// This property is optional. + /// + [JsonProperty("limit_disclosure")] + public string? LimitDisclosure { get; private set; } +} +/// +/// Represents the detailed structure of a specific field within the constraints. +/// +public class Field +{ /// - /// Represents the detailed structure of a specific field within the constraints. + /// Gets the filter associated with the field to evaluate values against. /// - public class Field - { - /// - /// Gets the filter associated with the field to evaluate values against. - /// - [JsonProperty("filter")] - public Filter? Filter { get; private set; } + [JsonProperty("filter")] + public Filter? Filter { get; private set; } - /// - /// Gets an array of JSONPath string expressions that select a target value from the input. - /// - [JsonProperty("path")] - public string[] Path { get; private set; } = null!; - } + /// + /// Gets an array of JSONPath string expressions that select a target value from the input. + /// + [JsonProperty("path")] + public string[] Path { get; private set; } = null!; +} +/// +/// Represents a filter applied to the value that is selected by the field's path. +/// +public class Filter +{ /// - /// Represents a filter applied to the value that is selected by the field's path. + /// Gets the constant value which the selected value is evaluated against. /// - public class Filter - { - /// - /// Gets the constant value which the selected value is evaluated against. - /// - [JsonProperty("const")] - public string Const { get; private set; } = null!; + [JsonProperty("const")] + public string Const { get; private set; } = null!; - /// - /// Gets the type of filter applied. - /// - [JsonProperty("type")] - public string Type { get; private set; } = null!; - } -} + /// + /// Gets the type of filter applied. + /// + [JsonProperty("type")] + public string Type { get; private set; } = null!; +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/PresentationDefinition.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/PresentationDefinition.cs index d1d9aec7..c5096342 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/PresentationDefinition.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/PresentationDefinition.cs @@ -1,64 +1,63 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models; + +/// +/// Represents objects that articulate what proofs a Verifier requires +/// +public class PresentationDefinition { /// - /// Represents objects that articulate what proofs a Verifier requires + /// Gets or sets the format of the presentation definition + /// This property is optional. /// - public class PresentationDefinition - { - /// - /// Gets or sets the format of the presentation definition - /// This property is optional. - /// - [JsonProperty("format")] - public Dictionary Formats { get; } + [JsonProperty("format")] + public Dictionary Formats { get; } - /// - /// Represents a collection of input descriptors. - /// - [JsonProperty("input_descriptors", Required = Required.Always)] - public InputDescriptor[] InputDescriptors { get; } + /// + /// Represents a collection of input descriptors. + /// + [JsonProperty("input_descriptors", Required = Required.Always)] + public InputDescriptor[] InputDescriptors { get; } - /// - /// This MUST be a string. The string SHOULD provide a unique ID for the desired context. - /// - [JsonProperty("id", Required = Required.Always)] - public string Id { get; } + /// + /// This MUST be a string. The string SHOULD provide a unique ID for the desired context. + /// + [JsonProperty("id", Required = Required.Always)] + public string Id { get; } - /// - /// This SHOULD be a human-friendly string intended to constitute a distinctive designation of the Presentation - /// Definition. - /// - [JsonProperty("name")] - public string? Name { get; } + /// + /// This SHOULD be a human-friendly string intended to constitute a distinctive designation of the Presentation + /// Definition. + /// + [JsonProperty("name")] + public string? Name { get; } - /// - /// This MUST be a string that describes the purpose for which the Presentation Definition's inputs are being used for. - /// - public string? Purpose { get; } + /// + /// This MUST be a string that describes the purpose for which the Presentation Definition's inputs are being used for. + /// + public string? Purpose { get; } - /// - /// Represents a collection of submission requirements - /// - [JsonProperty("submission_requirements")] - public SubmissionRequirement[] SubmissionRequirements { get; } + /// + /// Represents a collection of submission requirements + /// + [JsonProperty("submission_requirements")] + public SubmissionRequirement[] SubmissionRequirements { get; } - [JsonConstructor] - private PresentationDefinition( - Dictionary formats, - InputDescriptor[] inputDescriptors, - string id, - string? name, - string? purpose, - SubmissionRequirement[] submissionRequirements) - { - Formats = formats; - InputDescriptors = inputDescriptors; - Id = id; - Name = name; - Purpose = purpose; - SubmissionRequirements = submissionRequirements; - } + [JsonConstructor] + private PresentationDefinition( + Dictionary formats, + InputDescriptor[] inputDescriptors, + string id, + string? name, + string? purpose, + SubmissionRequirement[] submissionRequirements) + { + Formats = formats; + InputDescriptors = inputDescriptors; + Id = id; + Name = name; + Purpose = purpose; + SubmissionRequirements = submissionRequirements; } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/PresentationSubmission.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/PresentationSubmission.cs index b892202d..37221323 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/PresentationSubmission.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/PresentationSubmission.cs @@ -1,28 +1,27 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models; + +/// +/// Represents objects embedded within target Claim negotiation formats that express how the inputs presented as proofs to a Verifier are provided in accordance with the requirements specified in a Presentation Definition. +/// +public class PresentationSubmission { /// - /// Represents objects embedded within target Claim negotiation formats that express how the inputs presented as proofs to a Verifier are provided in accordance with the requirements specified in a Presentation Definition. + /// This MUST be a unique identifier, such as a UUID. /// - public class PresentationSubmission - { - /// - /// This MUST be a unique identifier, such as a UUID. - /// - [JsonProperty("id")] - public string Id { get; internal set; } = null!; + [JsonProperty("id")] + public string Id { get; internal set; } = null!; - /// - /// This MUST be the id value of a valid Presentation Definition. - /// - [JsonProperty("definition_id")] - public string DefinitionId { get; internal set; } = null!; + /// + /// This MUST be the id value of a valid Presentation Definition. + /// + [JsonProperty("definition_id")] + public string DefinitionId { get; internal set; } = null!; - /// - /// This MUST be the id value of a valid Presentation Definition. - /// - [JsonProperty("descriptor_map")] - public DescriptorMap[] DescriptorMap { get; internal set; } = null!; - } -} + /// + /// This MUST be the id value of a valid Presentation Definition. + /// + [JsonProperty("descriptor_map")] + public DescriptorMap[] DescriptorMap { get; internal set; } = null!; +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/SubmissionRequirement.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/SubmissionRequirement.cs index e730a313..eb29c08e 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/SubmissionRequirement.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Models/SubmissionRequirement.cs @@ -1,61 +1,60 @@ using Newtonsoft.Json; -namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models +namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models; + +/// +/// Represents a Submission Requirement Object. +/// +public class SubmissionRequirement { /// - /// Represents a Submission Requirement Object. + /// Gets the count associated with this submission requirement. This indicates the number of Input Descriptors or + /// Submission Requirement Objects to be submitted. /// - public class SubmissionRequirement - { - /// - /// Gets the count associated with this submission requirement. This indicates the number of Input Descriptors or - /// Submission Requirement Objects to be submitted. - /// - /// - /// This is an optional property. - /// - [JsonProperty("count")] - public int? Count { get; private set; } + /// + /// This is an optional property. + /// + [JsonProperty("count")] + public int? Count { get; private set; } - /// - /// Gets the group string for this submission requirement. - /// - /// - /// This property must contain a group string matching one of the group strings - /// specified for one or more Input Descriptor Objects. - /// - [JsonProperty("from", Required = Required.Always)] - public string From { get; private set; } = null!; - - /// - /// Gets the rule for this submission requirement. - /// - /// - /// According to the HAIP, this property must be "pick". - /// - [JsonProperty("rule", Required = Required.Always)] - public string Rule { get; private set; } = null!; + /// + /// Gets the group string for this submission requirement. + /// + /// + /// This property must contain a group string matching one of the group strings + /// specified for one or more Input Descriptor Objects. + /// + [JsonProperty("from", Required = Required.Always)] + public string From { get; private set; } = null!; - /// - /// Gets the name of the submission requirement. - /// - [JsonProperty("name")] - public string? Name { get; private set; } + /// + /// Gets the rule for this submission requirement. + /// + /// + /// According to the HAIP, this property must be "pick". + /// + [JsonProperty("rule", Required = Required.Always)] + public string Rule { get; private set; } = null!; - /// - /// Private constructor for the SubmissionRequirement class. - /// - private SubmissionRequirement() - { - } + /// + /// Gets the name of the submission requirement. + /// + [JsonProperty("name")] + public string? Name { get; private set; } - /// - /// Gets or sets the from_nested property. - /// - /// - /// According to the HAIP, this property must remain null because only "from" is supported, and you can set either - /// "from" or "from_nested," but not both. - /// - [JsonProperty("from_nested")] private string? _fromNested; + /// + /// Private constructor for the SubmissionRequirement class. + /// + private SubmissionRequirement() + { } -} + + /// + /// Gets or sets the from_nested property. + /// + /// + /// According to the HAIP, this property must remain null because only "from" is supported, and you can set either + /// "from" or "from_nested," but not both. + /// + [JsonProperty("from_nested")] private string? _fromNested; +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Services/IPexService.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Services/IPexService.cs index b425e81c..66b737e7 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Services/IPexService.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Services/IPexService.cs @@ -2,29 +2,28 @@ using WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models; using WalletFramework.SdJwtVc.Models.Records; -namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Services +namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Services; + +/// +/// Pex Service. +/// +public interface IPexService { /// - /// Pex Service. + /// Creates a presentation submission. /// - public interface IPexService - { - /// - /// Creates a presentation submission. - /// - /// The presentation definition. - /// Data used to build Descriptor Maps. - /// - /// A task representing the asynchronous operation. The task result contains the Presentation Submission. - /// - Task CreatePresentationSubmission(PresentationDefinition presentationDefinition, DescriptorMap[] descriptorMaps); + /// The presentation definition. + /// Data used to build Descriptor Maps. + /// + /// A task representing the asynchronous operation. The task result contains the Presentation Submission. + /// + Task CreatePresentationSubmission(PresentationDefinition presentationDefinition, DescriptorMap[] descriptorMaps); - /// - /// Finds the credential candidates based on the provided credentials and input descriptors. - /// - /// An array of available credentials. - /// An array of input descriptors to be satisfied. - /// An array of credential candidates, each containing a list of credentials that match the input descriptors. - Task FindCredentialCandidates(SdJwtRecord[] credentials, InputDescriptor[] inputDescriptors); - } -} + /// + /// Finds the credential candidates based on the provided credentials and input descriptors. + /// + /// An array of available credentials. + /// An array of input descriptors to be satisfied. + /// An array of credential candidates, each containing a list of credentials that match the input descriptors. + Task FindCredentialCandidates(SdJwtRecord[] credentials, InputDescriptor[] inputDescriptors); +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Services/PexService.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Services/PexService.cs index cf3ddccb..e3137f49 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Services/PexService.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/PresentationExchange/Services/PexService.cs @@ -6,103 +6,102 @@ using WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Models; using WalletFramework.SdJwtVc.Models.Records; -namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Services +namespace WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Services; + +/// +public class PexService : IPexService { /// - public class PexService : IPexService + public Task CreatePresentationSubmission(PresentationDefinition presentationDefinition, DescriptorMap[] descriptorMaps) { - /// - public Task CreatePresentationSubmission(PresentationDefinition presentationDefinition, DescriptorMap[] descriptorMaps) - { - var inputDescriptorIds = presentationDefinition.InputDescriptors.Select(x => x.Id); - if (!descriptorMaps.Select(x => x.Id).All(inputDescriptorIds.Contains)) - throw new ArgumentException("Missing descriptors for given input descriptors in presentation definition.", nameof(descriptorMaps)); + var inputDescriptorIds = presentationDefinition.InputDescriptors.Select(x => x.Id); + if (!descriptorMaps.Select(x => x.Id).All(inputDescriptorIds.Contains)) + throw new ArgumentException("Missing descriptors for given input descriptors in presentation definition.", nameof(descriptorMaps)); - var presentationSubmission = new PresentationSubmission - { - Id = Guid.NewGuid().ToString(), - DefinitionId = presentationDefinition.Id, - DescriptorMap = descriptorMaps.Cast().ToArray() - }; + var presentationSubmission = new PresentationSubmission + { + Id = Guid.NewGuid().ToString(), + DefinitionId = presentationDefinition.Id, + DescriptorMap = descriptorMaps.Cast().ToArray() + }; - return Task.FromResult(presentationSubmission); - } + return Task.FromResult(presentationSubmission); + } - /// - public virtual Task FindCredentialCandidates(SdJwtRecord[] credentials, - InputDescriptor[] inputDescriptors) - { - var result = new List(); + /// + public virtual Task FindCredentialCandidates(SdJwtRecord[] credentials, + InputDescriptor[] inputDescriptors) + { + var result = new List(); - foreach (var inputDescriptor in inputDescriptors) + foreach (var inputDescriptor in inputDescriptors) + { + if (!inputDescriptor.Formats.Keys.Contains("vc+sd-jwt")) { - if (!inputDescriptor.Formats.Keys.Contains("vc+sd-jwt")) - { - throw new NotSupportedException("Only vc+sd-jwt format is supported"); - } + throw new NotSupportedException("Only vc+sd-jwt format is supported"); + } - if (inputDescriptor.Constraints.Fields == null || inputDescriptor.Constraints.Fields.Length == 0) - { - throw new InvalidOperationException("Fields cannot be null or empty"); - } + if (inputDescriptor.Constraints.Fields == null || inputDescriptor.Constraints.Fields.Length == 0) + { + throw new InvalidOperationException("Fields cannot be null or empty"); + } - var matchingCredentials = - _filterMatchingCredentialsForFields(credentials, inputDescriptor.Constraints.Fields); - if (matchingCredentials.Length == 0) - { - continue; - } + var matchingCredentials = + _filterMatchingCredentialsForFields(credentials, inputDescriptor.Constraints.Fields); + if (matchingCredentials.Length == 0) + { + continue; + } - var limitDisclosuresRequired = string.Equals(inputDescriptor.Constraints.LimitDisclosure, "required"); + var limitDisclosuresRequired = string.Equals(inputDescriptor.Constraints.LimitDisclosure, "required"); - var credentialCandidates = new CredentialCandidates(inputDescriptor.Id, - matchingCredentials, limitDisclosuresRequired); + var credentialCandidates = new CredentialCandidates(inputDescriptor.Id, + matchingCredentials, limitDisclosuresRequired); - result.Add(credentialCandidates); - } + result.Add(credentialCandidates); + } - if (result.IsNullOrEmpty()) - { - throw new Oid4VpNoCredentialCandidateException(); - } - - return Task.FromResult(result.ToArray()); + if (result.IsNullOrEmpty()) + { + throw new Oid4VpNoCredentialCandidateException(); } - private static SdJwtRecord[] _filterMatchingCredentialsForFields(SdJwtRecord[] records, Field[] fields) + return Task.FromResult(result.ToArray()); + } + + private static SdJwtRecord[] _filterMatchingCredentialsForFields(SdJwtRecord[] records, Field[] fields) + { + var candidateRecords = new List(); + foreach (var record in records) { - var candidateRecords = new List(); - foreach (var record in records) + var doc = _toSdJwtDoc(record); + var isAMatch = fields.All(field => { - var doc = _toSdJwtDoc(record); - var isAMatch = fields.All(field => + try { - try + if (doc.UnsecuredPayload.SelectToken(field.Path.First(), true) is JValue value && field.Filter?.Const != null) { - if (doc.UnsecuredPayload.SelectToken(field.Path.First(), true) is JValue value && field.Filter?.Const != null) - { - return field.Filter?.Const == value.Value?.ToString(); - } - - return true; + return field.Filter?.Const == value.Value?.ToString(); } - catch (Exception) - { - return false; - } - }); - if (isAMatch) - candidateRecords.Add(record); - } + return true; + } + catch (Exception) + { + return false; + } + }); - return candidateRecords.ToArray(); + if (isAMatch) + candidateRecords.Add(record); } + return candidateRecords.ToArray(); + } - private static SdJwtDoc _toSdJwtDoc(SdJwtRecord record) - { - return new SdJwtDoc(record.EncodedIssuerSignedJwt + "~" + string.Join("~", record.Disclosures) + "~"); - } + + private static SdJwtDoc _toSdJwtDoc(SdJwtRecord record) + { + return new SdJwtDoc(record.EncodedIssuerSignedJwt + "~" + string.Join("~", record.Disclosures) + "~"); } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Services/IOid4VpClientService.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Services/IOid4VpClientService.cs index 9c5be49e..20fe39e4 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Services/IOid4VpClientService.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Services/IOid4VpClientService.cs @@ -1,33 +1,32 @@ using Hyperledger.Aries.Agents; using WalletFramework.Oid4Vc.Oid4Vp.Models; -namespace WalletFramework.Oid4Vc.Oid4Vp.Services +namespace WalletFramework.Oid4Vc.Oid4Vp.Services; + +/// +/// This Service offers methods to handle the OpenId4Vp protocol according to the HAIP +/// +public interface IOid4VpClientService { /// - /// This Service offers methods to handle the OpenId4Vp protocol according to the HAIP + /// Processes an OpenID4VP Authorization Request Url. /// - public interface IOid4VpClientService - { - /// - /// Processes an OpenID4VP Authorization Request Url. - /// - /// - /// /// - /// - /// A task representing the asynchronous operation. The task result contains the Authorization Response object associated with the OpenID4VP Authorization Request Url and Credentials Candidates that can be used to answer the request. - /// - Task<(AuthorizationRequest authorizationRequest, CredentialCandidates[] credentialCandidates)> ProcessAuthorizationRequestAsync(IAgentContext agentContext, Uri authorizationRequestUrl); + /// + /// /// + /// + /// A task representing the asynchronous operation. The task result contains the Authorization Response object associated with the OpenID4VP Authorization Request Url and Credentials Candidates that can be used to answer the request. + /// + Task<(AuthorizationRequest authorizationRequest, CredentialCandidates[] credentialCandidates)> ProcessAuthorizationRequestAsync(IAgentContext agentContext, Uri authorizationRequestUrl); - /// - /// Prepares and sends an Authorization Response containing a Presentation Submission and the VP Token to the Redirect Uri. - /// - /// - /// /// - /// - /// - /// - /// A task representing the asynchronous operation. The task result contains the Callback Url of the Authorization Response if present. - /// - Task SendAuthorizationResponseAsync(IAgentContext agentContext, AuthorizationRequest authorizationRequest, SelectedCredential[] selectedCredentials); - } -} + /// + /// Prepares and sends an Authorization Response containing a Presentation Submission and the VP Token to the Redirect Uri. + /// + /// + /// /// + /// + /// + /// + /// A task representing the asynchronous operation. The task result contains the Callback Url of the Authorization Response if present. + /// + Task SendAuthorizationResponseAsync(IAgentContext agentContext, AuthorizationRequest authorizationRequest, SelectedCredential[] selectedCredentials); +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Services/IOid4VpRecordService.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Services/IOid4VpRecordService.cs index 0c07d76d..d39152ce 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Services/IOid4VpRecordService.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Services/IOid4VpRecordService.cs @@ -2,62 +2,61 @@ using Hyperledger.Aries.Storage; using WalletFramework.Oid4Vc.Oid4Vp.Models; -namespace WalletFramework.Oid4Vc.Oid4Vp.Services +namespace WalletFramework.Oid4Vc.Oid4Vp.Services; + +/// +/// This Service offers methods to interact the OpenId4Vp protocol +/// +public interface IOid4VpRecordService { /// - /// This Service offers methods to interact the OpenId4Vp protocol + /// Retrieves a specific OidPresentation record by its ID. /// - public interface IOid4VpRecordService - { - /// - /// Retrieves a specific OidPresentation record by its ID. - /// - /// The agent context. - /// The ID of the OidPresentation record to retrieve. - /// - /// A task representing the asynchronous operation. The task result contains the - /// associated with the given ID. - /// - Task GetAsync(IAgentContext context, string presentationId); + /// The agent context. + /// The ID of the OidPresentation record to retrieve. + /// + /// A task representing the asynchronous operation. The task result contains the + /// associated with the given ID. + /// + Task GetAsync(IAgentContext context, string presentationId); - /// - /// Lists OidPresentation records based on specified criteria. - /// - /// The agent context. - /// The search query to filter OidPresentation records. Default is null, meaning no filter. - /// The maximum number of records to retrieve. Default is 100. - /// The number of records to skip. Default is 0. - /// - /// A task representing the asynchronous operation. The task result contains a list of - /// that match the criteria. - /// - Task> ListAsync(IAgentContext context, ISearchQuery? query = null, int count = 100, - int skip = 0); + /// + /// Lists OidPresentation records based on specified criteria. + /// + /// The agent context. + /// The search query to filter OidPresentation records. Default is null, meaning no filter. + /// The maximum number of records to retrieve. Default is 100. + /// The number of records to skip. Default is 0. + /// + /// A task representing the asynchronous operation. The task result contains a list of + /// that match the criteria. + /// + Task> ListAsync(IAgentContext context, ISearchQuery? query = null, int count = 100, + int skip = 0); - /// - /// Stores a new OidPresentation record. - /// - /// The agent context. - /// The combined issuance. - /// The key id. - /// The issuer metadata. - /// The name of the presentation definition. - /// A task representing the asynchronous operation. The task result contains the ID of the stored OidPresentation record. - Task StoreAsync( - IAgentContext context, - string clientId, - ClientMetadata? clientMetadata, - string? name, - PresentedCredential[] presentedCredentials); + /// + /// Stores a new OidPresentation record. + /// + /// The agent context. + /// The combined issuance. + /// The key id. + /// The issuer metadata. + /// The name of the presentation definition. + /// A task representing the asynchronous operation. The task result contains the ID of the stored OidPresentation record. + Task StoreAsync( + IAgentContext context, + string clientId, + ClientMetadata? clientMetadata, + string? name, + PresentedCredential[] presentedCredentials); - /// - /// Deletes a specific OidPresentation record by its ID. - /// - /// The agent context. - /// The ID of the OidPresentation record to delete. - /// - /// A task representing the asynchronous operation. The task result indicates whether the deletion was successful. - /// - Task DeleteAsync(IAgentContext context, string recordId); - } -} + /// + /// Deletes a specific OidPresentation record by its ID. + /// + /// The agent context. + /// The ID of the OidPresentation record to delete. + /// + /// A task representing the asynchronous operation. The task result indicates whether the deletion was successful. + /// + Task DeleteAsync(IAgentContext context, string recordId); +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpHaipClient.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpHaipClient.cs index 29c19c38..aa64e87f 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpHaipClient.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpHaipClient.cs @@ -6,114 +6,113 @@ using static WalletFramework.Oid4Vc.Oid4Vp.Models.ClientIdScheme.ClientIdSchemeValue; using static Newtonsoft.Json.JsonConvert; -namespace WalletFramework.Oid4Vc.Oid4Vp.Services +namespace WalletFramework.Oid4Vc.Oid4Vp.Services; + +/// +internal class Oid4VpHaipClient : IOid4VpHaipClient { - /// - internal class Oid4VpHaipClient : IOid4VpHaipClient + /// + /// Initializes a new instance of the class. + /// + /// The http client factory to create http clients. + /// The service responsible for presentation exchange protocol operations. + public Oid4VpHaipClient( + IHttpClientFactory httpClientFactory, + IPexService pexService) { - /// - /// Initializes a new instance of the class. - /// - /// The http client factory to create http clients. - /// The service responsible for presentation exchange protocol operations. - public Oid4VpHaipClient( - IHttpClientFactory httpClientFactory, - IPexService pexService) - { - _httpClientFactory = httpClientFactory; - _pexService = pexService; - } + _httpClientFactory = httpClientFactory; + _pexService = pexService; + } - private readonly IHttpClientFactory _httpClientFactory; - private readonly IPexService _pexService; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IPexService _pexService; - /// - public async Task CreateAuthorizationResponseAsync( - AuthorizationRequest authorizationRequest, - (string inputDescriptorId, string presentation)[] presentationMap) - { - var descriptorMaps = new List(); - var vpToken = new List(); + /// + public async Task CreateAuthorizationResponseAsync( + AuthorizationRequest authorizationRequest, + (string inputDescriptorId, string presentation)[] presentationMap) + { + var descriptorMaps = new List(); + var vpToken = new List(); - for (var index = 0; index < presentationMap.Length; index++) - { - vpToken.Add(presentationMap[index].presentation); - - var descriptorMap = new DescriptorMap - { - Format = "vc+sd-jwt", - Path = presentationMap.Length > 1 ? "$[" + index + "]" : "$", - Id = presentationMap[index].inputDescriptorId, - PathNested = null - }; - descriptorMaps.Add(descriptorMap); - } - - var presentationSubmission = - await _pexService.CreatePresentationSubmission(authorizationRequest.PresentationDefinition, - descriptorMaps.ToArray()); + for (var index = 0; index < presentationMap.Length; index++) + { + vpToken.Add(presentationMap[index].presentation); - return new AuthorizationResponse + var descriptorMap = new DescriptorMap { - PresentationSubmission = SerializeObject(presentationSubmission), - VpToken = vpToken.Count > 1 ? SerializeObject(vpToken) : vpToken[0], - State = authorizationRequest.State + Format = "vc+sd-jwt", + Path = presentationMap.Length > 1 ? "$[" + index + "]" : "$", + Id = presentationMap[index].inputDescriptorId, + PathNested = null }; + descriptorMaps.Add(descriptorMap); } - /// - public async Task ProcessAuthorizationRequestAsync( - HaipAuthorizationRequestUri haipAuthorizationRequestUri) + var presentationSubmission = + await _pexService.CreatePresentationSubmission(authorizationRequest.PresentationDefinition, + descriptorMaps.ToArray()); + + return new AuthorizationResponse { - var httpClient = _httpClientFactory.CreateClient(); - httpClient.DefaultRequestHeaders.Clear(); + PresentationSubmission = SerializeObject(presentationSubmission), + VpToken = vpToken.Count > 1 ? SerializeObject(vpToken) : vpToken[0], + State = authorizationRequest.State + }; + } + + /// + public async Task ProcessAuthorizationRequestAsync( + HaipAuthorizationRequestUri haipAuthorizationRequestUri) + { + var httpClient = _httpClientFactory.CreateClient(); + httpClient.DefaultRequestHeaders.Clear(); - var requestObject = - CreateRequestObject( - await httpClient.GetStringAsync(haipAuthorizationRequestUri.RequestUri) - ); + var requestObject = + CreateRequestObject( + await httpClient.GetStringAsync(haipAuthorizationRequestUri.RequestUri) + ); - var clientMetadata = await FetchClientMetadata(requestObject.ToAuthorizationRequest()); + var clientMetadata = await FetchClientMetadata(requestObject.ToAuthorizationRequest()); - return - requestObject.ClientIdScheme.Value switch - { - X509SanDns => - requestObject - .ValidateJwt() - .ValidateTrustChain() - .ValidateSanName() - .ToAuthorizationRequest() - .WithX509(requestObject) - .WithClientMetadata(clientMetadata), - RedirectUri => - requestObject - .ToAuthorizationRequest() - .WithClientMetadata(clientMetadata), - VerifierAttestation => - throw new NotImplementedException("Verifier Attestation not yet implemented"), - _ => - throw new InvalidOperationException( - $"Client ID Scheme {requestObject.ClientIdScheme} not supported" - ) - }; - } + return + requestObject.ClientIdScheme.Value switch + { + X509SanDns => + requestObject + .ValidateJwt() + .ValidateTrustChain() + .ValidateSanName() + .ToAuthorizationRequest() + .WithX509(requestObject) + .WithClientMetadata(clientMetadata), + RedirectUri => + requestObject + .ToAuthorizationRequest() + .WithClientMetadata(clientMetadata), + VerifierAttestation => + throw new NotImplementedException("Verifier Attestation not yet implemented"), + _ => + throw new InvalidOperationException( + $"Client ID Scheme {requestObject.ClientIdScheme} not supported" + ) + }; + } - private async Task FetchClientMetadata(AuthorizationRequest authorizationRequest) - { - var httpClient = _httpClientFactory.CreateClient(); - httpClient.DefaultRequestHeaders.Clear(); - httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + private async Task FetchClientMetadata(AuthorizationRequest authorizationRequest) + { + var httpClient = _httpClientFactory.CreateClient(); + httpClient.DefaultRequestHeaders.Clear(); + httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - if (authorizationRequest.ClientMetadata != null) - return authorizationRequest.ClientMetadata; + if (authorizationRequest.ClientMetadata != null) + return authorizationRequest.ClientMetadata; - if (string.IsNullOrWhiteSpace(authorizationRequest.ClientMetadataUri)) - return null; + if (string.IsNullOrWhiteSpace(authorizationRequest.ClientMetadataUri)) + return null; - var response = await httpClient.GetAsync(authorizationRequest.ClientMetadataUri); - var clientMetadata = await response.Content.ReadAsStringAsync(); - return DeserializeObject(clientMetadata); - } + var response = await httpClient.GetAsync(authorizationRequest.ClientMetadataUri); + var clientMetadata = await response.Content.ReadAsStringAsync(); + return DeserializeObject(clientMetadata); } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpRecordService.cs b/src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpRecordService.cs index 761e4169..a5de1dec 100644 --- a/src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpRecordService.cs +++ b/src/WalletFramework.Oid4Vc/Oid4Vp/Services/Oid4VpRecordService.cs @@ -3,67 +3,66 @@ using Hyperledger.Aries.Storage; using WalletFramework.Oid4Vc.Oid4Vp.Models; -namespace WalletFramework.Oid4Vc.Oid4Vp.Services +namespace WalletFramework.Oid4Vc.Oid4Vp.Services; + +/// +public class Oid4VpRecordService : IOid4VpRecordService { - /// - public class Oid4VpRecordService : IOid4VpRecordService - { - /// - /// The service responsible for wallet record operations. - /// - protected readonly IWalletRecordService RecordService; + /// + /// The service responsible for wallet record operations. + /// + protected readonly IWalletRecordService RecordService; - /// - /// Initializes a new instance of the class. - /// - /// The service responsible for wallet record operations. - public Oid4VpRecordService(IWalletRecordService recordService) - { - RecordService = recordService; - } + /// + /// Initializes a new instance of the class. + /// + /// The service responsible for wallet record operations. + public Oid4VpRecordService(IWalletRecordService recordService) + { + RecordService = recordService; + } - /// - public async Task GetAsync(IAgentContext context, string presentationId) - { - var record = await RecordService.GetAsync(context.Wallet, presentationId); - if (record == null) - throw new AriesFrameworkException(ErrorCode.RecordNotFound, "OidPresentation record not found"); + /// + public async Task GetAsync(IAgentContext context, string presentationId) + { + var record = await RecordService.GetAsync(context.Wallet, presentationId); + if (record == null) + throw new AriesFrameworkException(ErrorCode.RecordNotFound, "OidPresentation record not found"); - return record; - } + return record; + } - /// - public Task> ListAsync(IAgentContext context, ISearchQuery? query = null, int count = 100, int skip = 0) - { - return RecordService.SearchAsync(context.Wallet, query, null, count, skip); - } + /// + public Task> ListAsync(IAgentContext context, ISearchQuery? query = null, int count = 100, int skip = 0) + { + return RecordService.SearchAsync(context.Wallet, query, null, count, skip); + } - /// - public async Task StoreAsync( - IAgentContext context, - string clientId, - ClientMetadata? clientMetadata, - string? name, - PresentedCredential[] presentedCredentials) + /// + public async Task StoreAsync( + IAgentContext context, + string clientId, + ClientMetadata? clientMetadata, + string? name, + PresentedCredential[] presentedCredentials) + { + var record = new OidPresentationRecord { - var record = new OidPresentationRecord - { - Name = name, - ClientId = clientId, - ClientMetadata = clientMetadata, - Id = Guid.NewGuid().ToString(), - PresentedCredentials = presentedCredentials - }; + Name = name, + ClientId = clientId, + ClientMetadata = clientMetadata, + Id = Guid.NewGuid().ToString(), + PresentedCredentials = presentedCredentials + }; - await RecordService.AddAsync(context.Wallet, record); + await RecordService.AddAsync(context.Wallet, record); - return record.Id; - } + return record.Id; + } - /// - public Task DeleteAsync(IAgentContext context, string recordId) - { - return RecordService.DeleteAsync(context.Wallet, recordId); - } + /// + public Task DeleteAsync(IAgentContext context, string recordId) + { + return RecordService.DeleteAsync(context.Wallet, recordId); } -} +} \ No newline at end of file diff --git a/src/WalletFramework.Oid4Vc/SeviceCollectionExtensions.cs b/src/WalletFramework.Oid4Vc/SeviceCollectionExtensions.cs index 63e9b2a1..d5d027b0 100644 --- a/src/WalletFramework.Oid4Vc/SeviceCollectionExtensions.cs +++ b/src/WalletFramework.Oid4Vc/SeviceCollectionExtensions.cs @@ -16,7 +16,6 @@ using WalletFramework.Oid4Vc.Oid4Vp.PresentationExchange.Services; using WalletFramework.Oid4Vc.Oid4Vp.Services; using WalletFramework.SdJwtVc; -using WalletFramework.SdJwtVc.Services.SdJwtVcHolderService; namespace WalletFramework.Oid4Vc;