diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fcf0ac128..eb0fa85dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,7 +56,7 @@ jobs: verbose: true BDDTest: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v4 @@ -64,6 +64,7 @@ jobs: uses: actions/setup-go@v2 with: go-version: '${{ env.GO_VERSION }}' + - run: sudo apt-get update && sudo apt install docker-compose -y - name: Run BDD tests run: | echo '127.0.0.1 file-server.trustbloc.local' | sudo tee -a /etc/hosts diff --git a/Makefile b/Makefile index e7893564c..d68ae9fd7 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ ALPINE_IMAGE ?=alpine OPENSSL_IMAGE ?=frapsoft/openssl GOPROXY ?= https://proxy.golang.org -VC_FRAMEWORK_VERSION = 8cb417a4df0f7f7fcc18ab023c800c01c41b653f +VC_FRAMEWORK_VERSION = c7c2ade0e1fc839f572fbfd760ab846f3467acca KMS_FRAMEWORK_VERSION = 59c2830d27fd44f9a3a663242a4aa61544ce622e DID_GO_VERSION = aa500e57d8bdf51c90c20d3a6c815fdc76f716c3 SIDE_TREE_VERSION = f4260aff710479ba5fa3f0c61b51d451d9041225 diff --git a/api/spec/openapi.gen.go b/api/spec/openapi.gen.go index 514adf4dc..94fc1a9e4 100644 --- a/api/spec/openapi.gen.go +++ b/api/spec/openapi.gen.go @@ -19,200 +19,210 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", + "ZvVmnP6r/uPdp7v/HwAA//8MB7GU4zIBAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/cmd/vc-rest/go.mod b/cmd/vc-rest/go.mod index b6f6eeb1a..2475e1157 100644 --- a/cmd/vc-rest/go.mod +++ b/cmd/vc-rest/go.mod @@ -22,10 +22,10 @@ require ( github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 github.com/trustbloc/cmdutil-go v1.0.0 - github.com/trustbloc/did-go v1.2.1 + github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 github.com/trustbloc/logutil-go v1.0.0 github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104 - github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f + github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc github.com/trustbloc/vcs v0.0.0 github.com/trustbloc/vcs/component/credentialstatus v0.0.0-00010101000000-000000000000 github.com/trustbloc/vcs/component/echo v0.0.0-00010101000000-000000000000 diff --git a/cmd/vc-rest/go.sum b/cmd/vc-rest/go.sum index 3d91be1f3..9f297f1ca 100644 --- a/cmd/vc-rest/go.sum +++ b/cmd/vc-rest/go.sum @@ -588,16 +588,16 @@ github.com/trustbloc/bbs-signature-go v1.0.2 h1:gepEsbLiZHv/vva9FKG5gF38mGtOIyGe github.com/trustbloc/bbs-signature-go v1.0.2/go.mod h1:xYotcXHAbcE0TO+SteW0J6XI3geQaXq4wdnXR2k+XCU= github.com/trustbloc/cmdutil-go v1.0.0 h1:QCe7wVEIASWmy9ZDD0l0tsQCEsX6fx+kBFX5UqCVRdk= github.com/trustbloc/cmdutil-go v1.0.0/go.mod h1:o/v7C1z6d/5UrjaC6GAUc1hk0XVuE3M4tpyvsMMUw5k= -github.com/trustbloc/did-go v1.2.1 h1:SEOmPX+x2JlE6+jjjJp82yCCusoJ4/67zQ2ST6ytfSc= -github.com/trustbloc/did-go v1.2.1/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 h1:LpX6reFIcahgTxuDLrV9yro1gENtXQLv2NsneX5hWkc= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= github.com/trustbloc/kms-go v1.1.2 h1:nAlhDoHkSyX1eQFRz/sJsdgmJuNadyX7FJEy/9ROwys= github.com/trustbloc/kms-go v1.1.2/go.mod h1:OKOtsLbE6W5s4mpjWkvk8XEqcmt9vTgVmDNkHELpWO0= github.com/trustbloc/logutil-go v1.0.0 h1:KzNs9TRbnmn+M3oYw9UkrtOjNd3ZGO8aLgfYttMypcE= github.com/trustbloc/logutil-go v1.0.0/go.mod h1:JlxT0oZfNKgIlSNtgc001WEeDMxlnAvOM43gNm8DQVc= github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104 h1:0IW4muaGvhjJ4OkG6/PQG3DGf5POWxlA1wwEYsxWQ+4= github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104/go.mod h1:3yChjB5KOT7B9eZe0W1XaIx3MNUuC1Oe9nR/GCtI1W0= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f h1:v/Gf89EK2vCk2uj9bK+zmoTAQiJEXH9I2p3fYBiNLrg= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f/go.mod h1:7GkmcXtK5FNbfxypAZAVg8eM9PVnGJ/lmQo7gQgsjKk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc h1:6/AZPtL61aybL8FsAgZBtaq89zX4ahgRqa20aEKP1fk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc/go.mod h1:l70VUyzJ+jAxGSlS+V8jiP5GnVcxFfV4bPxJ0grlu14= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= diff --git a/cmd/vc-rest/startcmd/start.go b/cmd/vc-rest/startcmd/start.go index 9eba538c9..e09eba2f1 100644 --- a/cmd/vc-rest/startcmd/start.go +++ b/cmd/vc-rest/startcmd/start.go @@ -81,6 +81,7 @@ import ( "github.com/trustbloc/vcs/pkg/restapi/v1/mw" oidc4civ1 "github.com/trustbloc/vcs/pkg/restapi/v1/oidc4ci" oidc4vpv1 "github.com/trustbloc/vcs/pkg/restapi/v1/oidc4vp" + "github.com/trustbloc/vcs/pkg/restapi/v1/refresh" verifierv1 "github.com/trustbloc/vcs/pkg/restapi/v1/verifier" "github.com/trustbloc/vcs/pkg/restapi/v1/version" "github.com/trustbloc/vcs/pkg/service/clientidscheme" @@ -90,6 +91,7 @@ import ( "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/service/oidc4ci" "github.com/trustbloc/vcs/pkg/service/oidc4vp" + refresh2 "github.com/trustbloc/vcs/pkg/service/refresh" "github.com/trustbloc/vcs/pkg/service/requestobject" "github.com/trustbloc/vcs/pkg/service/trustregistry" "github.com/trustbloc/vcs/pkg/service/verifycredential" @@ -702,6 +704,11 @@ func buildEchoHandler( ProfileSvc: issuerProfileSvc, }) + prepareCredentialSvc := issuecredential.NewPrepareCredentialService(&issuecredential.PrepareCredentialServiceConfig{ + VcsAPIURL: conf.StartupParameters.hostURLExternal, + Composer: issuecredential.NewCredentialComposer(), + }) + oidc4ciService, err = oidc4ci.NewService(&oidc4ci.Config{ TransactionStore: oidc4ciTransactionStore, ClaimDataStore: oidc4ciClaimDataStore, @@ -720,8 +727,8 @@ func buildEchoHandler( JSONSchemaValidator: jsonSchemaValidator, TrustRegistry: trustRegistryService, AckService: ackService, - Composer: oidc4ci.NewCredentialComposer(), DocumentLoader: documentLoader, + PrepareCredential: prepareCredentialSvc, }) if err != nil { return nil, fmt.Errorf("failed to instantiate new oidc4ci service: %w", err) @@ -824,6 +831,30 @@ func buildEchoHandler( ) } + var verifyPresentationSvc verifypresentation.ServiceInterface + + verifyPresentationSvc = verifypresentation.New(&verifypresentation.Config{ + VcVerifier: verifyCredentialSvc, + DocumentLoader: documentLoader, + VDR: conf.VDR, + }) + + if conf.IsTraceEnabled { + verifyPresentationSvc = verifypresentationtracing.Wrap(verifyPresentationSvc, conf.Tracer) + } + + refreshService := refresh2.NewRefreshService(&refresh2.Config{ + VcsAPIURL: conf.StartupParameters.apiGatewayURL, + TxStore: oidc4ciTransactionStore, + ClaimsStore: oidc4ciClaimDataStore, + DataProtector: claimsDataProtector, + PresentationVerifier: verifyPresentationSvc, + CredentialIssuer: prepareCredentialSvc, + IssueCredentialService: issueCredentialSvc, + EventPublisher: eventSvc, + EventTopic: conf.StartupParameters.issuerEventTopic, + }) + oidc4civ1.RegisterHandlers(e, oidc4civ1.NewController(&oidc4civ1.Config{ OAuth2Provider: oauthProvider, StateStore: oidc4ciStateStore, @@ -845,6 +876,14 @@ func buildEchoHandler( LDPProofParser: oidc4civ1.NewDefaultLDPProofParser(), })) + refresh.RegisterHandlers(e, refresh.NewController(&refresh.Config{ + RefreshService: refreshService, + ProfileService: issuerProfileSvc, + ProofChecker: proofChecker, + DocumentLoader: documentLoader, + IssuerVCSPublicHost: conf.StartupParameters.hostURLExternal, + })) + oidc4vpv1.RegisterHandlers(e, oidc4vpv1.NewController(&oidc4vpv1.Config{ HTTPClient: getHTTPClient(metricsProvider.ClientOIDC4PV1), ExternalHostURL: conf.StartupParameters.hostURLExternal, // use host external as this url will be called internally @@ -864,6 +903,7 @@ func buildEchoHandler( Tracer: conf.Tracer, OpenidIssuerConfigProvider: openidCredentialIssuerConfigProviderSvc, JSONSchemaValidator: jsonSchemaValidator, + CredentialRefreshService: refreshService, })) // Verifier Profile Management API @@ -878,18 +918,6 @@ func buildEchoHandler( return nil, err } - var verifyPresentationSvc verifypresentation.ServiceInterface - - verifyPresentationSvc = verifypresentation.New(&verifypresentation.Config{ - VcVerifier: verifyCredentialSvc, - DocumentLoader: documentLoader, - VDR: conf.VDR, - }) - - if conf.IsTraceEnabled { - verifyPresentationSvc = verifypresentationtracing.Wrap(verifyPresentationSvc, conf.Tracer) - } - oidc4vpTxStore, err := getOIDC4VPTxStore( conf.StartupParameters.transientDataParams.storeType, redisClient, diff --git a/component/credentialstatus/go.mod b/component/credentialstatus/go.mod index 30c484247..4349d00b7 100644 --- a/component/credentialstatus/go.mod +++ b/component/credentialstatus/go.mod @@ -14,11 +14,11 @@ require ( github.com/piprate/json-gold v0.5.1-0.20230111113000-6ddbe6e6f19f github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 - github.com/trustbloc/did-go v1.2.1 + github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 github.com/trustbloc/kms-go v1.1.2 github.com/trustbloc/logutil-go v1.0.0 github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104 - github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f + github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc github.com/trustbloc/vcs v0.0.0-00010101000000-000000000000 ) diff --git a/component/credentialstatus/go.sum b/component/credentialstatus/go.sum index 9827da2d0..94c552303 100644 --- a/component/credentialstatus/go.sum +++ b/component/credentialstatus/go.sum @@ -447,16 +447,16 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/trustbloc/bbs-signature-go v1.0.2 h1:gepEsbLiZHv/vva9FKG5gF38mGtOIyGez7desZxiI1o= github.com/trustbloc/bbs-signature-go v1.0.2/go.mod h1:xYotcXHAbcE0TO+SteW0J6XI3geQaXq4wdnXR2k+XCU= -github.com/trustbloc/did-go v1.2.1 h1:SEOmPX+x2JlE6+jjjJp82yCCusoJ4/67zQ2ST6ytfSc= -github.com/trustbloc/did-go v1.2.1/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 h1:LpX6reFIcahgTxuDLrV9yro1gENtXQLv2NsneX5hWkc= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= github.com/trustbloc/kms-go v1.1.2 h1:nAlhDoHkSyX1eQFRz/sJsdgmJuNadyX7FJEy/9ROwys= github.com/trustbloc/kms-go v1.1.2/go.mod h1:OKOtsLbE6W5s4mpjWkvk8XEqcmt9vTgVmDNkHELpWO0= github.com/trustbloc/logutil-go v1.0.0 h1:KzNs9TRbnmn+M3oYw9UkrtOjNd3ZGO8aLgfYttMypcE= github.com/trustbloc/logutil-go v1.0.0/go.mod h1:JlxT0oZfNKgIlSNtgc001WEeDMxlnAvOM43gNm8DQVc= github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104 h1:0IW4muaGvhjJ4OkG6/PQG3DGf5POWxlA1wwEYsxWQ+4= github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104/go.mod h1:3yChjB5KOT7B9eZe0W1XaIx3MNUuC1Oe9nR/GCtI1W0= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f h1:v/Gf89EK2vCk2uj9bK+zmoTAQiJEXH9I2p3fYBiNLrg= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f/go.mod h1:7GkmcXtK5FNbfxypAZAVg8eM9PVnGJ/lmQo7gQgsjKk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc h1:6/AZPtL61aybL8FsAgZBtaq89zX4ahgRqa20aEKP1fk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc/go.mod h1:l70VUyzJ+jAxGSlS+V8jiP5GnVcxFfV4bPxJ0grlu14= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= diff --git a/component/event/go.mod b/component/event/go.mod index 7e757454a..baf41dfd0 100644 --- a/component/event/go.mod +++ b/component/event/go.mod @@ -13,7 +13,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/trustbloc/cmdutil-go v1.0.0 github.com/trustbloc/logutil-go v1.0.0 - github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f + github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc github.com/trustbloc/vcs v0.0.0-00010101000000-000000000000 go.opentelemetry.io/otel/trace v1.22.0 ) @@ -110,7 +110,7 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/trustbloc/bbs-signature-go v1.0.2 // indirect - github.com/trustbloc/did-go v1.2.1 // indirect + github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 // indirect github.com/trustbloc/kms-go v1.1.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect diff --git a/component/event/go.sum b/component/event/go.sum index 5cc5bda62..1d87c9498 100644 --- a/component/event/go.sum +++ b/component/event/go.sum @@ -433,14 +433,14 @@ github.com/trustbloc/bbs-signature-go v1.0.2 h1:gepEsbLiZHv/vva9FKG5gF38mGtOIyGe github.com/trustbloc/bbs-signature-go v1.0.2/go.mod h1:xYotcXHAbcE0TO+SteW0J6XI3geQaXq4wdnXR2k+XCU= github.com/trustbloc/cmdutil-go v1.0.0 h1:QCe7wVEIASWmy9ZDD0l0tsQCEsX6fx+kBFX5UqCVRdk= github.com/trustbloc/cmdutil-go v1.0.0/go.mod h1:o/v7C1z6d/5UrjaC6GAUc1hk0XVuE3M4tpyvsMMUw5k= -github.com/trustbloc/did-go v1.2.1 h1:SEOmPX+x2JlE6+jjjJp82yCCusoJ4/67zQ2ST6ytfSc= -github.com/trustbloc/did-go v1.2.1/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 h1:LpX6reFIcahgTxuDLrV9yro1gENtXQLv2NsneX5hWkc= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= github.com/trustbloc/kms-go v1.1.2 h1:nAlhDoHkSyX1eQFRz/sJsdgmJuNadyX7FJEy/9ROwys= github.com/trustbloc/kms-go v1.1.2/go.mod h1:OKOtsLbE6W5s4mpjWkvk8XEqcmt9vTgVmDNkHELpWO0= github.com/trustbloc/logutil-go v1.0.0 h1:KzNs9TRbnmn+M3oYw9UkrtOjNd3ZGO8aLgfYttMypcE= github.com/trustbloc/logutil-go v1.0.0/go.mod h1:JlxT0oZfNKgIlSNtgc001WEeDMxlnAvOM43gNm8DQVc= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f h1:v/Gf89EK2vCk2uj9bK+zmoTAQiJEXH9I2p3fYBiNLrg= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f/go.mod h1:7GkmcXtK5FNbfxypAZAVg8eM9PVnGJ/lmQo7gQgsjKk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc h1:6/AZPtL61aybL8FsAgZBtaq89zX4ahgRqa20aEKP1fk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc/go.mod h1:l70VUyzJ+jAxGSlS+V8jiP5GnVcxFfV4bPxJ0grlu14= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= diff --git a/component/profile/reader/file/go.mod b/component/profile/reader/file/go.mod index a0f897bad..c4b00404c 100644 --- a/component/profile/reader/file/go.mod +++ b/component/profile/reader/file/go.mod @@ -13,7 +13,7 @@ require ( github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 github.com/trustbloc/cmdutil-go v1.0.0 - github.com/trustbloc/did-go v1.2.1 + github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 github.com/trustbloc/kms-go v1.1.2 github.com/trustbloc/logutil-go v1.0.0 github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104 @@ -120,7 +120,7 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/trustbloc/bbs-signature-go v1.0.2 // indirect - github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f // indirect + github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.1 // indirect github.com/veraison/go-cose v1.1.1-0.20240126165338-2300d5c96dbd // indirect diff --git a/component/profile/reader/file/go.sum b/component/profile/reader/file/go.sum index 0a725c647..3e1b2a417 100644 --- a/component/profile/reader/file/go.sum +++ b/component/profile/reader/file/go.sum @@ -451,16 +451,16 @@ github.com/trustbloc/bbs-signature-go v1.0.2 h1:gepEsbLiZHv/vva9FKG5gF38mGtOIyGe github.com/trustbloc/bbs-signature-go v1.0.2/go.mod h1:xYotcXHAbcE0TO+SteW0J6XI3geQaXq4wdnXR2k+XCU= github.com/trustbloc/cmdutil-go v1.0.0 h1:QCe7wVEIASWmy9ZDD0l0tsQCEsX6fx+kBFX5UqCVRdk= github.com/trustbloc/cmdutil-go v1.0.0/go.mod h1:o/v7C1z6d/5UrjaC6GAUc1hk0XVuE3M4tpyvsMMUw5k= -github.com/trustbloc/did-go v1.2.1 h1:SEOmPX+x2JlE6+jjjJp82yCCusoJ4/67zQ2ST6ytfSc= -github.com/trustbloc/did-go v1.2.1/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 h1:LpX6reFIcahgTxuDLrV9yro1gENtXQLv2NsneX5hWkc= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= github.com/trustbloc/kms-go v1.1.2 h1:nAlhDoHkSyX1eQFRz/sJsdgmJuNadyX7FJEy/9ROwys= github.com/trustbloc/kms-go v1.1.2/go.mod h1:OKOtsLbE6W5s4mpjWkvk8XEqcmt9vTgVmDNkHELpWO0= github.com/trustbloc/logutil-go v1.0.0 h1:KzNs9TRbnmn+M3oYw9UkrtOjNd3ZGO8aLgfYttMypcE= github.com/trustbloc/logutil-go v1.0.0/go.mod h1:JlxT0oZfNKgIlSNtgc001WEeDMxlnAvOM43gNm8DQVc= github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104 h1:0IW4muaGvhjJ4OkG6/PQG3DGf5POWxlA1wwEYsxWQ+4= github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104/go.mod h1:3yChjB5KOT7B9eZe0W1XaIx3MNUuC1Oe9nR/GCtI1W0= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f h1:v/Gf89EK2vCk2uj9bK+zmoTAQiJEXH9I2p3fYBiNLrg= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f/go.mod h1:7GkmcXtK5FNbfxypAZAVg8eM9PVnGJ/lmQo7gQgsjKk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc h1:6/AZPtL61aybL8FsAgZBtaq89zX4ahgRqa20aEKP1fk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc/go.mod h1:l70VUyzJ+jAxGSlS+V8jiP5GnVcxFfV4bPxJ0grlu14= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= diff --git a/component/profile/reader/file/reader_test.go b/component/profile/reader/file/reader_test.go index 3e76da25d..7a34df9b6 100644 --- a/component/profile/reader/file/reader_test.go +++ b/component/profile/reader/file/reader_test.go @@ -1,6 +1,5 @@ /* Copyright Gen Digital Inc. All Rights Reserved. - SPDX-License-Identifier: Apache-2.0 */ diff --git a/component/wallet-cli/cmd/refresh_cmd.go b/component/wallet-cli/cmd/refresh_cmd.go new file mode 100644 index 000000000..7dcc75c0d --- /dev/null +++ b/component/wallet-cli/cmd/refresh_cmd.go @@ -0,0 +1,100 @@ +package cmd + +import ( + "context" + "fmt" + "net/http" + "net/url" + + "github.com/henvic/httpretty" + "github.com/spf13/cobra" + + "github.com/trustbloc/vcs/component/wallet-cli/internal/formatter" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/refresh" +) + +type refreshCommandFlags struct { + serviceFlags *walletFlags + proxyURL string + enableTracing bool + walletDIDIndex int +} + +func NewRefreshCmd() *cobra.Command { + flags := &refreshCommandFlags{ + serviceFlags: &walletFlags{}, + } + + return &cobra.Command{ + Use: "refresh", + Short: "Refresh credential", + Long: "Refresh credential", + RunE: func(cmd *cobra.Command, args []string) error { + w, svc, err := initWallet(flags.serviceFlags) + if err != nil { + return fmt.Errorf("init wallet: %w", err) + } + + httpTransport := &http.Transport{ + TLSClientConfig: svc.TLSConfig(), + } + + if flags.proxyURL != "" { + proxyURL, parseErr := url.Parse(flags.proxyURL) + if parseErr != nil { + return fmt.Errorf("parse proxy url: %w", parseErr) + } + + httpTransport.Proxy = http.ProxyURL(proxyURL) + } + + httpClient := &http.Client{ + Transport: httpTransport, + } + + if flags.enableTracing { + httpLogger := &httpretty.Logger{ + RequestHeader: true, + RequestBody: true, + ResponseHeader: true, + ResponseBody: true, + SkipSanitize: true, + Colors: true, + SkipRequestInfo: true, + Formatters: []httpretty.Formatter{&httpretty.JSONFormatter{}, &formatter.JWTFormatter{}}, + MaxResponseBody: 1e+7, + } + + httpClient.Transport = httpLogger.RoundTripper(httpClient.Transport) + } + var walletDIDIndex int + + if flags.walletDIDIndex != -1 { + walletDIDIndex = flags.walletDIDIndex + } else { + walletDIDIndex = len(w.DIDs()) - 1 + } + + provider := &oidc4vpProvider{ + storageProvider: svc.StorageProvider(), + httpClient: httpClient, + documentLoader: svc.DocumentLoader(), + vdrRegistry: svc.VDR(), + cryptoSuite: svc.CryptoSuite(), + wallet: w, + } + + var flow *refresh.Flow + + opts := []refresh.Opt{ + refresh.WithWalletDIDIndex(walletDIDIndex), + } + + if flow, err = refresh.NewFlow(provider, opts...); err != nil { + return err + } + + return flow.Run(context.Background()) + }, + } +} diff --git a/component/wallet-cli/go.mod b/component/wallet-cli/go.mod index b00e9ed3c..4c3fb4a1f 100644 --- a/component/wallet-cli/go.mod +++ b/component/wallet-cli/go.mod @@ -24,11 +24,11 @@ require ( github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 github.com/syndtr/goleveldb v1.0.0 - github.com/trustbloc/did-go v1.2.1 + github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 github.com/trustbloc/kms-go v1.1.2 github.com/trustbloc/logutil-go v1.0.0-rc1 github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104 - github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f + github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc github.com/trustbloc/vcs v0.0.0-00010101000000-000000000000 github.com/valyala/fastjson v1.6.3 github.com/veraison/go-cose v1.1.1-0.20240126165338-2300d5c96dbd diff --git a/component/wallet-cli/go.sum b/component/wallet-cli/go.sum index f7792ea15..f7af12fc6 100644 --- a/component/wallet-cli/go.sum +++ b/component/wallet-cli/go.sum @@ -556,16 +556,16 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/trustbloc/bbs-signature-go v1.0.2 h1:gepEsbLiZHv/vva9FKG5gF38mGtOIyGez7desZxiI1o= github.com/trustbloc/bbs-signature-go v1.0.2/go.mod h1:xYotcXHAbcE0TO+SteW0J6XI3geQaXq4wdnXR2k+XCU= -github.com/trustbloc/did-go v1.2.1 h1:SEOmPX+x2JlE6+jjjJp82yCCusoJ4/67zQ2ST6ytfSc= -github.com/trustbloc/did-go v1.2.1/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 h1:LpX6reFIcahgTxuDLrV9yro1gENtXQLv2NsneX5hWkc= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= github.com/trustbloc/kms-go v1.1.2 h1:nAlhDoHkSyX1eQFRz/sJsdgmJuNadyX7FJEy/9ROwys= github.com/trustbloc/kms-go v1.1.2/go.mod h1:OKOtsLbE6W5s4mpjWkvk8XEqcmt9vTgVmDNkHELpWO0= github.com/trustbloc/logutil-go v1.0.0-rc1 h1:rRJbvgQfrlUfyej+mY0nuQJymGqjRW4oZEwKi544F4c= github.com/trustbloc/logutil-go v1.0.0-rc1/go.mod h1:JlxT0oZfNKgIlSNtgc001WEeDMxlnAvOM43gNm8DQVc= github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104 h1:0IW4muaGvhjJ4OkG6/PQG3DGf5POWxlA1wwEYsxWQ+4= github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104/go.mod h1:3yChjB5KOT7B9eZe0W1XaIx3MNUuC1Oe9nR/GCtI1W0= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f h1:v/Gf89EK2vCk2uj9bK+zmoTAQiJEXH9I2p3fYBiNLrg= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f/go.mod h1:7GkmcXtK5FNbfxypAZAVg8eM9PVnGJ/lmQo7gQgsjKk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc h1:6/AZPtL61aybL8FsAgZBtaq89zX4ahgRqa20aEKP1fk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc/go.mod h1:l70VUyzJ+jAxGSlS+V8jiP5GnVcxFfV4bPxJ0grlu14= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= diff --git a/component/wallet-cli/internal/presentation/vp_token.go b/component/wallet-cli/internal/presentation/vp_token.go new file mode 100644 index 000000000..ba533ca87 --- /dev/null +++ b/component/wallet-cli/internal/presentation/vp_token.go @@ -0,0 +1,316 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package presentation + +import ( + "encoding/hex" + "fmt" + "time" + + "github.com/fxamacker/cbor/v2" + "github.com/google/uuid" + "github.com/piprate/json-gold/ld" + vdrapi "github.com/trustbloc/did-go/vdr/api" + "github.com/trustbloc/kms-go/spi/kms" + "github.com/trustbloc/kms-go/wrapper/api" + "github.com/trustbloc/vc-go/jwt" + "github.com/trustbloc/vc-go/presexch" + "github.com/trustbloc/vc-go/verifiable" + cwt2 "github.com/trustbloc/vc-go/verifiable/cwt" + "github.com/veraison/go-cose" + + jwssigner "github.com/trustbloc/vcs/component/wallet-cli/pkg/signer" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" + "github.com/trustbloc/vcs/pkg/doc/vc" + vccrypto "github.com/trustbloc/vcs/pkg/doc/vc/crypto" + vcs "github.com/trustbloc/vcs/pkg/doc/verifiable" + vcskms "github.com/trustbloc/vcs/pkg/kms" + kmssigner "github.com/trustbloc/vcs/pkg/kms/signer" + "github.com/trustbloc/vcs/pkg/observability/metrics/noop" +) + +const ( + tokenLifetimeSeconds = 600 +) + +type CreateVpTokenRequest struct { + ClientID string + Nonce string + VPFormats *presexch.Format + Wallet *wallet.Wallet + CryptoSuite api.Suite + VdrRegistry vdrapi.Registry + DocumentLoader ld.DocumentLoader +} + +type VPTokenClaims struct { + VP *verifiable.Presentation `json:"vp"` + Nonce string `json:"nonce"` + Exp int64 `json:"exp"` + Iss string `json:"iss"` + Aud string `json:"aud"` + Nbf int64 `json:"nbf"` + Iat int64 `json:"iat"` + Jti string `json:"jti"` +} + +func CreateVPToken( + presentations []*verifiable.Presentation, + req *CreateVpTokenRequest, +) ([]string, error) { + credential := presentations[0].Credentials()[0] + + subjectDID, err := verifiable.SubjectID(credential.Contents().Subject) + if err != nil { + return nil, fmt.Errorf("get subject did: %w", err) + } + + vpFormats := req.VPFormats + + var vpTokens []string + + for _, presentation := range presentations { + var ( + vpToken string + signErr error + ) + + switch { + case vpFormats.JwtVP != nil: + if vpToken, signErr = signPresentationJWT( + presentation, + subjectDID, + req, + ); signErr != nil { + return nil, signErr + } + case vpFormats.LdpVP != nil: + if vpToken, signErr = signPresentationLDP( + presentation, + vcs.SignatureType(vpFormats.LdpVP.ProofType[0]), + subjectDID, + req, + ); signErr != nil { + return nil, signErr + } + case vpFormats.CwtVP != nil: + if vpToken, signErr = signPresentationCWT( + presentation, + subjectDID, + req, + ); signErr != nil { + return nil, signErr + } + default: + return nil, fmt.Errorf("unsupported vp formats: %v", vpFormats) + } + + vpTokens = append(vpTokens, vpToken) + } + + return vpTokens, nil +} + +func signPresentationCWT( + vp *verifiable.Presentation, + signerDID string, + req *CreateVpTokenRequest, +) (string, error) { + var ( + kmsKeyID string + //kmsKeyType kms.KeyType + coseAlgo cose.Algorithm + err error + ) + + for _, didInfo := range req.Wallet.DIDs() { + if didInfo.ID == signerDID { + kmsKeyID = didInfo.KeyID + + coseAlgo, err = verifiable.KeyTypeToCWSAlgo(didInfo.KeyType) + if err != nil { + return "", fmt.Errorf("convert key type to cose algorithm: %w", err) + } + + break + } + } + + signer, err := req.CryptoSuite.FixedKeyMultiSigner(kmsKeyID) + if err != nil { + return "", fmt.Errorf("create signer for key %s: %w", kmsKeyID, err) + } + + kmsSigner := kmssigner.NewKMSSigner(signer, req.Wallet.SignatureType(), nil) + + claims := VPTokenClaims{ + VP: vp, + Nonce: req.Nonce, + Exp: time.Now().Unix() + tokenLifetimeSeconds, + Iss: signerDID, + Aud: req.ClientID, + Nbf: time.Now().Unix(), + Iat: time.Now().Unix(), + Jti: uuid.NewString(), + } + + // + payload, err := cbor.Marshal(claims) + if err != nil { + return "", fmt.Errorf("marshal cbor claims: %w", err) + } + + msg := &cose.Sign1Message{ + Headers: cose.Headers{ + Protected: cose.ProtectedHeader{ + cose.HeaderLabelAlgorithm: coseAlgo, + cose.HeaderLabelKeyID: []byte(kmsKeyID), + }, + Unprotected: cose.UnprotectedHeader{ + cose.HeaderLabelContentType: "application/vc+ld+json+cose", + }, + }, + Payload: payload, + } + + //verifiable.KeyTypeToCWSAlgo(f.wallet.SignatureType() + signData, err := cwt2.GetProofValue(msg) + if err != nil { + return "", err + } + + signed, err := kmsSigner.Sign(signData) + if err != nil { + return "", err + } + + msg.Signature = signed + + final, err := cbor.Marshal(msg) + if err != nil { + return "", err + } + + return hex.EncodeToString(final), nil +} + +func signPresentationJWT( + vp *verifiable.Presentation, + signerDID string, + req *CreateVpTokenRequest, +) (string, error) { + docResolution, err := req.VdrRegistry.Resolve(signerDID) + if err != nil { + return "", fmt.Errorf("resolve signer did: %w", err) + } + + verificationMethod := docResolution.DIDDocument.VerificationMethod[0] + + var kmsKeyID string + + for _, didInfo := range req.Wallet.DIDs() { + if didInfo.ID == signerDID { + kmsKeyID = didInfo.KeyID + break + } + } + + signer, err := req.CryptoSuite.FixedKeyMultiSigner(kmsKeyID) + if err != nil { + return "", fmt.Errorf("create signer for key %s: %w", kmsKeyID, err) + } + + kmsSigner := kmssigner.NewKMSSigner(signer, req.Wallet.SignatureType(), nil) + + claims := VPTokenClaims{ + VP: vp, + Nonce: req.Nonce, + Exp: time.Now().Unix() + tokenLifetimeSeconds, + Iss: signerDID, + Aud: req.ClientID, + Nbf: time.Now().Unix(), + Iat: time.Now().Unix(), + Jti: uuid.NewString(), + } + + signedJWT, err := jwt.NewJoseSigned( + claims, + map[string]interface{}{"typ": "JWT"}, + jwssigner.NewJWSSigner( + verificationMethod.ID, + string(req.Wallet.SignatureType()), + kmsSigner, + ), + ) + if err != nil { + return "", fmt.Errorf("create signed jwt: %w", err) + } + + jws, err := signedJWT.Serialize(false) + if err != nil { + return "", fmt.Errorf("serialize signed jwt: %w", err) + } + + return jws, nil +} + +func signPresentationLDP( + vp *verifiable.Presentation, + signatureType vcs.SignatureType, + signerDID string, + req *CreateVpTokenRequest, +) (string, error) { + cryptoSigner := vccrypto.New(req.VdrRegistry, req.DocumentLoader) + + vp.Context = append(vp.Context, "https://w3id.org/security/suites/jws-2020/v1") + + docResolution, err := req.VdrRegistry.Resolve(signerDID) + if err != nil { + return "", fmt.Errorf("resolve signer did: %w", err) + } + + verificationMethod := docResolution.DIDDocument.VerificationMethod[0] + + var ( + kmsKeyID string + kmsKeyType kms.KeyType + ) + + for _, didInfo := range req.Wallet.DIDs() { + if didInfo.ID == signerDID { + kmsKeyID = didInfo.KeyID + kmsKeyType = didInfo.KeyType + break + } + } + + signedVP, err := cryptoSigner.SignPresentation( + &vc.Signer{ + Creator: verificationMethod.ID, + KeyType: kmsKeyType, + KMSKeyID: kmsKeyID, + SignatureType: signatureType, + SignatureRepresentation: verifiable.SignatureProofValue, + KMS: vcskms.GetAriesKeyManager(req.CryptoSuite, vcskms.Local, noop.GetMetrics()), + }, + vp, + vccrypto.WithChallenge(req.Nonce), + vccrypto.WithDomain(req.ClientID), + ) + if err != nil { + return "", fmt.Errorf("sign vp: %w", err) + } + + var b []byte + + b, err = signedVP.MarshalJSON() + if err != nil { + return "", fmt.Errorf("marshal signed vp: %w", err) + } + + return string(b), nil +} diff --git a/component/wallet-cli/main.go b/component/wallet-cli/main.go index a4bd85ce6..0123141c4 100644 --- a/component/wallet-cli/main.go +++ b/component/wallet-cli/main.go @@ -28,6 +28,7 @@ func main() { rootCmd.AddCommand(cmd.NewAttestWalletCommand()) rootCmd.AddCommand(cmd.NewOIDC4VCICommand()) rootCmd.AddCommand(cmd.NewOIDC4VPCommand()) + rootCmd.AddCommand(cmd.NewRefreshCmd()) if err := rootCmd.Execute(); err != nil { slog.Error("failed to run wallet-cli", "err", err) diff --git a/component/wallet-cli/pkg/oidc4vp/models.go b/component/wallet-cli/pkg/oidc4vp/models.go index 0d77c930c..3e559b4fd 100644 --- a/component/wallet-cli/pkg/oidc4vp/models.go +++ b/component/wallet-cli/pkg/oidc4vp/models.go @@ -10,7 +10,6 @@ import ( "time" "github.com/trustbloc/vc-go/presexch" - "github.com/trustbloc/vc-go/verifiable" ) type RequestObject struct { @@ -52,17 +51,6 @@ type IDTokenClaims struct { Attachments map[string]string `json:"_attachments"` } -type VPTokenClaims struct { - VP *verifiable.Presentation `json:"vp"` - Nonce string `json:"nonce"` - Exp int64 `json:"exp"` - Iss string `json:"iss"` - Aud string `json:"aud"` - Nbf int64 `json:"nbf"` - Iat int64 `json:"iat"` - Jti string `json:"jti"` -} - type PerfInfo struct { FetchRequestObject time.Duration `json:"vp_fetch_request_object"` VerifyAuthorizationRequest time.Duration `json:"vp_verify_authorization_request"` diff --git a/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go b/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go index a9ab83ffc..e00459035 100644 --- a/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go +++ b/component/wallet-cli/pkg/oidc4vp/oidc4vp_flow.go @@ -9,7 +9,6 @@ package oidc4vp import ( "bytes" "context" - "encoding/hex" "encoding/json" "fmt" "io" @@ -20,42 +19,34 @@ import ( "strings" "time" - "github.com/fxamacker/cbor/v2" "github.com/google/uuid" "github.com/jinzhu/copier" "github.com/piprate/json-gold/ld" "github.com/trustbloc/did-go/doc/did" vdrapi "github.com/trustbloc/did-go/vdr/api" "github.com/trustbloc/kms-go/doc/jose" - "github.com/trustbloc/kms-go/spi/kms" "github.com/trustbloc/kms-go/wrapper/api" didconfigclient "github.com/trustbloc/vc-go/didconfig/client" "github.com/trustbloc/vc-go/jwt" "github.com/trustbloc/vc-go/presexch" "github.com/trustbloc/vc-go/proof/defaults" "github.com/trustbloc/vc-go/verifiable" - cwt2 "github.com/trustbloc/vc-go/verifiable/cwt" "github.com/trustbloc/vc-go/vermethod" - "github.com/veraison/go-cose" + "github.com/trustbloc/vcs/component/wallet-cli/internal/presentation" "github.com/trustbloc/vcs/component/wallet-cli/pkg/attestation" jwssigner "github.com/trustbloc/vcs/component/wallet-cli/pkg/signer" "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" - "github.com/trustbloc/vcs/pkg/doc/vc" - vccrypto "github.com/trustbloc/vcs/pkg/doc/vc/crypto" - vcs "github.com/trustbloc/vcs/pkg/doc/verifiable" - vcskms "github.com/trustbloc/vcs/pkg/kms" kmssigner "github.com/trustbloc/vcs/pkg/kms/signer" - "github.com/trustbloc/vcs/pkg/observability/metrics/noop" ) const ( linkedDomainsService = "LinkedDomains" - tokenLifetimeSeconds = 600 scopeOpenID = "openid" customScopeTimeDetails = "timedetails" customScopeWalletDetails = "walletdetails" + tokenLifetimeSeconds = 600 ) type AttestationService interface { @@ -459,7 +450,7 @@ func (f *Flow) sendAuthorizationResponse( v.Add("id_token", idToken) - vpTokens, err := f.createVPToken(presentations, requestObject) + vpTokens, err := f.CreateVPToken(presentations, requestObject) if err != nil { return fmt.Errorf("create vp token: %w", err) } @@ -488,263 +479,19 @@ func (f *Flow) sendAuthorizationResponse( return f.postAuthorizationResponse(ctx, requestObject.ResponseURI, []byte(v.Encode())) } -func (f *Flow) createVPToken( +func (f *Flow) CreateVPToken( presentations []*verifiable.Presentation, requestObject *RequestObject, ) ([]string, error) { - credential := presentations[0].Credentials()[0] - - subjectDID, err := verifiable.SubjectID(credential.Contents().Subject) - if err != nil { - return nil, fmt.Errorf("get subject did: %w", err) - } - - vpFormats := requestObject.ClientMetadata.VPFormats - - var vpTokens []string - - for _, presentation := range presentations { - var ( - vpToken string - signErr error - ) - - switch { - case vpFormats.JwtVP != nil: - if vpToken, signErr = f.signPresentationJWT( - presentation, - subjectDID, - requestObject.ClientID, - requestObject.Nonce, - ); signErr != nil { - return nil, signErr - } - case vpFormats.LdpVP != nil: - if vpToken, signErr = f.signPresentationLDP( - presentation, - vcs.SignatureType(vpFormats.LdpVP.ProofType[0]), - subjectDID, - requestObject.ClientID, - requestObject.Nonce, - ); signErr != nil { - return nil, signErr - } - case vpFormats.CwtVP != nil: - if vpToken, signErr = f.signPresentationCWT( - presentation, - subjectDID, - requestObject.ClientID, - requestObject.Nonce, - ); signErr != nil { - return nil, signErr - } - default: - return nil, fmt.Errorf("unsupported vp formats: %v", vpFormats) - } - - vpTokens = append(vpTokens, vpToken) - } - - return vpTokens, nil -} - -func (f *Flow) signPresentationCWT( - vp *verifiable.Presentation, - signerDID, - clientID, - nonce string, -) (string, error) { - var ( - kmsKeyID string - //kmsKeyType kms.KeyType - coseAlgo cose.Algorithm - err error - ) - - for _, didInfo := range f.wallet.DIDs() { - if didInfo.ID == signerDID { - kmsKeyID = didInfo.KeyID - - coseAlgo, err = verifiable.KeyTypeToCWSAlgo(didInfo.KeyType) - if err != nil { - return "", fmt.Errorf("convert key type to cose algorithm: %w", err) - } - - break - } - } - - signer, err := f.cryptoSuite.FixedKeyMultiSigner(kmsKeyID) - if err != nil { - return "", fmt.Errorf("create signer for key %s: %w", kmsKeyID, err) - } - - kmsSigner := kmssigner.NewKMSSigner(signer, f.wallet.SignatureType(), nil) - - claims := VPTokenClaims{ - VP: vp, - Nonce: nonce, - Exp: time.Now().Unix() + tokenLifetimeSeconds, - Iss: signerDID, - Aud: clientID, - Nbf: time.Now().Unix(), - Iat: time.Now().Unix(), - Jti: uuid.NewString(), - } - - // - payload, err := cbor.Marshal(claims) - if err != nil { - return "", fmt.Errorf("marshal cbor claims: %w", err) - } - - msg := &cose.Sign1Message{ - Headers: cose.Headers{ - Protected: cose.ProtectedHeader{ - cose.HeaderLabelAlgorithm: coseAlgo, - cose.HeaderLabelKeyID: []byte(kmsKeyID), - }, - Unprotected: cose.UnprotectedHeader{ - cose.HeaderLabelContentType: "application/vc+ld+json+cose", - }, - }, - Payload: payload, - } - - //verifiable.KeyTypeToCWSAlgo(f.wallet.SignatureType() - signData, err := cwt2.GetProofValue(msg) - if err != nil { - return "", err - } - - signed, err := kmsSigner.Sign(signData) - if err != nil { - return "", err - } - - msg.Signature = signed - - final, err := cbor.Marshal(msg) - if err != nil { - return "", err - } - - return hex.EncodeToString(final), nil -} - -func (f *Flow) signPresentationJWT( - vp *verifiable.Presentation, - signerDID, clientID, nonce string, -) (string, error) { - docResolution, err := f.vdrRegistry.Resolve(signerDID) - if err != nil { - return "", fmt.Errorf("resolve signer did: %w", err) - } - - verificationMethod := docResolution.DIDDocument.VerificationMethod[0] - - var kmsKeyID string - - for _, didInfo := range f.wallet.DIDs() { - if didInfo.ID == signerDID { - kmsKeyID = didInfo.KeyID - break - } - } - - signer, err := f.cryptoSuite.FixedKeyMultiSigner(kmsKeyID) - if err != nil { - return "", fmt.Errorf("create signer for key %s: %w", kmsKeyID, err) - } - - kmsSigner := kmssigner.NewKMSSigner(signer, f.wallet.SignatureType(), nil) - - claims := VPTokenClaims{ - VP: vp, - Nonce: nonce, - Exp: time.Now().Unix() + tokenLifetimeSeconds, - Iss: signerDID, - Aud: clientID, - Nbf: time.Now().Unix(), - Iat: time.Now().Unix(), - Jti: uuid.NewString(), - } - - signedJWT, err := jwt.NewJoseSigned( - claims, - map[string]interface{}{"typ": "JWT"}, - jwssigner.NewJWSSigner( - verificationMethod.ID, - string(f.wallet.SignatureType()), - kmsSigner, - ), - ) - if err != nil { - return "", fmt.Errorf("create signed jwt: %w", err) - } - - jws, err := signedJWT.Serialize(false) - if err != nil { - return "", fmt.Errorf("serialize signed jwt: %w", err) - } - - return jws, nil -} - -func (f *Flow) signPresentationLDP( - vp *verifiable.Presentation, - signatureType vcs.SignatureType, - signerDID, clientID, nonce string, -) (string, error) { - cryptoSigner := vccrypto.New(f.vdrRegistry, f.documentLoader) - - vp.Context = append(vp.Context, "https://w3id.org/security/suites/jws-2020/v1") - - docResolution, err := f.vdrRegistry.Resolve(signerDID) - if err != nil { - return "", fmt.Errorf("resolve signer did: %w", err) - } - - verificationMethod := docResolution.DIDDocument.VerificationMethod[0] - - var ( - kmsKeyID string - kmsKeyType kms.KeyType - ) - - for _, didInfo := range f.wallet.DIDs() { - if didInfo.ID == signerDID { - kmsKeyID = didInfo.KeyID - kmsKeyType = didInfo.KeyType - break - } - } - - signedVP, err := cryptoSigner.SignPresentation( - &vc.Signer{ - Creator: verificationMethod.ID, - KeyType: kmsKeyType, - KMSKeyID: kmsKeyID, - SignatureType: signatureType, - SignatureRepresentation: verifiable.SignatureProofValue, - KMS: vcskms.GetAriesKeyManager(f.cryptoSuite, vcskms.Local, noop.GetMetrics()), - }, - vp, - vccrypto.WithChallenge(nonce), - vccrypto.WithDomain(clientID), - ) - if err != nil { - return "", fmt.Errorf("sign vp: %w", err) - } - - var b []byte - - b, err = signedVP.MarshalJSON() - if err != nil { - return "", fmt.Errorf("marshal signed vp: %w", err) - } - - return string(b), nil + return presentation.CreateVPToken(presentations, &presentation.CreateVpTokenRequest{ + ClientID: requestObject.ClientID, + Nonce: requestObject.Nonce, + VPFormats: requestObject.ClientMetadata.VPFormats, + Wallet: f.wallet, + CryptoSuite: f.cryptoSuite, + VdrRegistry: f.vdrRegistry, + DocumentLoader: f.documentLoader, + }) } func (f *Flow) createIDToken( diff --git a/component/wallet-cli/pkg/refresh/refresh_flow.go b/component/wallet-cli/pkg/refresh/refresh_flow.go new file mode 100644 index 000000000..f4f38066d --- /dev/null +++ b/component/wallet-cli/pkg/refresh/refresh_flow.go @@ -0,0 +1,330 @@ +/* +Copyright Gen Digital Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package refresh + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log/slog" + "net/http" + "time" + + "github.com/piprate/json-gold/ld" + "github.com/trustbloc/did-go/doc/did" + vdrapi "github.com/trustbloc/did-go/vdr/api" + "github.com/trustbloc/kms-go/wrapper/api" + "github.com/trustbloc/vc-go/presexch" + "github.com/trustbloc/vc-go/verifiable" + + "github.com/trustbloc/vcs/component/wallet-cli/internal/presentation" + jwssigner "github.com/trustbloc/vcs/component/wallet-cli/pkg/signer" + "github.com/trustbloc/vcs/component/wallet-cli/pkg/wallet" + kmssigner "github.com/trustbloc/vcs/pkg/kms/signer" + "github.com/trustbloc/vcs/pkg/restapi/v1/refresh" +) + +type Flow struct { + httpClient *http.Client + signer *jwssigner.JWSSigner + perfInfo *PerfInfo + wallet *wallet.Wallet + documentLoader ld.DocumentLoader + cryptoSuite api.Suite + vdrRegistry vdrapi.Registry + history map[string]*CredentialHistory +} + +type CredentialHistory struct { + OldCredential *verifiable.Credential + NewCredential *verifiable.Credential +} + +type PerfInfo struct { + FullRefreshFlow time.Duration `json:"full_refresh_flow"` +} + +type Opt func(opts *options) + +func WithWalletDIDIndex(idx int) Opt { + return func(opts *options) { + opts.walletDIDIndex = idx + } +} + +type provider interface { + HTTPClient() *http.Client + DocumentLoader() ld.DocumentLoader + VDRegistry() vdrapi.Registry + Wallet() *wallet.Wallet + CryptoSuite() api.Suite +} + +type options struct { + walletDIDIndex int +} + +func NewFlow(p provider, opts ...Opt) (*Flow, error) { + o := &options{ + walletDIDIndex: len(p.Wallet().DIDs()) - 1, + } + + for i := range opts { + opts[i](o) + } + + if o.walletDIDIndex < 0 || o.walletDIDIndex >= len(p.Wallet().DIDs()) { + return nil, fmt.Errorf("invalid wallet did index: %d", o.walletDIDIndex) + } + + walletDIDInfo := p.Wallet().DIDs()[o.walletDIDIndex] + + walletDID, err := did.Parse(walletDIDInfo.ID) + if err != nil { + return nil, fmt.Errorf("parse wallet did: %w", err) + } + + docResolution, err := p.VDRegistry().Resolve(walletDID.String()) + if err != nil { + return nil, fmt.Errorf("resolve wallet did: %w", err) + } + + signer, err := p.CryptoSuite().FixedKeyMultiSigner(walletDIDInfo.KeyID) + if err != nil { + return nil, fmt.Errorf("create signer for key %s: %w", walletDIDInfo.KeyID, err) + } + + signatureType := p.Wallet().SignatureType() + + jwsSigner := jwssigner.NewJWSSigner( + docResolution.DIDDocument.VerificationMethod[0].ID, + string(signatureType), + kmssigner.NewKMSSigner(signer, signatureType, nil), + ) + + return &Flow{ + httpClient: p.HTTPClient(), + signer: jwsSigner, + documentLoader: p.DocumentLoader(), + vdrRegistry: p.VDRegistry(), + cryptoSuite: p.CryptoSuite(), + wallet: p.Wallet(), + history: make(map[string]*CredentialHistory), + perfInfo: &PerfInfo{}, + }, nil +} + +func (f *Flow) GetUpdatedCredentials() map[string]*CredentialHistory { + return f.history +} + +func (f *Flow) Run(ctx context.Context) error { + totalFlowStart := time.Now() + defer func() { + f.perfInfo.FullRefreshFlow = time.Since(totalFlowStart) + }() + + allCredentials, err := f.wallet.GetAll() + if err != nil { + return fmt.Errorf("get all credentials: %w", err) + } + + var finalErr error + + for v, cred := range allCredentials { + parsedCred, parseErr := verifiable.ParseCredential(cred, + verifiable.WithDisabledProofCheck(), + verifiable.WithJSONLDDocumentLoader(f.documentLoader), + ) + if parseErr != nil { + finalErr = errors.Join(finalErr, errors.Join(errors.New("failed to parse credential"), parseErr)) + continue + } + + updatedCred, updatedParsedCred, updateErr := f.fetchUpdateForCred(ctx, parsedCred) + + if updateErr != nil { + slog.Error("failed to fetch update for credential", updateErr) + finalErr = errors.Join(finalErr, updateErr) + + continue + } + + if updatedCred == nil { + continue + } + + if err = f.wallet.Delete(v); err != nil { + return fmt.Errorf("failed to delete old credential: %w", err) + } + + if err = f.wallet.Add(updatedCred, v); err != nil { + return fmt.Errorf("failed to add updated credential: %w", err) + } + + f.history[v] = &CredentialHistory{ + OldCredential: parsedCred, + NewCredential: updatedParsedCred, + } + + slog.Info(fmt.Sprintf("credential with key %v updated", v)) + } + + return finalErr +} + +func (f *Flow) fetchUpdateForCred(ctx context.Context, parsedCred *verifiable.Credential) ([]byte, *verifiable.Credential, error) { + credID := parsedCred.Contents().ID + refreshService := parsedCred.Contents().RefreshService + + if refreshService == nil { + slog.Info(fmt.Sprintf("no refresh service found for credential %s", credID)) + return nil, nil, nil + } + + if refreshService.Type != "VerifiableCredentialRefreshService2021" { + return nil, nil, fmt.Errorf("unexpected refresh service type: %s. Supported VerifiableCredentialRefreshService2021", + refreshService.Type) + } + + if refreshService.ID == "" { + return nil, nil, fmt.Errorf("refresh service endpoint is not set") + } + + slog.Info(fmt.Sprintf("fetching update for credential %s from %s", credID, refreshService.ID)) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, refreshService.ID, nil) + if err != nil { + return nil, nil, fmt.Errorf("create http request: %w", err) + } + + resp, err := f.httpClient.Do(req) + if err != nil { + return nil, nil, fmt.Errorf("send request to refresh service: %w", err) + } + + if resp.StatusCode == http.StatusNoContent { + slog.Info(fmt.Sprintf("no update available for credential %s", credID)) + return nil, nil, nil + } + + var body []byte + if resp.Body != nil { + body, _ = io.ReadAll(resp.Body) // nolint + } + + if resp.StatusCode != http.StatusOK { + return nil, nil, fmt.Errorf("unexpected status code %d and body: %s", resp.StatusCode, body) + } + + var parsed refresh.CredentialRefreshAvailableResponse + if err = json.Unmarshal(body, &parsed); err != nil { + return nil, nil, fmt.Errorf("parse response: %w", err) + } + + if len(parsed.VerifiablePresentationRequest.Interact.Service) == 0 { + return nil, nil, fmt.Errorf("no service endpoint found in presentation") + } + + interactEndpoint := parsed.VerifiablePresentationRequest.Interact.Service[0].ServiceEndpoint + + slog.Info(fmt.Sprintf("update available for credential %s: %s", credID, credID)) + + presDef, err := json.Marshal(parsed.VerifiablePresentationRequest.Query) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal presentation definition: %w", err) + } + + queryRes, _, err := f.wallet.Query(presDef, false, false) + if err != nil { + return nil, nil, fmt.Errorf("failed to query wallet: %w", err) + } + + if len(queryRes) != 1 { + return nil, nil, fmt.Errorf("expected 1 presentation, got %d", len(queryRes)) + } + + signedPres, err := presentation.CreateVPToken(queryRes, &presentation.CreateVpTokenRequest{ + ClientID: parsed.VerifiablePresentationRequest.Domain, + Nonce: parsed.VerifiablePresentationRequest.Challenge, + VPFormats: &presexch.Format{ + JwtVP: &presexch.JwtType{}, + }, + Wallet: f.wallet, + CryptoSuite: f.cryptoSuite, + VdrRegistry: f.vdrRegistry, + DocumentLoader: f.documentLoader, + }) + if err != nil { + return nil, nil, errors.Join(errors.New("failed to sign presentation"), err) + } + + reqBody, err := json.Marshal(refresh.GetRefreshedCredentialReq{ + VerifiablePresentation: []byte(signedPres[0]), + }) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal request body: %w", err) + } + + slog.Info(fmt.Sprintf("sending request to interact endpoint %s", interactEndpoint)) + + req, err = http.NewRequestWithContext(ctx, http.MethodPost, + interactEndpoint, + bytes.NewReader(reqBody), + ) + if err != nil { + return nil, nil, fmt.Errorf("create http request: %w", err) + } + + req.Header.Set("Content-Type", "application/json") + + resp, err = f.httpClient.Do(req) + if err != nil { + return nil, nil, fmt.Errorf("send request to interact service: %w", err) + } + + body = nil + if resp.Body != nil { + body, _ = io.ReadAll(resp.Body) // nolint + } + + if resp.StatusCode != http.StatusOK { + return nil, nil, fmt.Errorf("unexpected status code %d and body: %s", resp.StatusCode, body) + } + + var refreshedCredResp refresh.GetRefreshedCredentialResp + if err = json.Unmarshal(body, &refreshedCredResp); err != nil { + return nil, nil, fmt.Errorf("failed to parse response: %w", err) + } + + var rawUpdatedCred []byte + + switch v := refreshedCredResp.VerifiableCredential.(type) { + case []byte: + rawUpdatedCred = v + case string: + rawUpdatedCred = []byte(v) + default: + rawUpdatedCred, err = json.Marshal(v) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal updated credential: %w", err) + } + } + + newParsedCred, err := verifiable.ParseCredential(rawUpdatedCred, verifiable.WithDisabledProofCheck()) + if err != nil { + return nil, nil, fmt.Errorf("failed to parse updated credential: %w", err) + } + + slog.Info(fmt.Sprintf("received updated credential. old id : %v new id: %v", credID, + newParsedCred.Contents().ID)) + + return rawUpdatedCred, newParsedCred, nil +} diff --git a/component/wallet-cli/pkg/wallet/wallet.go b/component/wallet-cli/pkg/wallet/wallet.go index 6ede72af7..55b0762a1 100644 --- a/component/wallet-cli/pkg/wallet/wallet.go +++ b/component/wallet-cli/pkg/wallet/wallet.go @@ -317,6 +317,17 @@ func (w *Wallet) Add(vc json.RawMessage, key string) error { return nil } +func (w *Wallet) Delete(key string) error { + w.mu.Lock() + defer w.mu.Unlock() + + if err := w.store.Delete(key); err != nil { + return err + } + + return nil +} + type contentID struct { ID string `json:"id"` } diff --git a/docs/v1/openapi.yaml b/docs/v1/openapi.yaml index 11a25db50..ad266952e 100644 --- a/docs/v1/openapi.yaml +++ b/docs/v1/openapi.yaml @@ -195,6 +195,110 @@ paths: type: object operationId: post-credentials-status description: Updates credential status. + /refresh/{profileID}/{profileVersion}: + post: + summary: Receive updated (refreshed) credentials. + operationId: get-refreshed-credential + description: Receive updated (refreshed) credentials. + parameters: + - schema: + type: string + name: profileID + in: path + required: true + description: Issuer Profile ID + - schema: + type: string + name: profileVersion + in: path + required: true + description: Issuer Profile Version + - schema: + type: string + name: credentialID + in: query + required: true + description: Credential ID + tags: + - refresh + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GetRefreshedCredentialReq' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetRefreshedCredentialResp' + get: + summary: Get refresh status for credential. + parameters: + - schema: + type: string + name: profileID + in: path + required: true + description: Issuer Profile ID + - schema: + type: string + name: profileVersion + in: path + required: true + description: Issuer Profile Version + - schema: + type: string + name: credentialID + in: query + required: true + description: Credential ID + tags: + - refresh + responses: + '204': + description: No Updates + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/CredentialRefreshAvailableResponse' + operationId: request-refresh-status + description: Updates credential status. + '/issuer/profiles/{profileID}/{profileVersion}/interactions/refresh': + parameters: + - schema: + type: string + name: profileID + in: path + required: true + description: Issuer Profile ID. + - schema: + type: string + name: profileVersion + in: path + required: true + description: Issuer Profile Version. + post: + summary: 'Set Credential Refresh State' + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/SetCredentialRefreshStateResult' + operationId: set-credential-refresh-state + description: Used by issuer to refresh credential claims by credential id + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SetCredentialRefreshStateRequest' + tags: + - issuer '/issuer/profiles/{profileID}/{profileVersion}/interactions/initiate-oidc': parameters: - schema: @@ -1513,6 +1617,32 @@ components: - profileVersion - claimEndpoint - credentialTemplateID + GetRefreshedCredentialReq: + title: GetRefreshedCredentialReq + type: object + description: Model for getting refreshed credential. + properties: + verifiable_presentation: + type: string + description: Verifiable Presentation. + format: byte + required: + - verifiable_presentation + x-tags: + - refresh + GetRefreshedCredentialResp: + title: GetRefreshedCredentialResp + type: object + description: Model for getting refreshed credential. + properties: + verifiable_credential: + type: string + anyOf: + - { } + x-tags: + - refresh + required: + - verifiable_credential DeprecatedComposeOIDC4CICredential: title: DeprecatedComposeOIDC4CICredential type: object @@ -1539,6 +1669,38 @@ components: type: object description: Raw Complete credential for sign and customization nullable: false + SetCredentialRefreshStateRequest: + title: SetCredentialRefreshStateRequest + type: object + properties: + credential_id: + type: string + description: Credential ID. + claims: + type: object + description: New claims. + credential_name: + type: string + description: Credential name. + credential_description: + type: string + description: Credential description. + required: + - credential_id + - claims + x-tags: + - issuer + SetCredentialRefreshStateResult: + title: SetCredentialRefreshStateResult + type: object + properties: + transaction_id: + type: string + description: Transaction ID. + required: + - transaction_id + x-tags: + - issuer InitiateOIDC4CIRequest: title: InitiateOIDC4CIRequest type: object @@ -1653,6 +1815,57 @@ components: description: 'An array of objects that describes specifics of the Multiple Credential Issuance.' x-tags: - issuer + CredentialRefreshAvailableResponse: + title: CredentialUpdateResponse + type: object + description: Model for Credential Update Response. + properties: + verifiablePresentationRequest: + $ref: '#/components/schemas/VerifiablePresentationRequest' + required: + - verifiablePresentationRequest + VerifiablePresentationRequest: + title: VerifiablePresentationRequest + type: object + properties: + query: + type: object + description: Credential Presentation Query. + domain: + type: string + description: Domain value. + challenge: + type: string + description: Challenge value. + interact: + $ref: '#/components/schemas/RefreshServiceInteract' + required: + - query + - domain + - challenge + - interact + RefreshServiceInteract: + type: object + properties: + service: + type: array + items: + $ref: '#/components/schemas/RefreshService' + required: + - service + RefreshService: + title: RefreshService + type: object + properties: + type: + type: string + description: Service type. + serviceEndpoint: + type: string + description: Service endpoint. + required: + - type + - serviceEndpoint InitiateOIDC4CIResponse: title: InitiateOIDC4CIResponse type: object diff --git a/go.mod b/go.mod index d4b97f700..58358c972 100644 --- a/go.mod +++ b/go.mod @@ -41,10 +41,10 @@ require ( github.com/tidwall/gjson v1.14.4 github.com/tidwall/sjson v1.2.5 github.com/trustbloc/bbs-signature-go v1.0.2 - github.com/trustbloc/did-go v1.2.1 + github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 github.com/trustbloc/kms-go v1.1.2 github.com/trustbloc/logutil-go v1.0.0-rc1 - github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f + github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc github.com/valyala/fastjson v1.6.3 github.com/veraison/go-cose v1.1.1-0.20240126165338-2300d5c96dbd github.com/xeipuuv/gojsonschema v1.2.0 diff --git a/go.sum b/go.sum index 3b80c1bcc..e511a6397 100644 --- a/go.sum +++ b/go.sum @@ -625,14 +625,14 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/trustbloc/bbs-signature-go v1.0.2 h1:gepEsbLiZHv/vva9FKG5gF38mGtOIyGez7desZxiI1o= github.com/trustbloc/bbs-signature-go v1.0.2/go.mod h1:xYotcXHAbcE0TO+SteW0J6XI3geQaXq4wdnXR2k+XCU= -github.com/trustbloc/did-go v1.2.1 h1:SEOmPX+x2JlE6+jjjJp82yCCusoJ4/67zQ2ST6ytfSc= -github.com/trustbloc/did-go v1.2.1/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 h1:LpX6reFIcahgTxuDLrV9yro1gENtXQLv2NsneX5hWkc= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= github.com/trustbloc/kms-go v1.1.2 h1:nAlhDoHkSyX1eQFRz/sJsdgmJuNadyX7FJEy/9ROwys= github.com/trustbloc/kms-go v1.1.2/go.mod h1:OKOtsLbE6W5s4mpjWkvk8XEqcmt9vTgVmDNkHELpWO0= github.com/trustbloc/logutil-go v1.0.0-rc1 h1:rRJbvgQfrlUfyej+mY0nuQJymGqjRW4oZEwKi544F4c= github.com/trustbloc/logutil-go v1.0.0-rc1/go.mod h1:JlxT0oZfNKgIlSNtgc001WEeDMxlnAvOM43gNm8DQVc= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f h1:v/Gf89EK2vCk2uj9bK+zmoTAQiJEXH9I2p3fYBiNLrg= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f/go.mod h1:7GkmcXtK5FNbfxypAZAVg8eM9PVnGJ/lmQo7gQgsjKk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc h1:6/AZPtL61aybL8FsAgZBtaq89zX4ahgRqa20aEKP1fk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc/go.mod h1:l70VUyzJ+jAxGSlS+V8jiP5GnVcxFfV4bPxJ0grlu14= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= diff --git a/internal/claims/claims.go b/internal/claims/claims.go new file mode 100644 index 000000000..3d5f2e068 --- /dev/null +++ b/internal/claims/claims.go @@ -0,0 +1,60 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package claims + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/trustbloc/vcs/pkg/dataprotect" + "github.com/trustbloc/vcs/pkg/restapi/resterr" + "github.com/trustbloc/vcs/pkg/service/issuecredential" +) + +type dataProtector interface { + Encrypt(ctx context.Context, msg []byte) (*dataprotect.EncryptedData, error) + Decrypt(ctx context.Context, encryptedData *dataprotect.EncryptedData) ([]byte, error) +} + +func EncryptClaims( + ctx context.Context, + data map[string]interface{}, + protector dataProtector, +) (*issuecredential.ClaimData, error) { + bytesData, err := json.Marshal(data) + if err != nil { + return nil, err + } + + encrypted, err := protector.Encrypt(ctx, bytesData) + if err != nil { + return nil, resterr.NewSystemError(resterr.DataProtectorComponent, "Encrypt", err) + } + + return &issuecredential.ClaimData{ + EncryptedData: encrypted, + }, nil +} + +func DecryptClaims( + ctx context.Context, + data *issuecredential.ClaimData, + protector dataProtector, +) (map[string]interface{}, error) { + resp, err := protector.Decrypt(ctx, data.EncryptedData) + if err != nil { + return nil, resterr.NewSystemError(resterr.DataProtectorComponent, "Decrypt", err) + } + + finalMap := map[string]interface{}{} + if err = json.Unmarshal(resp, &finalMap); err != nil { + return nil, fmt.Errorf("unmarshal: %w", err) + } + + return finalMap, nil +} diff --git a/internal/utils/map.go b/internal/utils/map.go index 2c4bbed78..e549a34fc 100644 --- a/internal/utils/map.go +++ b/internal/utils/map.go @@ -6,6 +6,8 @@ SPDX-License-Identifier: Apache-2.0 package utils +import "encoding/json" + func ExtractKeys(prefix string, m map[string]interface{}) []string { var keys []string for k, v := range m { @@ -20,3 +22,19 @@ func ExtractKeys(prefix string, m map[string]interface{}) []string { } return keys } + +func StructureToMap(obj interface{}) (map[string]interface{}, error) { + b, err := json.Marshal(obj) + if err != nil { + return nil, err + } + + var result map[string]interface{} + + err = json.Unmarshal(b, &result) + if err != nil { + return nil, err + } + + return result, nil +} diff --git a/internal/utils/map_test.go b/internal/utils/map_test.go index 955fcc609..ba8163cd8 100644 --- a/internal/utils/map_test.go +++ b/internal/utils/map_test.go @@ -41,3 +41,17 @@ func TestExtract(t *testing.T) { "$.claim.value.yy.zz", }, keys) } + +func TestStructureToMap(t *testing.T) { + type testStruct struct { + Field1 string `json:"field1"` + Field2 int `json:"field2"` + } + + m, err := utils.StructureToMap(testStruct{Field1: "value1", Field2: 100}) + assert.NoError(t, err) + assert.EqualValues(t, map[string]interface{}{ + "field1": "value1", + "field2": float64(100), + }, m) +} diff --git a/pkg/event/spi/spi.go b/pkg/event/spi/spi.go index d4020e7ba..0e697506e 100644 --- a/pkg/event/spi/spi.go +++ b/pkg/event/spi/spi.go @@ -55,6 +55,10 @@ const ( IssuerOIDCInteractionAckExpired EventType = "issuer.oidc-interaction-ack-expired.v1" CredentialStatusStatusUpdated EventType = "issuer.credential-status-updated.v1" //nolint:gosec + + CredentialRefreshInitiated EventType = "issuer.credential-refresh-initiated.v1" //nolint + CredentialRefreshSuccessful EventType = "issuer.credential-refresh-successful.v1" //nolint + CredentialRefreshFailed EventType = "issuer.credential-refresh-failed.v1" //nolint ) // Payload defines payload. diff --git a/pkg/kms/mocks/kms_mocks.go b/pkg/kms/mocks/kms_mocks.go index 455d902b3..2b02f6366 100644 --- a/pkg/kms/mocks/kms_mocks.go +++ b/pkg/kms/mocks/kms_mocks.go @@ -192,4 +192,4 @@ func (c *VCSKeyManagerSupportedKeyTypesCall) Do(f func() []kms.KeyType) *VCSKeyM func (c *VCSKeyManagerSupportedKeyTypesCall) DoAndReturn(f func() []kms.KeyType) *VCSKeyManagerSupportedKeyTypesCall { c.Call = c.Call.DoAndReturn(f) return c -} +} \ No newline at end of file diff --git a/pkg/observability/tracing/wrappers/oidc4ci/oidc4ci_wrapper.go b/pkg/observability/tracing/wrappers/oidc4ci/oidc4ci_wrapper.go index 45a5f11b6..4a4eda3b9 100644 --- a/pkg/observability/tracing/wrappers/oidc4ci/oidc4ci_wrapper.go +++ b/pkg/observability/tracing/wrappers/oidc4ci/oidc4ci_wrapper.go @@ -19,6 +19,7 @@ import ( "github.com/trustbloc/vcs/internal/utils" profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/restapi/v1/common" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/service/oidc4ci" ) @@ -66,7 +67,7 @@ func (w *Wrapper) InitiateIssuance( return resp, nil } -func (w *Wrapper) PushAuthorizationDetails(ctx context.Context, opState string, ad []*oidc4ci.AuthorizationDetails) error { +func (w *Wrapper) PushAuthorizationDetails(ctx context.Context, opState string, ad []*issuecredential.AuthorizationDetails) error { return w.svc.PushAuthorizationDetails(ctx, opState, ad) } @@ -74,7 +75,12 @@ func (w *Wrapper) PrepareClaimDataAuthorizationRequest(ctx context.Context, req return w.svc.PrepareClaimDataAuthorizationRequest(ctx, req) } -func (w *Wrapper) StoreAuthorizationCode(ctx context.Context, opState string, code string, flowData *common.WalletInitiatedFlowData) (oidc4ci.TxID, error) { +func (w *Wrapper) StoreAuthorizationCode( + ctx context.Context, + opState string, + code string, + flowData *common.WalletInitiatedFlowData, +) (issuecredential.TxID, error) { return w.svc.StoreAuthorizationCode(ctx, opState, code, flowData) } @@ -82,7 +88,14 @@ func (w *Wrapper) ExchangeAuthorizationCode(ctx context.Context, opState, client return w.svc.ExchangeAuthorizationCode(ctx, opState, clientID, clientAttestationType, clientAttestation) } -func (w *Wrapper) ValidatePreAuthorizedCodeRequest(ctx context.Context, preAuthorizedCode, pin, clientID, clientAttestationType, clientAttestation string) (*oidc4ci.Transaction, error) { +func (w *Wrapper) ValidatePreAuthorizedCodeRequest( + ctx context.Context, + preAuthorizedCode, + pin, + clientID, + clientAttestationType, + clientAttestation string, +) (*issuecredential.Transaction, error) { ctx, span := w.tracer.Start(ctx, "oidc4ci.ValidatePreAuthorizedCodeRequest") defer span.End() diff --git a/pkg/observability/tracing/wrappers/oidc4ci/oidc4ci_wrapper_test.go b/pkg/observability/tracing/wrappers/oidc4ci/oidc4ci_wrapper_test.go index f4774a480..5b171d05c 100644 --- a/pkg/observability/tracing/wrappers/oidc4ci/oidc4ci_wrapper_test.go +++ b/pkg/observability/tracing/wrappers/oidc4ci/oidc4ci_wrapper_test.go @@ -13,9 +13,11 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" + nooptracer "go.opentelemetry.io/otel/trace/noop" + "github.com/trustbloc/vcs/pkg/profile" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/service/oidc4ci" - nooptracer "go.opentelemetry.io/otel/trace/noop" ) func TestWrapper_InitiateIssuance(t *testing.T) { @@ -48,11 +50,14 @@ func TestWrapper_PushAuthorizationDetails(t *testing.T) { ctrl := gomock.NewController(t) svc := NewMockService(ctrl) - svc.EXPECT().PushAuthorizationDetails(gomock.Any(), "opState", []*oidc4ci.AuthorizationDetails{{}}).Times(1) + svc.EXPECT().PushAuthorizationDetails(gomock.Any(), "opState", []*issuecredential.AuthorizationDetails{{}}). + Times(1) w := Wrap(svc, nooptracer.NewTracerProvider().Tracer("")) - err := w.PushAuthorizationDetails(context.Background(), "opState", []*oidc4ci.AuthorizationDetails{{}}) + err := w.PushAuthorizationDetails(context.Background(), "opState", + []*issuecredential.AuthorizationDetails{{}}, + ) require.NoError(t, err) } @@ -96,7 +101,8 @@ func TestWrapper_ValidatePreAuthorizedCodeRequest(t *testing.T) { ctrl := gomock.NewController(t) svc := NewMockService(ctrl) - svc.EXPECT().ValidatePreAuthorizedCodeRequest(gomock.Any(), "code", "pin", "clientID", "", "").Return(&oidc4ci.Transaction{ID: "id"}, nil) + svc.EXPECT().ValidatePreAuthorizedCodeRequest(gomock.Any(), "code", "pin", "clientID", "", + "").Return(&issuecredential.Transaction{ID: "id"}, nil) w := Wrap(svc, nooptracer.NewTracerProvider().Tracer("")) diff --git a/pkg/profile/api.go b/pkg/profile/api.go index f3cc743a2..4facf8ce7 100644 --- a/pkg/profile/api.go +++ b/pkg/profile/api.go @@ -193,6 +193,7 @@ type VCConfig struct { Context []string `json:"context,omitempty"` SDJWT vc.SDJWT `json:"sdjwt,omitempty"` DataIntegrityProof vc.DataIntegrityProofConfig `json:"dataIntegrityProof,omitempty"` + RefreshServiceEnabled bool `json:"refreshServiceEnabled,omitempty"` } // StatusConfig represents the VC status configuration. diff --git a/pkg/restapi/resterr/error.go b/pkg/restapi/resterr/error.go index 373b909d9..db9e7bb4f 100644 --- a/pkg/restapi/resterr/error.go +++ b/pkg/restapi/resterr/error.go @@ -64,10 +64,11 @@ type Component = string //nolint:gosec const ( - IssuerSvcComponent Component = "issuer.service" - IssuerProfileSvcComponent Component = "issuer.profile-service" - IssueCredentialSvcComponent Component = "issuer.issue-credential-service" - IssuerOIDC4ciSvcComponent Component = "issuer.oidc4ci-service" + IssuerSvcComponent Component = "issuer.service" + IssuerProfileSvcComponent Component = "issuer.profile-service" + IssuerCredentialRefreshSvcComponent Component = "issuer.credential-refresh-service" + IssueCredentialSvcComponent Component = "issuer.issue-credential-service" + IssuerOIDC4ciSvcComponent Component = "issuer.oidc4ci-service" VerifierVerifyCredentialSvcComponent Component = "verifier.verify-credential-service" VerifierOIDC4vpSvcComponent Component = "verifier.oidc4vp-service" diff --git a/pkg/restapi/v1/issuer/controller.go b/pkg/restapi/v1/issuer/controller.go index 3fdc30be5..0f3e8aad4 100644 --- a/pkg/restapi/v1/issuer/controller.go +++ b/pkg/restapi/v1/issuer/controller.go @@ -43,6 +43,7 @@ import ( "github.com/trustbloc/vcs/pkg/service/credentialstatus" "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/refresh" ) var logger = log.New("restapi-issuer") @@ -90,6 +91,13 @@ type jsonSchemaValidator interface { Validate(data interface{}, schemaID string, schema []byte) error } +type CredentialRefreshService interface { + CreateRefreshState( + ctx context.Context, + req *refresh.CreateRefreshStateRequest, + ) (string, error) +} + type Config struct { EventSvc eventService EventTopic string @@ -103,6 +111,7 @@ type Config struct { ExternalHostURL string Tracer trace.Tracer JSONSchemaValidator jsonSchemaValidator + CredentialRefreshService CredentialRefreshService } // Controller for Issuer Profile Management API. @@ -120,6 +129,7 @@ type Controller struct { eventSvc eventService eventTopic string marshal func(any) ([]byte, error) + credentialRefreshService CredentialRefreshService } // NewController creates a new controller for Issuer Profile Management API. @@ -138,9 +148,40 @@ func NewController(config *Config) *Controller { eventSvc: config.EventSvc, eventTopic: config.EventTopic, marshal: json.Marshal, + credentialRefreshService: config.CredentialRefreshService, } } +// SetCredentialRefreshState sets claims for credential refresh. +// POST /issuer/profiles/{profileID}/{profileVersion}/interactions/refresh. +func (c *Controller) SetCredentialRefreshState(ctx echo.Context, profileID string, profileVersion string) error { + var body SetCredentialRefreshStateRequest + if err := util.ReadBody(ctx, &body); err != nil { + return err + } + + profile, err := c.profileSvc.GetProfile(profileID, profileVersion) + if err != nil { + return resterr.NewSystemError(resterr.IssuerOIDC4ciSvcComponent, + "PrepareClaimDataAuthorizationRequest", err) + } + + txID, err := c.credentialRefreshService.CreateRefreshState(ctx.Request().Context(), &refresh.CreateRefreshStateRequest{ + CredentialID: body.CredentialId, + Issuer: *profile, + Claims: body.Claims, + CredentialName: body.CredentialName, + CredentialDescription: body.CredentialDescription, + }) + if err != nil { + return resterr.NewSystemError(resterr.IssuerOIDC4ciSvcComponent, "SetCredentialRefreshState", err) + } + + return util.WriteOutput(ctx)(SetCredentialRefreshStateResult{ + TransactionId: txID, + }, nil) +} + // PostIssueCredentials issues credentials. // POST /issuer/profiles/{profileID}/{profileVersion}/credentials/issue. func (c *Controller) PostIssueCredentials(e echo.Context, profileID, profileVersion string) error { @@ -632,7 +673,7 @@ func (c *Controller) prepareClaimDataAuthorizationRequest( body *PrepareClaimDataAuthorizationRequest, ) (*PrepareClaimDataAuthorizationResponse, error) { var ( - ad []*oidc4ci.AuthorizationDetails + ad []*issuecredential.AuthorizationDetails err error ) @@ -809,7 +850,7 @@ func (c *Controller) PrepareCredential(e echo.Context) error { result, err := c.oidc4ciService.PrepareCredential( ctx, &oidc4ci.PrepareCredential{ - TxID: oidc4ci.TxID(body.TxId), + TxID: issuecredential.TxID(body.TxId), CredentialRequests: []*oidc4ci.PrepareCredentialRequest{ { CredentialTypes: body.Types, @@ -989,7 +1030,7 @@ func (c *Controller) PrepareBatchCredential(e echo.Context) error { result, err := c.oidc4ciService.PrepareCredential( ctx, &oidc4ci.PrepareCredential{ - TxID: oidc4ci.TxID(body.TxId), + TxID: issuecredential.TxID(body.TxId), CredentialRequests: credentialRequests, }, ) @@ -1139,6 +1180,7 @@ func (c *Controller) validateJSONLD( return validator.ValidateJSONLDMap(data, validator.WithDocumentLoader(c.documentLoader), validator.WithStrictValidation(true), + validator.WithJSONLDIncludeDetailedStructureDiffOnError(), ) } diff --git a/pkg/restapi/v1/issuer/controller_test.go b/pkg/restapi/v1/issuer/controller_test.go index b36144268..63a1be8de 100644 --- a/pkg/restapi/v1/issuer/controller_test.go +++ b/pkg/restapi/v1/issuer/controller_test.go @@ -38,7 +38,9 @@ import ( "github.com/trustbloc/vcs/pkg/restapi/v1/common" "github.com/trustbloc/vcs/pkg/restapi/v1/util" "github.com/trustbloc/vcs/pkg/service/credentialstatus" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/refresh" ) const ( @@ -1564,7 +1566,8 @@ func TestController_StoreAuthZCode(t *testing.T) { opState := uuid.NewString() code := uuid.NewString() mockOIDC4CIService := NewMockOIDC4CIService(gomock.NewController(t)) - mockOIDC4CIService.EXPECT().StoreAuthorizationCode(gomock.Any(), opState, code, nil).Return(oidc4ci.TxID("1234"), nil) + mockOIDC4CIService.EXPECT().StoreAuthorizationCode(gomock.Any(), opState, code, nil).Return( + issuecredential.TxID("1234"), nil) c := &Controller{ oidc4ciService: mockOIDC4CIService, @@ -1584,7 +1587,7 @@ func TestController_StoreAuthZCode(t *testing.T) { ProfileId: "123", ProfileVersion: "xxx", }). - Return(oidc4ci.TxID("1234"), nil) + Return(issuecredential.TxID("1234"), nil) c := &Controller{ oidc4ciService: mockOIDC4CIService, @@ -1610,7 +1613,7 @@ func TestController_ExchangeAuthorizationCode(t *testing.T) { mockOIDC4CIService.EXPECT().ExchangeAuthorizationCode(gomock.Any(), opState, "", "", ""). Return(&oidc4ci.ExchangeAuthorizationCodeResult{ TxID: "TxID", - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ getTestAuthorizationDetails(t, true), }, }, nil) @@ -1644,7 +1647,7 @@ func TestController_ExchangeAuthorizationCode(t *testing.T) { mockOIDC4CIService.EXPECT().ExchangeAuthorizationCode(gomock.Any(), opState, "", "", ""). Return(&oidc4ci.ExchangeAuthorizationCodeResult{ TxID: "TxID", - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ getTestAuthorizationDetails(t, false), }, }, nil) @@ -1732,12 +1735,12 @@ func TestController_ValidatePreAuthorizedCodeRequest(t *testing.T) { t.Run("success with pin and authorizationDetails", func(t *testing.T) { mockOIDC4CIService := NewMockOIDC4CIService(gomock.NewController(t)) mockOIDC4CIService.EXPECT().ValidatePreAuthorizedCodeRequest(gomock.Any(), "1234", "5432", "123", "", ""). - Return(&oidc4ci.Transaction{ + Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ OpState: "random_op_state", Scope: []string{"a", "b"}, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { AuthorizationDetails: getTestAuthorizationDetails(t, true), CredentialConfigurationID: "CredentialConfigurationID", @@ -1772,9 +1775,9 @@ func TestController_ValidatePreAuthorizedCodeRequest(t *testing.T) { t.Run("success without pin and without authorizationDetails", func(t *testing.T) { mockOIDC4CIService := NewMockOIDC4CIService(gomock.NewController(t)) mockOIDC4CIService.EXPECT().ValidatePreAuthorizedCodeRequest(gomock.Any(), "1234", "", "123", "", ""). - Return(&oidc4ci.Transaction{ + Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ OpState: "random_op_state", Scope: []string{"a", "b"}, //AuthorizationDetails: nil, @@ -1858,7 +1861,7 @@ func TestController_PrepareCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) assert.Len(t, req.CredentialRequests, 1) assert.Equal(t, []string{"UniversityDegreeCredential"}, req.CredentialRequests[0].CredentialTypes) assert.Equal(t, vcsverifiable.OIDCFormat("ldp_vc"), req.CredentialRequests[0].CredentialFormat) @@ -1930,7 +1933,7 @@ func TestController_PrepareCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) return &oidc4ci.PrepareCredentialResult{ ProfileID: profileID, @@ -1990,7 +1993,7 @@ func TestController_PrepareCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) return &oidc4ci.PrepareCredentialResult{ ProfileID: profileID, @@ -2031,7 +2034,7 @@ func TestController_PrepareCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) return &oidc4ci.PrepareCredentialResult{ ProfileID: profileID, @@ -2077,7 +2080,7 @@ func TestController_PrepareCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) return &oidc4ci.PrepareCredentialResult{ ProfileID: profileID, @@ -2175,7 +2178,7 @@ func TestController_PrepareCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) return &oidc4ci.PrepareCredentialResult{ ProfileID: profileID, @@ -2246,7 +2249,7 @@ func TestController_PrepareCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) return &oidc4ci.PrepareCredentialResult{ ProfileID: profileID, @@ -2313,7 +2316,7 @@ func TestController_PrepareCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) return &oidc4ci.PrepareCredentialResult{ ProfileID: profileID, @@ -2380,7 +2383,7 @@ func TestController_PrepareCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) return &oidc4ci.PrepareCredentialResult{ ProfileID: profileID, @@ -2460,7 +2463,7 @@ func TestController_PrepareBatchCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) assert.Len(t, req.CredentialRequests, 2) assert.Equal(t, []string{"UniversityDegreeCredential"}, req.CredentialRequests[0].CredentialTypes) assert.Equal(t, vcsverifiable.OIDCFormat("ldp_vc"), req.CredentialRequests[0].CredentialFormat) @@ -2547,7 +2550,7 @@ func TestController_PrepareBatchCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) return &oidc4ci.PrepareCredentialResult{ ProfileID: profileID, @@ -2588,7 +2591,7 @@ func TestController_PrepareBatchCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) return &oidc4ci.PrepareCredentialResult{ ProfileID: profileID, @@ -2634,7 +2637,7 @@ func TestController_PrepareBatchCredential(t *testing.T) { ctx context.Context, req *oidc4ci.PrepareCredential, ) (*oidc4ci.PrepareCredentialResult, error) { - assert.Equal(t, oidc4ci.TxID("123"), req.TxID) + assert.Equal(t, issuecredential.TxID("123"), req.TxID) return &oidc4ci.PrepareCredentialResult{ ProfileID: profileID, @@ -2897,6 +2900,152 @@ func TestCredentialIssuanceHistory(t *testing.T) { }) } +func TestSetCredentialRefreshState(t *testing.T) { + t.Run("success", func(t *testing.T) { + profileRepo := NewMockProfileService(gomock.NewController(t)) + credRefreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + + c := &Controller{ + profileSvc: profileRepo, + credentialRefreshService: credRefreshSvc, + } + + recorder := httptest.NewRecorder() + + reqBody := &SetCredentialRefreshStateRequest{ + Claims: map[string]interface{}{ + "claim1": "value1", + }, + CredentialDescription: lo.ToPtr("some-cred-desc"), + CredentialId: "some-cred-id", + CredentialName: lo.ToPtr("some-cred-name"), + } + data, err := json.Marshal(reqBody) + require.NoError(t, err) + + echoCtx := echoContext( + withRecorder(recorder), + withRequestBody(data), + ) + + issuer := profileapi.Issuer{ + ID: "abc", + } + profileRepo.EXPECT().GetProfile(profileID, profileVersion). + Return(&issuer, nil) + + credRefreshSvc.EXPECT().CreateRefreshState(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, request *refresh.CreateRefreshStateRequest) (string, error) { + assert.EqualValues(t, reqBody.CredentialId, request.CredentialID) + assert.EqualValues(t, reqBody.CredentialName, request.CredentialName) + assert.EqualValues(t, reqBody.CredentialDescription, request.CredentialDescription) + assert.EqualValues(t, reqBody.Claims, request.Claims) + assert.EqualValues(t, issuer, request.Issuer) + + return "some-value", nil + }) + + err = c.SetCredentialRefreshState(echoCtx, profileID, profileVersion) + assert.NoError(t, err) + }) + + t.Run("invalid body", func(t *testing.T) { + profileRepo := NewMockProfileService(gomock.NewController(t)) + credRefreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + + c := &Controller{ + profileSvc: profileRepo, + credentialRefreshService: credRefreshSvc, + } + + recorder := httptest.NewRecorder() + + echoCtx := echoContext( + withRecorder(recorder), + withRequestBody([]byte("{")), + ) + + err := c.SetCredentialRefreshState(echoCtx, profileID, profileVersion) + assert.Error(t, err) + }) + + t.Run("profile not found", func(t *testing.T) { + profileRepo := NewMockProfileService(gomock.NewController(t)) + credRefreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + + c := &Controller{ + profileSvc: profileRepo, + credentialRefreshService: credRefreshSvc, + } + + recorder := httptest.NewRecorder() + + reqBody := &SetCredentialRefreshStateRequest{ + Claims: map[string]interface{}{ + "claim1": "value1", + }, + CredentialDescription: lo.ToPtr("some-cred-desc"), + CredentialId: "some-cred-id", + CredentialName: lo.ToPtr("some-cred-name"), + } + data, err := json.Marshal(reqBody) + require.NoError(t, err) + + echoCtx := echoContext( + withRecorder(recorder), + withRequestBody(data), + ) + + profileRepo.EXPECT().GetProfile(profileID, profileVersion). + Return(nil, errors.New("profile not found")) + + err = c.SetCredentialRefreshState(echoCtx, profileID, profileVersion) + assert.ErrorContains(t, err, "profile not found") + }) + + t.Run("service err", func(t *testing.T) { + profileRepo := NewMockProfileService(gomock.NewController(t)) + credRefreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + + c := &Controller{ + profileSvc: profileRepo, + credentialRefreshService: credRefreshSvc, + } + + recorder := httptest.NewRecorder() + + reqBody := &SetCredentialRefreshStateRequest{ + Claims: map[string]interface{}{ + "claim1": "value1", + }, + CredentialDescription: lo.ToPtr("some-cred-desc"), + CredentialId: "some-cred-id", + CredentialName: lo.ToPtr("some-cred-name"), + } + data, err := json.Marshal(reqBody) + require.NoError(t, err) + + echoCtx := echoContext( + withRecorder(recorder), + withRequestBody(data), + ) + + issuer := profileapi.Issuer{ + ID: "abc", + } + profileRepo.EXPECT().GetProfile(profileID, profileVersion). + Return(&issuer, nil) + + credRefreshSvc.EXPECT().CreateRefreshState(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, request *refresh.CreateRefreshStateRequest) (string, error) { + return "", errors.New("invalid request") + }) + + err = c.SetCredentialRefreshState(echoCtx, profileID, profileVersion) + assert.ErrorContains(t, err, "invalid request") + }) +} + func Test_getCredentialSubjects(t *testing.T) { t.Run("subject", func(t *testing.T) { subjects, err := getCredentialSubjects(verifiable.Subject{ID: "id1"}) @@ -3029,10 +3178,10 @@ func requireCustomError(t *testing.T, expectedCode resterr.ErrorCode, actual err require.Error(t, actualErr.Err) } -func getTestAuthorizationDetails(t *testing.T, includeCredentialDefinition bool) *oidc4ci.AuthorizationDetails { +func getTestAuthorizationDetails(t *testing.T, includeCredentialDefinition bool) *issuecredential.AuthorizationDetails { t.Helper() - res := &oidc4ci.AuthorizationDetails{ + res := &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "CredentialConfigurationID", Locations: []string{"https://example.com/rs1", "https://example.com/rs2"}, Type: "openid_credential", @@ -3042,7 +3191,7 @@ func getTestAuthorizationDetails(t *testing.T, includeCredentialDefinition bool) } if includeCredentialDefinition { - res.CredentialDefinition = &oidc4ci.CredentialDefinition{ + res.CredentialDefinition = &issuecredential.CredentialDefinition{ Context: []string{"https://example.com/context/1", "https://example.com/context/2"}, CredentialSubject: map[string]interface{}{"key": "value"}, Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, diff --git a/pkg/restapi/v1/issuer/openapi.gen.go b/pkg/restapi/v1/issuer/openapi.gen.go index c689c803f..10eec5668 100644 --- a/pkg/restapi/v1/issuer/openapi.gen.go +++ b/pkg/restapi/v1/issuer/openapi.gen.go @@ -489,6 +489,27 @@ type RequestedCredentialResponseEncryption struct { Enc string `json:"enc"` } +// SetCredentialRefreshStateRequest defines model for SetCredentialRefreshStateRequest. +type SetCredentialRefreshStateRequest struct { + // New claims. + Claims map[string]interface{} `json:"claims"` + + // Credential description. + CredentialDescription *string `json:"credential_description,omitempty"` + + // Credential ID. + CredentialId string `json:"credential_id"` + + // Credential name. + CredentialName *string `json:"credential_name,omitempty"` +} + +// SetCredentialRefreshStateResult defines model for SetCredentialRefreshStateResult. +type SetCredentialRefreshStateResult struct { + // Transaction ID. + TransactionId string `json:"transaction_id"` +} + // Model for storing auth code from issuer oauth type StoreAuthorizationCodeRequest struct { Code string `json:"code"` @@ -648,6 +669,9 @@ type InitiateCredentialComposeIssuanceJSONBody = InitiateOIDC4CIRequest // InitiateCredentialIssuanceJSONBody defines parameters for InitiateCredentialIssuance. type InitiateCredentialIssuanceJSONBody = InitiateOIDC4CIRequest +// SetCredentialRefreshStateJSONBody defines parameters for SetCredentialRefreshState. +type SetCredentialRefreshStateJSONBody = SetCredentialRefreshStateRequest + // PostCredentialsStatusJSONRequestBody defines body for PostCredentialsStatus for application/json ContentType. type PostCredentialsStatusJSONRequestBody = PostCredentialsStatusJSONBody @@ -681,6 +705,9 @@ type InitiateCredentialComposeIssuanceJSONRequestBody = InitiateCredentialCompos // InitiateCredentialIssuanceJSONRequestBody defines body for InitiateCredentialIssuance for application/json ContentType. type InitiateCredentialIssuanceJSONRequestBody = InitiateCredentialIssuanceJSONBody +// SetCredentialRefreshStateJSONRequestBody defines body for SetCredentialRefreshState for application/json ContentType. +type SetCredentialRefreshStateJSONRequestBody = SetCredentialRefreshStateJSONBody + // Getter for additional properties for CredentialConfigurationsSupported_ProofTypesSupported. Returns the specified // element and whether it was found func (a CredentialConfigurationsSupported_ProofTypesSupported) Get(fieldName string) (value ProofTypeSupported, found bool) { @@ -921,6 +948,11 @@ type ClientInterface interface { InitiateCredentialIssuance(ctx context.Context, profileID string, profileVersion string, body InitiateCredentialIssuanceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // SetCredentialRefreshState request with any body + SetCredentialRefreshStateWithBody(ctx context.Context, profileID string, profileVersion string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + SetCredentialRefreshState(ctx context.Context, profileID string, profileVersion string, body SetCredentialRefreshStateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // OpenidCredentialIssuerConfig request OpenidCredentialIssuerConfig(ctx context.Context, profileID string, profileVersion string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1216,6 +1248,30 @@ func (c *Client) InitiateCredentialIssuance(ctx context.Context, profileID strin return c.Client.Do(req) } +func (c *Client) SetCredentialRefreshStateWithBody(ctx context.Context, profileID string, profileVersion string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSetCredentialRefreshStateRequestWithBody(c.Server, profileID, profileVersion, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) SetCredentialRefreshState(ctx context.Context, profileID string, profileVersion string, body SetCredentialRefreshStateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewSetCredentialRefreshStateRequest(c.Server, profileID, profileVersion, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) OpenidCredentialIssuerConfig(ctx context.Context, profileID string, profileVersion string, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewOpenidCredentialIssuerConfigRequest(c.Server, profileID, profileVersion) if err != nil { @@ -1833,6 +1889,60 @@ func NewInitiateCredentialIssuanceRequestWithBody(server string, profileID strin return req, nil } +// NewSetCredentialRefreshStateRequest calls the generic SetCredentialRefreshState builder with application/json body +func NewSetCredentialRefreshStateRequest(server string, profileID string, profileVersion string, body SetCredentialRefreshStateJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewSetCredentialRefreshStateRequestWithBody(server, profileID, profileVersion, "application/json", bodyReader) +} + +// NewSetCredentialRefreshStateRequestWithBody generates requests for SetCredentialRefreshState with any type of body +func NewSetCredentialRefreshStateRequestWithBody(server string, profileID string, profileVersion string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "profileID", runtime.ParamLocationPath, profileID) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "profileVersion", runtime.ParamLocationPath, profileVersion) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/issuer/profiles/%s/%s/interactions/refresh", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewOpenidCredentialIssuerConfigRequest generates requests for OpenidCredentialIssuerConfig func NewOpenidCredentialIssuerConfigRequest(server string, profileID string, profileVersion string) (*http.Request, error) { var err error @@ -2019,6 +2129,11 @@ type ClientWithResponsesInterface interface { InitiateCredentialIssuanceWithResponse(ctx context.Context, profileID string, profileVersion string, body InitiateCredentialIssuanceJSONRequestBody, reqEditors ...RequestEditorFn) (*InitiateCredentialIssuanceResponse, error) + // SetCredentialRefreshState request with any body + SetCredentialRefreshStateWithBodyWithResponse(ctx context.Context, profileID string, profileVersion string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetCredentialRefreshStateResponse, error) + + SetCredentialRefreshStateWithResponse(ctx context.Context, profileID string, profileVersion string, body SetCredentialRefreshStateJSONRequestBody, reqEditors ...RequestEditorFn) (*SetCredentialRefreshStateResponse, error) + // OpenidCredentialIssuerConfig request OpenidCredentialIssuerConfigWithResponse(ctx context.Context, profileID string, profileVersion string, reqEditors ...RequestEditorFn) (*OpenidCredentialIssuerConfigResponse, error) @@ -2311,6 +2426,28 @@ func (r InitiateCredentialIssuanceResponse) StatusCode() int { return 0 } +type SetCredentialRefreshStateResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *SetCredentialRefreshStateResult +} + +// Status returns HTTPResponse.Status +func (r SetCredentialRefreshStateResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r SetCredentialRefreshStateResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type OpenidCredentialIssuerConfigResponse struct { Body []byte HTTPResponse *http.Response @@ -2560,6 +2697,23 @@ func (c *ClientWithResponses) InitiateCredentialIssuanceWithResponse(ctx context return ParseInitiateCredentialIssuanceResponse(rsp) } +// SetCredentialRefreshStateWithBodyWithResponse request with arbitrary body returning *SetCredentialRefreshStateResponse +func (c *ClientWithResponses) SetCredentialRefreshStateWithBodyWithResponse(ctx context.Context, profileID string, profileVersion string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*SetCredentialRefreshStateResponse, error) { + rsp, err := c.SetCredentialRefreshStateWithBody(ctx, profileID, profileVersion, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseSetCredentialRefreshStateResponse(rsp) +} + +func (c *ClientWithResponses) SetCredentialRefreshStateWithResponse(ctx context.Context, profileID string, profileVersion string, body SetCredentialRefreshStateJSONRequestBody, reqEditors ...RequestEditorFn) (*SetCredentialRefreshStateResponse, error) { + rsp, err := c.SetCredentialRefreshState(ctx, profileID, profileVersion, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseSetCredentialRefreshStateResponse(rsp) +} + // OpenidCredentialIssuerConfigWithResponse request returning *OpenidCredentialIssuerConfigResponse func (c *ClientWithResponses) OpenidCredentialIssuerConfigWithResponse(ctx context.Context, profileID string, profileVersion string, reqEditors ...RequestEditorFn) (*OpenidCredentialIssuerConfigResponse, error) { rsp, err := c.OpenidCredentialIssuerConfig(ctx, profileID, profileVersion, reqEditors...) @@ -2906,6 +3060,32 @@ func ParseInitiateCredentialIssuanceResponse(rsp *http.Response) (*InitiateCrede return response, nil } +// ParseSetCredentialRefreshStateResponse parses an HTTP response from a SetCredentialRefreshStateWithResponse call +func ParseSetCredentialRefreshStateResponse(rsp *http.Response) (*SetCredentialRefreshStateResponse, error) { + bodyBytes, err := ioutil.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &SetCredentialRefreshStateResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest SetCredentialRefreshStateResult + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + // ParseOpenidCredentialIssuerConfigResponse parses an HTTP response from a OpenidCredentialIssuerConfigWithResponse call func ParseOpenidCredentialIssuerConfigResponse(rsp *http.Response) (*OpenidCredentialIssuerConfigResponse, error) { bodyBytes, err := ioutil.ReadAll(rsp.Body) @@ -2999,6 +3179,9 @@ type ServerInterface interface { // Initiate OIDC Credential Issuance // (POST /issuer/profiles/{profileID}/{profileVersion}/interactions/initiate-oidc) InitiateCredentialIssuance(ctx echo.Context, profileID string, profileVersion string) error + // Set Credential Refresh State + // (POST /issuer/profiles/{profileID}/{profileVersion}/interactions/refresh) + SetCredentialRefreshState(ctx echo.Context, profileID string, profileVersion string) error // Request VCS IDP OIDC Configuration. // (GET /issuer/{profileID}/{profileVersion}/.well-known/openid-credential-issuer) OpenidCredentialIssuerConfig(ctx echo.Context, profileID string, profileVersion string) error @@ -3212,6 +3395,30 @@ func (w *ServerInterfaceWrapper) InitiateCredentialIssuance(ctx echo.Context) er return err } +// SetCredentialRefreshState converts echo context to params. +func (w *ServerInterfaceWrapper) SetCredentialRefreshState(ctx echo.Context) error { + var err error + // ------------- Path parameter "profileID" ------------- + var profileID string + + err = runtime.BindStyledParameterWithLocation("simple", false, "profileID", runtime.ParamLocationPath, ctx.Param("profileID"), &profileID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter profileID: %s", err)) + } + + // ------------- Path parameter "profileVersion" ------------- + var profileVersion string + + err = runtime.BindStyledParameterWithLocation("simple", false, "profileVersion", runtime.ParamLocationPath, ctx.Param("profileVersion"), &profileVersion) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter profileVersion: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.SetCredentialRefreshState(ctx, profileID, profileVersion) + return err +} + // OpenidCredentialIssuerConfig converts echo context to params. func (w *ServerInterfaceWrapper) OpenidCredentialIssuerConfig(ctx echo.Context) error { var err error @@ -3301,6 +3508,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.POST(baseURL+"/issuer/profiles/:profileID/:profileVersion/credentials/issue", wrapper.PostIssueCredentials) router.POST(baseURL+"/issuer/profiles/:profileID/:profileVersion/interactions/compose-and-initiate-issuance", wrapper.InitiateCredentialComposeIssuance) router.POST(baseURL+"/issuer/profiles/:profileID/:profileVersion/interactions/initiate-oidc", wrapper.InitiateCredentialIssuance) + router.POST(baseURL+"/issuer/profiles/:profileID/:profileVersion/interactions/refresh", wrapper.SetCredentialRefreshState) router.GET(baseURL+"/issuer/:profileID/:profileVersion/.well-known/openid-credential-issuer", wrapper.OpenidCredentialIssuerConfig) router.GET(baseURL+"/oidc/idp/:profileID/:profileVersion/.well-known/openid-credential-issuer", wrapper.OpenidCredentialIssuerConfigV2) diff --git a/pkg/restapi/v1/mw/api_key_auth.go b/pkg/restapi/v1/mw/api_key_auth.go index d8fcaa984..28f8ce125 100644 --- a/pkg/restapi/v1/mw/api_key_auth.go +++ b/pkg/restapi/v1/mw/api_key_auth.go @@ -25,6 +25,7 @@ const ( checkAuthorizationResponse = "/verifier/interactions/authorization-response" oidcAuthorize = "/oidc/authorize" oidcRedirect = "/oidc/redirect" + refresh = "/refresh" oidcPresent = "/oidc/present" oidcToken = "/oidc/token" oidcAck = "/oidc/notification" @@ -53,6 +54,10 @@ func APIKeyAuth(apiKey string) echo.MiddlewareFunc { //nolint:gocognit return next(c) } + if strings.HasPrefix(currentPath, refresh) { + return next(c) + } + if strings.Contains(currentPath, statusCheckPath) { return next(c) } diff --git a/pkg/restapi/v1/mw/api_key_auth_test.go b/pkg/restapi/v1/mw/api_key_auth_test.go index 1c19ab425..d3f990681 100644 --- a/pkg/restapi/v1/mw/api_key_auth_test.go +++ b/pkg/restapi/v1/mw/api_key_auth_test.go @@ -180,4 +180,44 @@ func TestApiKeyAuth(t *testing.T) { require.NoError(t, err) require.True(t, handlerCalled) }) + + t.Run("skip refresh endpoint", func(t *testing.T) { + handlerCalled := false + handler := func(c echo.Context) error { + handlerCalled = true + return c.String(http.StatusOK, "test") + } + + middlewareChain := mw.APIKeyAuth("test-api-key")(handler) + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/refresh", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + err := middlewareChain(c) + + require.NoError(t, err) + require.True(t, handlerCalled) + }) + + t.Run("skip status endpoint", func(t *testing.T) { + handlerCalled := false + handler := func(c echo.Context) error { + handlerCalled = true + return c.String(http.StatusOK, "test") + } + + middlewareChain := mw.APIKeyAuth("test-api-key")(handler) + + e := echo.New() + req := httptest.NewRequest(http.MethodPost, "/credentials/status/", nil) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + err := middlewareChain(c) + + require.NoError(t, err) + require.True(t, handlerCalled) + }) } diff --git a/pkg/restapi/v1/oidc4ci/controller_test.go b/pkg/restapi/v1/oidc4ci/controller_test.go index d14b4b69e..87a353fa6 100644 --- a/pkg/restapi/v1/oidc4ci/controller_test.go +++ b/pkg/restapi/v1/oidc4ci/controller_test.go @@ -52,6 +52,7 @@ import ( "github.com/trustbloc/vcs/pkg/restapi/v1/issuer" "github.com/trustbloc/vcs/pkg/restapi/v1/oidc4ci" "github.com/trustbloc/vcs/pkg/service/clientmanager" + "github.com/trustbloc/vcs/pkg/service/issuecredential" oidc4cisrv "github.com/trustbloc/vcs/pkg/service/oidc4ci" ) @@ -5042,11 +5043,11 @@ func (m *mockJWEEncrypter) Options() gojose.EncrypterOptions { func getTestOIDCTokenAuthorizationDetailsPayload(t *testing.T) string { t.Helper() - res := &oidc4cisrv.AuthorizationDetails{ + res := &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "CredentialConfigurationID", Locations: []string{"https://example.com/rs1", "https://example.com/rs2"}, Type: "openid_credential", - CredentialDefinition: &oidc4cisrv.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Context: []string{"https://example.com/context/1", "https://example.com/context/2"}, CredentialSubject: map[string]interface{}{"key": "value"}, Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, diff --git a/pkg/restapi/v1/refresh/controller.go b/pkg/restapi/v1/refresh/controller.go new file mode 100644 index 000000000..d3714195f --- /dev/null +++ b/pkg/restapi/v1/refresh/controller.go @@ -0,0 +1,122 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +//go:generate oapi-codegen --config=openapi.cfg.yaml ../../../../docs/v1/openapi.yaml + +package refresh + +import ( + "fmt" + "net/http" + + "github.com/labstack/echo/v4" + "github.com/piprate/json-gold/ld" + "github.com/trustbloc/vc-go/verifiable" + + "github.com/trustbloc/vcs/internal/utils" + "github.com/trustbloc/vcs/pkg/restapi/resterr" +) + +var _ ServerInterface = (*Controller)(nil) // make sure Controller implements ServerInterface + +type Config struct { + RefreshService CredentialRefreshService + ProfileService ProfileService + ProofChecker ProofChecker + DocumentLoader ld.DocumentLoader + IssuerVCSPublicHost string +} +type Controller struct { + cfg *Config +} + +func NewController(cfg *Config) *Controller { + return &Controller{ + cfg: cfg, + } +} + +// GetRefreshedCredential gets refreshed credentials (POST /refresh/{profileID}/{profileVersion}). +func (c *Controller) GetRefreshedCredential( + ctx echo.Context, + profileID string, + profileVersion string, + _ GetRefreshedCredentialParams, +) error { + var req GetRefreshedCredentialReq + if err := ctx.Bind(&req); err != nil { + return resterr.NewValidationError(resterr.InvalidValue, "request", err) + } + + pres, err := verifiable.ParsePresentation(req.VerifiablePresentation, + verifiable.WithPresJSONLDDocumentLoader(c.cfg.DocumentLoader), + verifiable.WithPresProofChecker(c.cfg.ProofChecker)) + if err != nil { + return resterr.NewValidationError(resterr.InvalidValue, "verifiable_presentation", err) + } + + targetIssuer, err := c.cfg.ProfileService.GetProfile(profileID, profileVersion) + if err != nil { + return resterr.NewSystemError(resterr.IssuerProfileSvcComponent, "GetProfile", err) + } + + resp, err := c.cfg.RefreshService.GetRefreshedCredential(ctx.Request().Context(), pres, *targetIssuer) + if err != nil { + return resterr.NewSystemError(resterr.IssuerCredentialRefreshSvcComponent, + "GetRefreshedCredential", + err) + } + + return ctx.JSON(http.StatusOK, GetRefreshedCredentialResp{ + VerifiableCredential: resp, + }) +} + +// RequestRefreshStatus gets refresh status (GET /refresh/{profileID}/{profileVersion}). +func (c *Controller) RequestRefreshStatus( + ctx echo.Context, + issuerID string, + profileVersion string, + params RequestRefreshStatusParams, +) error { + targetIssuer, err := c.cfg.ProfileService.GetProfile(issuerID, profileVersion) + if err != nil { + return resterr.NewSystemError(resterr.IssuerProfileSvcComponent, "GetProfile", err) + } + + resp, err := c.cfg.RefreshService.RequestRefreshStatus(ctx.Request().Context(), params.CredentialID, *targetIssuer) + if err != nil { + return resterr.NewSystemError(resterr.IssuerCredentialRefreshSvcComponent, "RequestRefreshStatus", + err) + } + + if resp == nil { + return ctx.NoContent(http.StatusNoContent) + } + + query, err := utils.StructureToMap(resp.VerifiablePresentationRequest.Query) + if err != nil { + return resterr.NewSystemError(resterr.IssuerCredentialRefreshSvcComponent, "RequestRefreshStatus", + err) + } + + return ctx.JSON(http.StatusOK, &CredentialRefreshAvailableResponse{ + VerifiablePresentationRequest: VerifiablePresentationRequest{ + Challenge: resp.Challenge, + Domain: resp.Domain, + Interact: RefreshServiceInteract{ + Service: []RefreshService{ + { + ServiceEndpoint: fmt.Sprintf("%s%s", c.cfg.IssuerVCSPublicHost, + ctx.Request().URL.String()), + Type: resp.RefreshServiceType.Type, + }, + }, + }, + Query: query, + }, + }) +} diff --git a/pkg/restapi/v1/refresh/controller_test.go b/pkg/restapi/v1/refresh/controller_test.go new file mode 100644 index 000000000..3336cf1b1 --- /dev/null +++ b/pkg/restapi/v1/refresh/controller_test.go @@ -0,0 +1,425 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package refresh_test + +import ( + "bytes" + _ "embed" + "encoding/json" + "errors" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/golang/mock/gomock" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + + vcs "github.com/trustbloc/vcs/pkg/doc/verifiable" + "github.com/trustbloc/vcs/pkg/internal/testutil" + profileapi "github.com/trustbloc/vcs/pkg/profile" + "github.com/trustbloc/vcs/pkg/restapi/v1/refresh" + refresh2 "github.com/trustbloc/vcs/pkg/service/refresh" +) + +const ( + orgID = "orgID1" + profileID = "testID" + profileVersion = "v1.0" +) + +var ( + //go:embed testdata/requested_credentials_vp.jsonld + requestedCredentialsVP []byte +) + +func TestGetRefreshStatus(t *testing.T) { + t.Run("success", func(t *testing.T) { + recorder := httptest.NewRecorder() + + profileSvc := NewMockProfileService(gomock.NewController(t)) + refreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + + echoCtx := echoContext( + withRecorder(recorder), + ) + + echoCtx.Request().URL.Path = "/refresh/" + profileID + "/" + profileVersion + + issuer := &profileapi.Issuer{} + profileSvc.EXPECT().GetProfile(profileID, profileVersion). + Return(issuer, nil) + + refreshSvc.EXPECT().RequestRefreshStatus(gomock.Any(), "some-cred-id", *issuer). + Return(&refresh2.GetRefreshStateResponse{ + Challenge: "challenge", + Domain: "domain", + RefreshServiceType: refresh2.ServiceType{ + Type: "someType", + URL: "someURL", + }, + }, nil) + + ctr := refresh.NewController(&refresh.Config{ + ProfileService: profileSvc, + RefreshService: refreshSvc, + IssuerVCSPublicHost: "https://public.local/api/vc", + }) + + assert.NoError(t, ctr.RequestRefreshStatus(echoCtx, profileID, profileVersion, refresh.RequestRefreshStatusParams{ + CredentialID: "some-cred-id", + })) + + var res refresh.CredentialRefreshAvailableResponse + assert.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &res)) + + assert.Equal(t, "challenge", res.VerifiablePresentationRequest.Challenge) + assert.Equal(t, "domain", res.VerifiablePresentationRequest.Domain) + + assert.Len(t, res.VerifiablePresentationRequest.Interact.Service, 1) + assert.Equal(t, "someType", res.VerifiablePresentationRequest.Interact.Service[0].Type) + assert.Equal(t, "https://public.local/api/vc/refresh/testID/v1.0", + res.VerifiablePresentationRequest.Interact.Service[0].ServiceEndpoint) + + assert.EqualValues(t, recorder.Result().StatusCode, http.StatusOK) + }) + + t.Run("no updates", func(t *testing.T) { + recorder := httptest.NewRecorder() + + profileSvc := NewMockProfileService(gomock.NewController(t)) + refreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + + echoCtx := echoContext( + withRecorder(recorder), + ) + + echoCtx.Request().URL.Path = "/refresh/" + profileID + "/" + profileVersion + + issuer := &profileapi.Issuer{} + profileSvc.EXPECT().GetProfile(profileID, profileVersion). + Return(issuer, nil) + + refreshSvc.EXPECT().RequestRefreshStatus(gomock.Any(), "some-cred-id", *issuer). + Return(nil, nil) + + ctr := refresh.NewController(&refresh.Config{ + ProfileService: profileSvc, + RefreshService: refreshSvc, + IssuerVCSPublicHost: "https://public.local/api/vc", + }) + + assert.NoError(t, ctr.RequestRefreshStatus(echoCtx, profileID, profileVersion, refresh.RequestRefreshStatusParams{ + CredentialID: "some-cred-id", + })) + + assert.EqualValues(t, recorder.Result().StatusCode, http.StatusNoContent) + }) + + t.Run("refresh err", func(t *testing.T) { + recorder := httptest.NewRecorder() + + profileSvc := NewMockProfileService(gomock.NewController(t)) + refreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + + echoCtx := echoContext( + withRecorder(recorder), + ) + + echoCtx.Request().URL.Path = "/refresh/" + profileID + "/" + profileVersion + + issuer := &profileapi.Issuer{} + profileSvc.EXPECT().GetProfile(profileID, profileVersion). + Return(issuer, nil) + + refreshSvc.EXPECT().RequestRefreshStatus( + gomock.Any(), + "some-cred-id", + *issuer, + ).Return(nil, errors.New("refresh err")) + + ctr := refresh.NewController(&refresh.Config{ + ProfileService: profileSvc, + RefreshService: refreshSvc, + IssuerVCSPublicHost: "https://public.local/api/vc", + }) + + assert.ErrorContains(t, + ctr.RequestRefreshStatus(echoCtx, + profileID, + profileVersion, + refresh.RequestRefreshStatusParams{ + CredentialID: "some-cred-id", + }), "refresh err") + }) + + t.Run("profile err", func(t *testing.T) { + recorder := httptest.NewRecorder() + + profileSvc := NewMockProfileService(gomock.NewController(t)) + refreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + + echoCtx := echoContext( + withRecorder(recorder), + ) + + echoCtx.Request().URL.Path = "/refresh/" + profileID + "/" + profileVersion + + profileSvc.EXPECT().GetProfile(profileID, profileVersion). + Return(nil, errors.New("profile err")) + + ctr := refresh.NewController(&refresh.Config{ + ProfileService: profileSvc, + RefreshService: refreshSvc, + IssuerVCSPublicHost: "https://public.local/api/vc", + }) + + assert.ErrorContains(t, ctr.RequestRefreshStatus( + echoCtx, + profileID, + profileVersion, + refresh.RequestRefreshStatusParams{ + CredentialID: "some-cred-id", + }), "profile err") + }) +} + +func TestGetRefreshedCredential(t *testing.T) { + t.Run("success", func(t *testing.T) { + recorder := httptest.NewRecorder() + + profileSvc := NewMockProfileService(gomock.NewController(t)) + refreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + proofChecker := NewMockProofChecker(gomock.NewController(t)) + + proofChecker.EXPECT().CheckJWTProof(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + + signedRequestedCredentialsVP := testutil.SignedVP(t, requestedCredentialsVP, vcs.Jwt) + + presBody, err := signedRequestedCredentialsVP.Presentation.MarshalJSON() + assert.NoError(t, err) + + reqBody, err := json.Marshal(refresh.GetRefreshedCredentialReq{ + VerifiablePresentation: presBody, + }) + assert.NoError(t, err) + + echoCtx := echoContext( + withRecorder(recorder), + withRequestBody(reqBody), + ) + + issuer := &profileapi.Issuer{} + profileSvc.EXPECT().GetProfile(profileID, profileVersion). + Return(issuer, nil) + + refreshSvc.EXPECT().GetRefreshedCredential(gomock.Any(), gomock.Any(), *issuer). + Return(signedRequestedCredentialsVP.Presentation.Credentials()[0], nil) + + ctr := refresh.NewController(&refresh.Config{ + ProfileService: profileSvc, + RefreshService: refreshSvc, + ProofChecker: proofChecker, + DocumentLoader: testutil.DocumentLoader(t), + }) + + assert.NoError(t, ctr.GetRefreshedCredential(echoCtx, profileID, profileVersion, + refresh.GetRefreshedCredentialParams{})) + }) + + t.Run("svc error", func(t *testing.T) { + recorder := httptest.NewRecorder() + + profileSvc := NewMockProfileService(gomock.NewController(t)) + refreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + proofChecker := NewMockProofChecker(gomock.NewController(t)) + + proofChecker.EXPECT().CheckJWTProof(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + + signedRequestedCredentialsVP := testutil.SignedVP(t, requestedCredentialsVP, vcs.Jwt) + + presBody, err := signedRequestedCredentialsVP.Presentation.MarshalJSON() + assert.NoError(t, err) + + reqBody, err := json.Marshal(refresh.GetRefreshedCredentialReq{ + VerifiablePresentation: presBody, + }) + assert.NoError(t, err) + + echoCtx := echoContext( + withRecorder(recorder), + withRequestBody(reqBody), + ) + + issuer := &profileapi.Issuer{} + profileSvc.EXPECT().GetProfile(profileID, profileVersion). + Return(issuer, nil) + + refreshSvc.EXPECT().GetRefreshedCredential(gomock.Any(), gomock.Any(), *issuer). + Return(nil, errors.New("svc error")) + + ctr := refresh.NewController(&refresh.Config{ + ProfileService: profileSvc, + RefreshService: refreshSvc, + ProofChecker: proofChecker, + DocumentLoader: testutil.DocumentLoader(t), + }) + + assert.ErrorContains(t, ctr.GetRefreshedCredential(echoCtx, profileID, profileVersion, + refresh.GetRefreshedCredentialParams{}), "svc error") + }) + + t.Run("svc error", func(t *testing.T) { + recorder := httptest.NewRecorder() + + profileSvc := NewMockProfileService(gomock.NewController(t)) + refreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + proofChecker := NewMockProofChecker(gomock.NewController(t)) + + proofChecker.EXPECT().CheckJWTProof(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + + signedRequestedCredentialsVP := testutil.SignedVP(t, requestedCredentialsVP, vcs.Jwt) + + presBody, err := signedRequestedCredentialsVP.Presentation.MarshalJSON() + assert.NoError(t, err) + + reqBody, err := json.Marshal(refresh.GetRefreshedCredentialReq{ + VerifiablePresentation: presBody, + }) + assert.NoError(t, err) + + echoCtx := echoContext( + withRecorder(recorder), + withRequestBody(reqBody), + ) + + issuer := &profileapi.Issuer{} + profileSvc.EXPECT().GetProfile(profileID, profileVersion). + Return(issuer, errors.New("no profile found")) + + ctr := refresh.NewController(&refresh.Config{ + ProfileService: profileSvc, + RefreshService: refreshSvc, + ProofChecker: proofChecker, + DocumentLoader: testutil.DocumentLoader(t), + }) + + assert.ErrorContains(t, ctr.GetRefreshedCredential(echoCtx, profileID, profileVersion, + refresh.GetRefreshedCredentialParams{}), "no profile found") + }) + + t.Run("invalid body", func(t *testing.T) { + recorder := httptest.NewRecorder() + + profileSvc := NewMockProfileService(gomock.NewController(t)) + refreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + proofChecker := NewMockProofChecker(gomock.NewController(t)) + + echoCtx := echoContext( + withRecorder(recorder), + withRequestBody([]byte{0x1}), + ) + + ctr := refresh.NewController(&refresh.Config{ + ProfileService: profileSvc, + RefreshService: refreshSvc, + ProofChecker: proofChecker, + DocumentLoader: testutil.DocumentLoader(t), + }) + + assert.ErrorContains(t, ctr.GetRefreshedCredential(echoCtx, profileID, profileVersion, + refresh.GetRefreshedCredentialParams{}), "invalid character") + }) + + t.Run("invalid proof", func(t *testing.T) { + recorder := httptest.NewRecorder() + + profileSvc := NewMockProfileService(gomock.NewController(t)) + refreshSvc := NewMockCredentialRefreshService(gomock.NewController(t)) + proofChecker := NewMockProofChecker(gomock.NewController(t)) + + proofChecker.EXPECT().CheckJWTProof(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(errors.New("invalid proof")) + + signedRequestedCredentialsVP := testutil.SignedVP(t, requestedCredentialsVP, vcs.Jwt) + + presBody, err := signedRequestedCredentialsVP.Presentation.MarshalJSON() + assert.NoError(t, err) + + reqBody, err := json.Marshal(refresh.GetRefreshedCredentialReq{ + VerifiablePresentation: presBody, + }) + assert.NoError(t, err) + + echoCtx := echoContext( + withRecorder(recorder), + withRequestBody(reqBody), + ) + + ctr := refresh.NewController(&refresh.Config{ + ProfileService: profileSvc, + RefreshService: refreshSvc, + ProofChecker: proofChecker, + DocumentLoader: testutil.DocumentLoader(t), + }) + + assert.ErrorContains(t, ctr.GetRefreshedCredential(echoCtx, profileID, profileVersion, + refresh.GetRefreshedCredentialParams{}), "invalid proof") + }) +} + +type options struct { + tenantID string + requestBody []byte + responseWriter http.ResponseWriter +} + +type contextOpt func(*options) + +func withRequestBody(body []byte) contextOpt { + return func(o *options) { + o.requestBody = body + } +} + +func withRecorder(w http.ResponseWriter) contextOpt { + return func(o *options) { + o.responseWriter = w + } +} + +func echoContext(opts ...contextOpt) echo.Context { + o := &options{ + tenantID: orgID, + responseWriter: httptest.NewRecorder(), + } + + for _, fn := range opts { + fn(o) + } + + e := echo.New() + + var body io.Reader = http.NoBody + + if o.requestBody != nil { + body = bytes.NewReader(o.requestBody) + } + + req := httptest.NewRequest(http.MethodPost, "/", body) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + + if o.tenantID != "" { + req.Header.Set("X-Tenant-ID", o.tenantID) + } + + return e.NewContext(req, o.responseWriter) +} diff --git a/pkg/restapi/v1/refresh/interfaces.go b/pkg/restapi/v1/refresh/interfaces.go new file mode 100644 index 000000000..62a9e9e3f --- /dev/null +++ b/pkg/restapi/v1/refresh/interfaces.go @@ -0,0 +1,60 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package refresh + +import ( + "context" + + "github.com/trustbloc/did-go/doc/ld/processor" + "github.com/trustbloc/did-go/doc/ld/proof" + "github.com/trustbloc/kms-go/doc/jose" + "github.com/trustbloc/vc-go/proof/checker" + "github.com/trustbloc/vc-go/verifiable" + + profileapi "github.com/trustbloc/vcs/pkg/profile" + "github.com/trustbloc/vcs/pkg/service/refresh" +) + +//go:generate mockgen -destination interfaces_mocks_test.go -package refresh_test -source=interfaces.go + +// ProfileService defines issuer profile service interface. +type ProfileService interface { + GetProfile(profileID profileapi.ID, profileVersion profileapi.Version) (*profileapi.Issuer, error) +} + +// CredentialRefreshService defines credential refresh service interface. +type CredentialRefreshService interface { + RequestRefreshStatus( + ctx context.Context, + credentialID string, + issuer profileapi.Issuer, + ) (*refresh.GetRefreshStateResponse, error) + + GetRefreshedCredential( + ctx context.Context, + presentation *verifiable.Presentation, + issuer profileapi.Issuer, + ) (*verifiable.Credential, error) +} + +type ProofChecker interface { + CheckLDProof(proof *proof.Proof, expectedProofIssuer string, msg, signature []byte) error + + // GetLDPCanonicalDocument will return normalized/canonical version of the document + GetLDPCanonicalDocument(proof *proof.Proof, doc map[string]interface{}, opts ...processor.Opts) ([]byte, error) + + // GetLDPDigest returns document digest + GetLDPDigest(proof *proof.Proof, doc []byte) ([]byte, error) + + CheckJWTProof(headers jose.Headers, expectedProofIssuer string, msg, signature []byte) error + CheckCWTProof( + checkCWTRequest checker.CheckCWTProofRequest, + expectedProofIssuer string, + msg []byte, + signature []byte, + ) error +} diff --git a/pkg/restapi/v1/refresh/openapi.cfg.yaml b/pkg/restapi/v1/refresh/openapi.cfg.yaml new file mode 100644 index 000000000..1db1045cd --- /dev/null +++ b/pkg/restapi/v1/refresh/openapi.cfg.yaml @@ -0,0 +1,15 @@ +# +# Copyright SecureKey Technologies Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +package: refresh +output: openapi.gen.go +generate: + models: true + echo-server: true + embedded-spec: false +output-options: + include-tags: + - refresh diff --git a/pkg/restapi/v1/refresh/openapi.gen.go b/pkg/restapi/v1/refresh/openapi.gen.go new file mode 100644 index 000000000..83806c5fa --- /dev/null +++ b/pkg/restapi/v1/refresh/openapi.gen.go @@ -0,0 +1,187 @@ +// Package refresh provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/deepmap/oapi-codegen version v1.11.0 DO NOT EDIT. +package refresh + +import ( + "fmt" + "net/http" + + "github.com/deepmap/oapi-codegen/pkg/runtime" + "github.com/labstack/echo/v4" +) + +// Model for Credential Update Response. +type CredentialRefreshAvailableResponse struct { + VerifiablePresentationRequest VerifiablePresentationRequest `json:"verifiablePresentationRequest"` +} + +// Model for getting refreshed credential. +type GetRefreshedCredentialReq struct { + // Verifiable Presentation. + VerifiablePresentation []byte `json:"verifiable_presentation"` +} + +// Model for getting refreshed credential. +type GetRefreshedCredentialResp struct { + VerifiableCredential interface{} `json:"verifiable_credential"` +} + +// RefreshService defines model for RefreshService. +type RefreshService struct { + // Service endpoint. + ServiceEndpoint string `json:"serviceEndpoint"` + + // Service type. + Type string `json:"type"` +} + +// RefreshServiceInteract defines model for RefreshServiceInteract. +type RefreshServiceInteract struct { + Service []RefreshService `json:"service"` +} + +// VerifiablePresentationRequest defines model for VerifiablePresentationRequest. +type VerifiablePresentationRequest struct { + // Challenge value. + Challenge string `json:"challenge"` + + // Domain value. + Domain string `json:"domain"` + Interact RefreshServiceInteract `json:"interact"` + + // Credential Presentation Query. + Query map[string]interface{} `json:"query"` +} + +// RequestRefreshStatusParams defines parameters for RequestRefreshStatus. +type RequestRefreshStatusParams struct { + // Credential ID + CredentialID string `form:"credentialID" json:"credentialID"` +} + +// GetRefreshedCredentialJSONBody defines parameters for GetRefreshedCredential. +type GetRefreshedCredentialJSONBody = GetRefreshedCredentialReq + +// GetRefreshedCredentialParams defines parameters for GetRefreshedCredential. +type GetRefreshedCredentialParams struct { + // Credential ID + CredentialID string `form:"credentialID" json:"credentialID"` +} + +// GetRefreshedCredentialJSONRequestBody defines body for GetRefreshedCredential for application/json ContentType. +type GetRefreshedCredentialJSONRequestBody = GetRefreshedCredentialJSONBody + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Get refresh status for credential. + // (GET /refresh/{profileID}/{profileVersion}) + RequestRefreshStatus(ctx echo.Context, profileID string, profileVersion string, params RequestRefreshStatusParams) error + // Receive updated (refreshed) credentials. + // (POST /refresh/{profileID}/{profileVersion}) + GetRefreshedCredential(ctx echo.Context, profileID string, profileVersion string, params GetRefreshedCredentialParams) error +} + +// ServerInterfaceWrapper converts echo contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface +} + +// RequestRefreshStatus converts echo context to params. +func (w *ServerInterfaceWrapper) RequestRefreshStatus(ctx echo.Context) error { + var err error + // ------------- Path parameter "profileID" ------------- + var profileID string + + err = runtime.BindStyledParameterWithLocation("simple", false, "profileID", runtime.ParamLocationPath, ctx.Param("profileID"), &profileID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter profileID: %s", err)) + } + + // ------------- Path parameter "profileVersion" ------------- + var profileVersion string + + err = runtime.BindStyledParameterWithLocation("simple", false, "profileVersion", runtime.ParamLocationPath, ctx.Param("profileVersion"), &profileVersion) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter profileVersion: %s", err)) + } + + // Parameter object where we will unmarshal all parameters from the context + var params RequestRefreshStatusParams + // ------------- Required query parameter "credentialID" ------------- + + err = runtime.BindQueryParameter("form", true, true, "credentialID", ctx.QueryParams(), ¶ms.CredentialID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter credentialID: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.RequestRefreshStatus(ctx, profileID, profileVersion, params) + return err +} + +// GetRefreshedCredential converts echo context to params. +func (w *ServerInterfaceWrapper) GetRefreshedCredential(ctx echo.Context) error { + var err error + // ------------- Path parameter "profileID" ------------- + var profileID string + + err = runtime.BindStyledParameterWithLocation("simple", false, "profileID", runtime.ParamLocationPath, ctx.Param("profileID"), &profileID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter profileID: %s", err)) + } + + // ------------- Path parameter "profileVersion" ------------- + var profileVersion string + + err = runtime.BindStyledParameterWithLocation("simple", false, "profileVersion", runtime.ParamLocationPath, ctx.Param("profileVersion"), &profileVersion) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter profileVersion: %s", err)) + } + + // Parameter object where we will unmarshal all parameters from the context + var params GetRefreshedCredentialParams + // ------------- Required query parameter "credentialID" ------------- + + err = runtime.BindQueryParameter("form", true, true, "credentialID", ctx.QueryParams(), ¶ms.CredentialID) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter credentialID: %s", err)) + } + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetRefreshedCredential(ctx, profileID, profileVersion, params) + return err +} + +// This is a simple interface which specifies echo.Route addition functions which +// are present on both echo.Echo and echo.Group, since we want to allow using +// either of them for path registration +type EchoRouter interface { + CONNECT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + DELETE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + HEAD(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + OPTIONS(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PATCH(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + POST(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + PUT(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route + TRACE(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) *echo.Route +} + +// RegisterHandlers adds each server route to the EchoRouter. +func RegisterHandlers(router EchoRouter, si ServerInterface) { + RegisterHandlersWithBaseURL(router, si, "") +} + +// Registers handlers, and prepends BaseURL to the paths, so that the paths +// can be served under a prefix. +func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL string) { + + wrapper := ServerInterfaceWrapper{ + Handler: si, + } + + router.GET(baseURL+"/refresh/:profileID/:profileVersion", wrapper.RequestRefreshStatus) + router.POST(baseURL+"/refresh/:profileID/:profileVersion", wrapper.GetRefreshedCredential) + +} diff --git a/pkg/restapi/v1/refresh/testdata/requested_credentials_vp.jsonld b/pkg/restapi/v1/refresh/testdata/requested_credentials_vp.jsonld new file mode 100644 index 000000000..34fdb95e2 --- /dev/null +++ b/pkg/restapi/v1/refresh/testdata/requested_credentials_vp.jsonld @@ -0,0 +1,42 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + "https://trustbloc.github.io/context/vc/examples-v1.jsonld", + "https://w3id.org/vc/status-list/2021/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ], + "id": "urn:uuid:3978344f-8596-4c3a-a978-8fcaba3903c5", + "type": "VerifiablePresentation", + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "id": "http://example.edu/credentials/58473", + "type": [ + "VerifiableCredential", + "UniversityDegreeCredential" + ], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "alumniOf": "Example University" + }, + "credentialStatus": { + "id": "urn:uuid:71d57ab4-9e3a-4267-a3fd-e64d661c0df3", + "statusListCredential": "https://test.com/2.json", + "statusListIndex": "17", + "statusPurpose": "revocation", + "type": "StatusList2021Entry" + }, + "proof": { + "type": "RsaSignature2018" + } + } + ], + "holder": "did:trustblock:abc" +} diff --git a/pkg/restapi/v1/util/validate.go b/pkg/restapi/v1/util/validate.go index 21e96b38d..78d5302b7 100644 --- a/pkg/restapi/v1/util/validate.go +++ b/pkg/restapi/v1/util/validate.go @@ -14,12 +14,12 @@ import ( vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" "github.com/trustbloc/vcs/pkg/restapi/resterr" "github.com/trustbloc/vcs/pkg/restapi/v1/common" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) func ValidateAuthorizationDetails( - authorizationDetails []common.AuthorizationDetails) ([]*oidc4ci.AuthorizationDetails, error) { - var authorizationDetailsDomain []*oidc4ci.AuthorizationDetails + authorizationDetails []common.AuthorizationDetails) ([]*issuecredential.AuthorizationDetails, error) { + var authorizationDetailsDomain []*issuecredential.AuthorizationDetails for _, ad := range authorizationDetails { if ad.Type != "openid_credential" { @@ -30,7 +30,7 @@ func ValidateAuthorizationDetails( oidcCredentialFormat := lo.FromPtr(ad.Format) credentialConfigurationID := lo.FromPtr(ad.CredentialConfigurationId) - mapped := &oidc4ci.AuthorizationDetails{ + mapped := &issuecredential.AuthorizationDetails{ Type: ad.Type, Locations: lo.FromPtr(ad.Locations), CredentialConfigurationID: "", @@ -54,7 +54,7 @@ func ValidateAuthorizationDetails( "authorization_details.credential_definition", errors.New("not supplied")) } - mapped.CredentialDefinition = &oidc4ci.CredentialDefinition{ + mapped.CredentialDefinition = &issuecredential.CredentialDefinition{ Context: lo.FromPtr(ad.CredentialDefinition.Context), CredentialSubject: lo.FromPtr(ad.CredentialDefinition.CredentialSubject), Type: ad.CredentialDefinition.Type, diff --git a/pkg/restapi/v1/util/validate_test.go b/pkg/restapi/v1/util/validate_test.go index a0cc5cbdc..0fdfb72c8 100644 --- a/pkg/restapi/v1/util/validate_test.go +++ b/pkg/restapi/v1/util/validate_test.go @@ -16,7 +16,7 @@ import ( vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" "github.com/trustbloc/vcs/pkg/restapi/v1/common" "github.com/trustbloc/vcs/pkg/restapi/v1/util" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) func TestValidateAuthorizationDetails(t *testing.T) { @@ -26,7 +26,7 @@ func TestValidateAuthorizationDetails(t *testing.T) { tests := []struct { name string args args - want []*oidc4ci.AuthorizationDetails + want []*issuecredential.AuthorizationDetails wantErr bool errorContains string }{ @@ -50,7 +50,7 @@ func TestValidateAuthorizationDetails(t *testing.T) { }, }, }, - want: []*oidc4ci.AuthorizationDetails{ + want: []*issuecredential.AuthorizationDetails{ { Type: "openid_credential", Locations: []string{"https://example.com/rs1", "https://example.com/rs2"}, @@ -101,13 +101,13 @@ func TestValidateAuthorizationDetails(t *testing.T) { }, }, }, - want: []*oidc4ci.AuthorizationDetails{ + want: []*issuecredential.AuthorizationDetails{ { Type: "openid_credential", Locations: []string{"https://example.com/rs1", "https://example.com/rs2"}, CredentialConfigurationID: "", Format: vcsverifiable.OIDCFormat("jwt_vc_json"), - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Context: []string{"https://example.com/context/1", "https://example.com/context/2"}, CredentialSubject: map[string]interface{}{ "key": "value", @@ -120,7 +120,7 @@ func TestValidateAuthorizationDetails(t *testing.T) { Locations: []string{"https://example.com/rs1", "https://example.com/rs2"}, CredentialConfigurationID: "", Format: vcsverifiable.OIDCFormat("jwt_vc_json"), - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Context: []string{"https://example.com/context/1", "https://example.com/context/2"}, CredentialSubject: map[string]interface{}{ "key": "value", diff --git a/pkg/service/clientidscheme/clientidscheme_service.go b/pkg/service/clientidscheme/clientidscheme_service.go index a48e20b2a..f988424dd 100644 --- a/pkg/service/clientidscheme/clientidscheme_service.go +++ b/pkg/service/clientidscheme/clientidscheme_service.go @@ -23,7 +23,7 @@ import ( profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/restapi/resterr" "github.com/trustbloc/vcs/pkg/service/clientmanager" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) const ( @@ -45,7 +45,7 @@ type profileService interface { } type transactionStore interface { - FindByOpState(ctx context.Context, opState string) (*oidc4ci.Transaction, error) + FindByOpState(ctx context.Context, opState string) (*issuecredential.Transaction, error) } // Config defines configuration for Service. diff --git a/pkg/service/clientidscheme/clientidscheme_service_test.go b/pkg/service/clientidscheme/clientidscheme_service_test.go index 3d2a8ce1a..460c7ef35 100644 --- a/pkg/service/clientidscheme/clientidscheme_service_test.go +++ b/pkg/service/clientidscheme/clientidscheme_service_test.go @@ -23,7 +23,7 @@ import ( "github.com/trustbloc/vcs/pkg/restapi/resterr" "github.com/trustbloc/vcs/pkg/service/clientidscheme" "github.com/trustbloc/vcs/pkg/service/clientmanager" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) func TestService_Register(t *testing.T) { @@ -107,8 +107,8 @@ func TestService_Register(t *testing.T) { }, }, nil) - store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ ProfileID: "profileID", ProfileVersion: "profileVersion", }, @@ -178,8 +178,8 @@ func TestService_Register(t *testing.T) { }, }, nil) - store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ ProfileID: "profileID", ProfileVersion: "profileVersion", }, @@ -235,8 +235,8 @@ func TestService_Register(t *testing.T) { profileService.EXPECT().GetProfile("profileID", "profileVersion"). Return(nil, errors.New("get profile error")) - store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ ProfileID: "profileID", ProfileVersion: "profileVersion", }, @@ -263,8 +263,8 @@ func TestService_Register(t *testing.T) { }, }, nil) - store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ ProfileID: "profileID", ProfileVersion: "profileVersion", }, @@ -291,8 +291,8 @@ func TestService_Register(t *testing.T) { }, }, nil) - store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ ProfileID: "profileID", ProfileVersion: "profileVersion", }, @@ -322,8 +322,8 @@ func TestService_Register(t *testing.T) { }, }, nil) - store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ ProfileID: "profileID", ProfileVersion: "profileVersion", }, @@ -365,8 +365,8 @@ func TestService_Register(t *testing.T) { }, }, nil) - store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + store.EXPECT().FindByOpState(gomock.Any(), issuerState).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ ProfileID: "profileID", ProfileVersion: "profileVersion", }, diff --git a/pkg/service/oidc4ci/composer.go b/pkg/service/issuecredential/composer.go similarity index 67% rename from pkg/service/oidc4ci/composer.go rename to pkg/service/issuecredential/composer.go index ab3c2af78..ab700a56f 100644 --- a/pkg/service/oidc4ci/composer.go +++ b/pkg/service/issuecredential/composer.go @@ -4,7 +4,7 @@ Copyright Avast Software. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -package oidc4ci +package issuecredential import ( "bytes" @@ -27,16 +27,15 @@ func NewCredentialComposer() *CredentialComposer { func (c *CredentialComposer) Compose( _ context.Context, credential *verifiable.Credential, - tx *Transaction, - txCredentialConfiguration *TxCredentialConfiguration, - prepRequest *PrepareCredentialRequest, + req *PrepareCredentialsRequest, ) (*verifiable.Credential, error) { - if txCredentialConfiguration == nil || txCredentialConfiguration.CredentialComposeConfiguration == nil { + if req == nil || req.CredentialConfiguration == nil || + req.CredentialConfiguration.CredentialComposeConfiguration == nil { return credential, nil } - if idTemplate := txCredentialConfiguration.CredentialComposeConfiguration.IDTemplate; idTemplate != "" { - params := c.baseParams(tx) + if idTemplate := req.CredentialConfiguration.CredentialComposeConfiguration.IDTemplate; idTemplate != "" { + params := c.baseParams(req) params["CredentialID"] = credential.Contents().ID id, err := c.renderRaw(idTemplate, params) @@ -47,21 +46,21 @@ func (c *CredentialComposer) Compose( credential = credential.WithModifiedID(id) } - if txCredentialConfiguration.CredentialComposeConfiguration.OverrideIssuer { + if req.CredentialConfiguration.CredentialComposeConfiguration.OverrideIssuer { issuer := credential.Contents().Issuer if issuer == nil { issuer = &verifiable.Issuer{} } - issuer.ID = tx.DID + issuer.ID = req.IssuerDID credential = credential.WithModifiedIssuer(issuer) } - if txCredentialConfiguration.CredentialComposeConfiguration.OverrideSubjectDID { + if req.CredentialConfiguration.CredentialComposeConfiguration.OverrideSubjectDID { var newSubjects []verifiable.Subject for _, s := range credential.Contents().Subject { - s.ID = prepRequest.DID + s.ID = req.SubjectDID newSubjects = append(newSubjects, s) } @@ -69,8 +68,8 @@ func (c *CredentialComposer) Compose( credential = credential.WithModifiedSubject(newSubjects) } - if credential.Contents().Expired == nil && txCredentialConfiguration.CredentialExpiresAt != nil { - credential = credential.WithModifiedExpired(util.NewTime(*txCredentialConfiguration.CredentialExpiresAt)) + if credential.Contents().Expired == nil && req.CredentialConfiguration.CredentialExpiresAt != nil { + credential = credential.WithModifiedExpired(util.NewTime(*req.CredentialConfiguration.CredentialExpiresAt)) } if credential.Contents().Issued == nil { @@ -81,12 +80,12 @@ func (c *CredentialComposer) Compose( } func (c *CredentialComposer) baseParams( - tx *Transaction, + tx *PrepareCredentialsRequest, ) map[string]interface{} { result := map[string]interface{}{ "RandomID": uuid.NewString(), - "TxID": tx.ID, - "IssuerDID": tx.DID, + "TxID": tx.TxID, + "IssuerDID": tx.IssuerDID, } return result diff --git a/pkg/service/oidc4ci/composer_test.go b/pkg/service/issuecredential/composer_test.go similarity index 62% rename from pkg/service/oidc4ci/composer_test.go rename to pkg/service/issuecredential/composer_test.go index 9aed6dd21..b6aa826a4 100644 --- a/pkg/service/oidc4ci/composer_test.go +++ b/pkg/service/issuecredential/composer_test.go @@ -1,4 +1,4 @@ -package oidc4ci_test +package issuecredential_test import ( "context" @@ -10,12 +10,12 @@ import ( util "github.com/trustbloc/did-go/doc/util/time" "github.com/trustbloc/vc-go/verifiable" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) func TestComposer(t *testing.T) { t.Run("success", func(t *testing.T) { - srv := oidc4ci.NewCredentialComposer() + srv := issuecredential.NewCredentialComposer() cred, err := verifiable.CreateCredential(verifiable.CredentialContents{ Types: []string{"VerifiableCredential"}, @@ -31,23 +31,19 @@ func TestComposer(t *testing.T) { resp, err := srv.Compose( context.TODO(), cred, - &oidc4ci.Transaction{ - ID: "some-awesome-id", - TransactionData: oidc4ci.TransactionData{ - DID: "did:example:123", + &issuecredential.PrepareCredentialsRequest{ + TxID: "some-awesome-id", + IssuerDID: "did:example:123", + SubjectDID: "some-awesome-did", + CredentialConfiguration: &issuecredential.TxCredentialConfiguration{ + CredentialComposeConfiguration: &issuecredential.CredentialComposeConfiguration{ + IDTemplate: "hardcoded:{{.TxID}}:suffix", + OverrideIssuer: true, + OverrideSubjectDID: true, + }, + CredentialExpiresAt: &expectedExpiration, }, }, - &oidc4ci.TxCredentialConfiguration{ - CredentialComposeConfiguration: &oidc4ci.CredentialComposeConfiguration{ - IDTemplate: "hardcoded:{{.TxID}}:suffix", - OverrideIssuer: true, - OverrideSubjectDID: true, - }, - CredentialExpiresAt: &expectedExpiration, - }, - &oidc4ci.PrepareCredentialRequest{ - DID: "some-awesome-did", - }, ) assert.NotNil(t, resp.Contents().Issued) @@ -73,7 +69,7 @@ func TestComposer(t *testing.T) { }) t.Run("success with prev-id", func(t *testing.T) { - srv := oidc4ci.NewCredentialComposer() + srv := issuecredential.NewCredentialComposer() cred, err := verifiable.CreateCredential(verifiable.CredentialContents{ ID: "some-id", @@ -92,22 +88,18 @@ func TestComposer(t *testing.T) { resp, err := srv.Compose( context.TODO(), cred, - &oidc4ci.Transaction{ - ID: "some-awesome-id", - TransactionData: oidc4ci.TransactionData{ - DID: "did:example:123", - }, - }, - &oidc4ci.TxCredentialConfiguration{ - CredentialComposeConfiguration: &oidc4ci.CredentialComposeConfiguration{ - IDTemplate: "{{.CredentialID}}:suffix", - OverrideIssuer: true, - OverrideSubjectDID: true, + &issuecredential.PrepareCredentialsRequest{ + TxID: "some-awesome-id", + IssuerDID: "did:example:123", + SubjectDID: "some-awesome-did", + CredentialConfiguration: &issuecredential.TxCredentialConfiguration{ + CredentialComposeConfiguration: &issuecredential.CredentialComposeConfiguration{ + IDTemplate: "{{.CredentialID}}:suffix", + OverrideIssuer: true, + OverrideSubjectDID: true, + }, + CredentialExpiresAt: lo.ToPtr(time.Now()), }, - CredentialExpiresAt: lo.ToPtr(time.Now()), - }, - &oidc4ci.PrepareCredentialRequest{ - DID: "some-awesome-did", }, ) @@ -123,7 +115,7 @@ func TestComposer(t *testing.T) { }) t.Run("invalid template", func(t *testing.T) { - srv := oidc4ci.NewCredentialComposer() + srv := issuecredential.NewCredentialComposer() cred, err := verifiable.CreateCredential(verifiable.CredentialContents{}, verifiable.CustomFields{}) assert.NoError(t, err) @@ -131,19 +123,19 @@ func TestComposer(t *testing.T) { resp, err := srv.Compose( context.TODO(), cred, - &oidc4ci.Transaction{ - ID: "some-awesome-id", - TransactionData: oidc4ci.TransactionData{ - DID: "did:example:123", - }, - }, - &oidc4ci.TxCredentialConfiguration{ - CredentialComposeConfiguration: &oidc4ci.CredentialComposeConfiguration{ - IDTemplate: "hardcoded:{{.NotExistingValue.$x}}:suffix", - OverrideIssuer: true, + &issuecredential.PrepareCredentialsRequest{ + TxID: "some-awesome-id", + IssuerDID: "did:example:123", + SubjectDID: "some-awesome-did", + CredentialConfiguration: &issuecredential.TxCredentialConfiguration{ + CredentialComposeConfiguration: &issuecredential.CredentialComposeConfiguration{ + IDTemplate: "hardcoded:{{.NotExistingValue.$x}}:suffix", + OverrideIssuer: true, + OverrideSubjectDID: true, + }, + CredentialExpiresAt: lo.ToPtr(time.Now()), }, }, - &oidc4ci.PrepareCredentialRequest{}, ) assert.ErrorContains(t, err, "bad character") @@ -151,12 +143,12 @@ func TestComposer(t *testing.T) { }) t.Run("missing compose", func(t *testing.T) { - srv := oidc4ci.NewCredentialComposer() + srv := issuecredential.NewCredentialComposer() cred, err := verifiable.CreateCredential(verifiable.CredentialContents{}, verifiable.CustomFields{}) assert.NoError(t, err) - resp, err := srv.Compose(context.TODO(), cred, nil, nil, nil) + resp, err := srv.Compose(context.TODO(), cred, nil) assert.Equal(t, cred, resp) assert.NoError(t, err) }) diff --git a/pkg/service/issuecredential/api.go b/pkg/service/issuecredential/interfaces.go similarity index 61% rename from pkg/service/issuecredential/api.go rename to pkg/service/issuecredential/interfaces.go index e18c6c9c7..d563f9574 100644 --- a/pkg/service/issuecredential/api.go +++ b/pkg/service/issuecredential/interfaces.go @@ -14,6 +14,16 @@ import ( profileapi "github.com/trustbloc/vcs/pkg/profile" ) +//go:generate mockgen -destination interfaces_mocks_test.go -package issuecredential_test -source=interfaces.go + +type composer interface { + Compose( + ctx context.Context, + cred *verifiable.Credential, + req *PrepareCredentialsRequest, + ) (*verifiable.Credential, error) +} + type ServiceInterface interface { IssueCredential( ctx context.Context, diff --git a/pkg/service/issuecredential/prepare_credential.go b/pkg/service/issuecredential/prepare_credential.go new file mode 100644 index 000000000..9f9b61698 --- /dev/null +++ b/pkg/service/issuecredential/prepare_credential.go @@ -0,0 +1,160 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package issuecredential + +import ( + "context" + "fmt" + "net/url" + "time" + + "github.com/google/uuid" + util "github.com/trustbloc/did-go/doc/util/time" + "github.com/trustbloc/vc-go/verifiable" +) + +const ( + defaultCtx = "https://www.w3.org/2018/credentials/v1" +) + +type PrepareCredentialService struct { + cfg *PrepareCredentialServiceConfig +} + +type PrepareCredentialServiceConfig struct { + VcsAPIURL string + Composer composer +} + +func NewPrepareCredentialService( + cfg *PrepareCredentialServiceConfig, +) *PrepareCredentialService { + return &PrepareCredentialService{ + cfg: cfg, + } +} + +func (s *PrepareCredentialService) PrepareCredential( + ctx context.Context, + req *PrepareCredentialsRequest, +) (*verifiable.Credential, error) { + if req.CredentialConfiguration == nil { + return nil, fmt.Errorf("missing credential configuration") + } + + var finalCred *verifiable.Credential + var err error + + switch req.CredentialConfiguration.ClaimDataType { + case ClaimDataTypeClaims: + finalCred, err = s.prepareCredentialFromClaims( + ctx, + req, + ) + case ClaimDataTypeVC: + finalCred, err = s.prepareCredentialFromCompose( + ctx, + req, + ) + } + + if err != nil { + return nil, fmt.Errorf("prepare credential: %w", err) + } + + if finalCred == nil { + return nil, fmt.Errorf("prepare credential: final credential is nil") + } + + if req.RefreshServiceEnabled { + finalCred = finalCred.WithModifiedRefreshService(s.CreateRefreshService(ctx, finalCred, req)) + } + + return finalCred, err +} + +func (s *PrepareCredentialService) prepareCredentialFromClaims( + _ context.Context, + req *PrepareCredentialsRequest, +) (*verifiable.Credential, error) { + contexts := req.CredentialConfiguration.CredentialTemplate.Contexts + if len(contexts) == 0 { + contexts = []string{defaultCtx} + } + + // prepare credential for signing + vcc := verifiable.CredentialContents{ + Context: contexts, + ID: uuid.New().URN(), + Types: []string{"VerifiableCredential", req.CredentialConfiguration.CredentialTemplate.Type}, + Issuer: &verifiable.Issuer{ID: req.IssuerDID}, + Issued: util.NewTime(time.Now()), + } + + customFields := map[string]interface{}{} + + if req.CredentialConfiguration.CredentialDescription != "" { + customFields["description"] = req.CredentialConfiguration.CredentialDescription + } + if req.CredentialConfiguration.CredentialName != "" { + customFields["name"] = req.CredentialConfiguration.CredentialName + } + + if req.CredentialConfiguration.CredentialExpiresAt != nil { + vcc.Expired = util.NewTime(*req.CredentialConfiguration.CredentialExpiresAt) + } + + if req.ClaimData != nil { + vcc.Subject = []verifiable.Subject{{ + ID: req.SubjectDID, + CustomFields: req.ClaimData, + }} + } else { + vcc.Subject = []verifiable.Subject{{ID: req.SubjectDID}} + } + + return verifiable.CreateCredential(vcc, customFields) +} + +func (s *PrepareCredentialService) prepareCredentialFromCompose( + ctx context.Context, + req *PrepareCredentialsRequest, +) (*verifiable.Credential, error) { + cred, err := verifiable.ParseCredentialJSON(req.ClaimData, + verifiable.WithCredDisableValidation(), + verifiable.WithDisabledProofCheck(), + ) + if err != nil { + return nil, fmt.Errorf("parse credential json: %w", err) + } + + return s.cfg.Composer.Compose(ctx, cred, req) +} + +func (s *PrepareCredentialService) CreateRefreshService( + _ context.Context, + cred *verifiable.Credential, + req *PrepareCredentialsRequest, +) *verifiable.TypedID { + return &verifiable.TypedID{ + Type: "VerifiableCredentialRefreshService2021", + ID: s.getRefreshServiceURL(cred.Contents().ID, req.IssuerID, req.IssuerVersion), + } +} + +func (s *PrepareCredentialService) getRefreshServiceURL( + credentialID string, + issuerID string, + issuerVersion string, +) string { + return fmt.Sprintf("%s/refresh/%s/%s?credentialID=%s", + s.cfg.VcsAPIURL, + issuerID, + issuerVersion, + url.QueryEscape(credentialID), + ) +} diff --git a/pkg/service/issuecredential/prepare_credential_test.go b/pkg/service/issuecredential/prepare_credential_test.go new file mode 100644 index 000000000..fb1d3f45b --- /dev/null +++ b/pkg/service/issuecredential/prepare_credential_test.go @@ -0,0 +1,117 @@ +package issuecredential_test + +import ( + "context" + _ "embed" + "encoding/json" + "errors" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/samber/lo" + "github.com/stretchr/testify/assert" + "github.com/trustbloc/vc-go/verifiable" + + profileapi "github.com/trustbloc/vcs/pkg/profile" + "github.com/trustbloc/vcs/pkg/service/issuecredential" +) + +//go:embed testdata/university_degree.jsonld +var exampleCredential string + +func TestPrepareCredential(t *testing.T) { + t.Run("success from claims", func(t *testing.T) { + srv := issuecredential.NewPrepareCredentialService(&issuecredential.PrepareCredentialServiceConfig{}) + + cred, err := srv.PrepareCredential(context.TODO(), &issuecredential.PrepareCredentialsRequest{ + TxID: "", + ClaimData: map[string]interface{}{}, + IssuerDID: "", + SubjectDID: "", + CredentialConfiguration: &issuecredential.TxCredentialConfiguration{ + ClaimDataType: issuecredential.ClaimDataTypeClaims, + CredentialTemplate: &profileapi.CredentialTemplate{}, + CredentialDescription: "some description", + CredentialName: "some name", + CredentialExpiresAt: lo.ToPtr(time.Now()), + }, + IssuerID: "", + IssuerVersion: "", + }) + + assert.NoError(t, err) + assert.NotNil(t, cred) + }) + + t.Run("success from vc", func(t *testing.T) { + compose := NewMockcomposer(gomock.NewController(t)) + + srv := issuecredential.NewPrepareCredentialService(&issuecredential.PrepareCredentialServiceConfig{ + Composer: compose, + }) + + var parsedCred map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(exampleCredential), &parsedCred)) + + req := &issuecredential.PrepareCredentialsRequest{ + TxID: "", + ClaimData: parsedCred, + IssuerDID: "", + SubjectDID: "", + CredentialConfiguration: &issuecredential.TxCredentialConfiguration{ + ClaimDataType: issuecredential.ClaimDataTypeVC, + CredentialTemplate: &profileapi.CredentialTemplate{}, + CredentialDescription: "some description", + CredentialName: "some name", + CredentialExpiresAt: lo.ToPtr(time.Now()), + }, + IssuerID: "", + IssuerVersion: "", + } + + result := &verifiable.Credential{} + compose.EXPECT().Compose(gomock.Any(), gomock.Any(), req). + Return(result, nil) + + cred, err := srv.PrepareCredential(context.TODO(), req) + + assert.NoError(t, err) + assert.NotNil(t, cred) + }) + + t.Run("err from vc", func(t *testing.T) { + compose := NewMockcomposer(gomock.NewController(t)) + + srv := issuecredential.NewPrepareCredentialService(&issuecredential.PrepareCredentialServiceConfig{ + Composer: compose, + }) + + var parsedCred map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(exampleCredential), &parsedCred)) + + req := &issuecredential.PrepareCredentialsRequest{ + TxID: "", + ClaimData: parsedCred, + IssuerDID: "", + SubjectDID: "", + CredentialConfiguration: &issuecredential.TxCredentialConfiguration{ + ClaimDataType: issuecredential.ClaimDataTypeVC, + CredentialTemplate: &profileapi.CredentialTemplate{}, + CredentialDescription: "some description", + CredentialName: "some name", + CredentialExpiresAt: lo.ToPtr(time.Now()), + }, + IssuerID: "", + IssuerVersion: "", + } + + compose.EXPECT().Compose(gomock.Any(), gomock.Any(), req). + Return(nil, errors.New("some err")) + + cred, err := srv.PrepareCredential(context.TODO(), req) + + assert.ErrorContains(t, err, "some err") + assert.Nil(t, cred) + }) +} diff --git a/pkg/service/issuecredential/testdata/university_degree.jsonld b/pkg/service/issuecredential/testdata/university_degree.jsonld new file mode 100644 index 000000000..13ae6e4cb --- /dev/null +++ b/pkg/service/issuecredential/testdata/university_degree.jsonld @@ -0,0 +1,34 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3c-ccg.github.io/lds-jws2020/contexts/lds-jws2020-v1.json", + "https://www.w3.org/2018/credentials/examples/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "UniversityDegreeCredential" + ], + "id": "http://example.gov/credentials/3732", + "issuanceDate": "2020-03-16T22:37:26.544Z", + "issuer": { + "id": "did:trustblock:abc", + "name": "University" + }, + "credentialStatus": { + "id": "https://issuer-vcs.sandbox.trustbloc.dev/vc-issuer-test-2/status/1#0", + "type": "StatusList2021Entry", + "statusListIndex": "1", + "statusListCredential": "", + "statusPurpose": "2" + }, + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "degree": "MIT" + }, + "name": "Jayden Doe", + "spouse": "did:example:c276e12ec21ebfeb1f712ebc6f1" + } +} \ No newline at end of file diff --git a/pkg/service/issuecredential/types.go b/pkg/service/issuecredential/types.go new file mode 100644 index 000000000..fce6ccd5d --- /dev/null +++ b/pkg/service/issuecredential/types.go @@ -0,0 +1,155 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package issuecredential + +import ( + "time" + + "github.com/samber/lo" + + "github.com/trustbloc/vcs/pkg/dataprotect" + vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" + profileapi "github.com/trustbloc/vcs/pkg/profile" + "github.com/trustbloc/vcs/pkg/restapi/v1/common" +) + +type ClaimDataType int16 + +const ( + ClaimDataTypeClaims = ClaimDataType(0) + ClaimDataTypeVC = ClaimDataType(1) +) + +type PrepareCredentialsRequest struct { + TxID string + ClaimData map[string]interface{} + IssuerDID string + SubjectDID string + CredentialConfiguration *TxCredentialConfiguration + + IssuerID string + IssuerVersion string + RefreshServiceEnabled bool +} + +type TxCredentialConfiguration struct { + ID string + CredentialTemplate *profileapi.CredentialTemplate + OIDCCredentialFormat vcsverifiable.OIDCFormat + ClaimEndpoint string + ClaimDataID string + ClaimDataType ClaimDataType + CredentialName string + CredentialDescription string + CredentialExpiresAt *time.Time + PreAuthCodeExpiresAt *time.Time + CredentialConfigurationID string + // AuthorizationDetails may be defined on Authorization Request via using "authorization_details" parameter. + // If "scope" param is used, this field will stay empty. + AuthorizationDetails *AuthorizationDetails + CredentialComposeConfiguration *CredentialComposeConfiguration +} + +type CredentialComposeConfiguration struct { + IDTemplate string `json:"id_template"` + OverrideIssuer bool `json:"override_issuer"` + OverrideSubjectDID bool `json:"override_subject_did"` +} + +// AuthorizationDetails represents the domain model for Authorization Details request. +// This object is used to convey the details about the Credentials the Wallet wants to obtain. +// +// Spec: https://openid.github.io/OpenID4VCI/openid-4-verifiable-credential-issuance-wg-draft.html#section-5.1.1 +type AuthorizationDetails struct { + Type string + Format vcsverifiable.OIDCFormat + Locations []string + CredentialConfigurationID string + CredentialDefinition *CredentialDefinition + CredentialIdentifiers []string +} + +func (ad *AuthorizationDetails) ToDTO() common.AuthorizationDetails { + var credentialDefinition *common.CredentialDefinition + if cd := ad.CredentialDefinition; cd != nil { + credentialDefinition = &common.CredentialDefinition{ + Context: &cd.Context, + CredentialSubject: &cd.CredentialSubject, + Type: cd.Type, + } + } + + return common.AuthorizationDetails{ + CredentialConfigurationId: &ad.CredentialConfigurationID, + CredentialDefinition: credentialDefinition, + CredentialIdentifiers: lo.ToPtr(ad.CredentialIdentifiers), + Format: lo.ToPtr(string(ad.Format)), + Locations: &ad.Locations, + Type: ad.Type, + } +} + +// CredentialDefinition contains the detailed description of the credential type. +type CredentialDefinition struct { + // For ldp_vc only. Array as defined in https://www.w3.org/TR/vc-data-model/#contexts. + Context []string + CredentialSubject map[string]interface{} + Type []string +} + +// ClaimData represents user claims in pre-auth code flow. +type ClaimData struct { + EncryptedData *dataprotect.EncryptedData `json:"encrypted_data"` +} + +// TxID defines type for transaction ID. +type TxID string + +// Transaction is the credential issuance transaction. Issuer creates a transaction to convey the intention of issuing a +// credential with the given parameters. The transaction is stored in the transaction store and its status is updated as +// the credential issuance progresses. +type Transaction struct { + ID TxID + TransactionData +} + +// TransactionData is the transaction data stored in the underlying storage. +type TransactionData struct { + ProfileID profileapi.ID + ProfileVersion profileapi.Version + IsPreAuthFlow bool + PreAuthCode string + OrgID string + AuthorizationEndpoint string + PushedAuthorizationRequestEndpoint string + TokenEndpoint string + OpState string + RedirectURI string + GrantType string + ResponseType string + Scope []string + IssuerAuthCode string + IssuerToken string + State TransactionState + WebHookURL string + UserPin string + DID string + WalletInitiatedIssuance bool + CredentialConfiguration []*TxCredentialConfiguration + RefreshServiceEnabled bool +} + +type TransactionState int16 + +const ( + TransactionStateUnknown = TransactionState(0) + TransactionStateIssuanceInitiated = TransactionState(1) + TransactionStatePreAuthCodeValidated = TransactionState(2) // pre-auth only + TransactionStateAwaitingIssuerOIDCAuthorization = TransactionState(3) // auth only + TransactionStateIssuerOIDCAuthorizationDone = TransactionState(4) + TransactionStateCredentialsIssued = TransactionState(5) +) diff --git a/pkg/service/issuecredential/types_test.go b/pkg/service/issuecredential/types_test.go new file mode 100644 index 000000000..2e626098b --- /dev/null +++ b/pkg/service/issuecredential/types_test.go @@ -0,0 +1,23 @@ +package issuecredential_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/trustbloc/vcs/pkg/service/issuecredential" +) + +func TestToDTO(t *testing.T) { + det := issuecredential.AuthorizationDetails{ + CredentialDefinition: &issuecredential.CredentialDefinition{ + Context: []string{"a", "b"}, + Type: []string{"c", "d"}, + }, + } + + resp := det.ToDTO() + + assert.Equal(t, []string{"a", "b"}, *resp.CredentialDefinition.Context) + assert.Equal(t, []string{"c", "d"}, resp.CredentialDefinition.Type) +} diff --git a/pkg/service/oidc4ci/api.go b/pkg/service/oidc4ci/api.go index f33aec502..331316cb2 100644 --- a/pkg/service/oidc4ci/api.go +++ b/pkg/service/oidc4ci/api.go @@ -13,151 +13,26 @@ import ( "time" "github.com/labstack/echo/v4" - "github.com/samber/lo" "github.com/trustbloc/vc-go/verifiable" - "github.com/trustbloc/vcs/pkg/dataprotect" vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/restapi/v1/common" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) -// TxID defines type for transaction ID. -type TxID string - -// Transaction is the credential issuance transaction. Issuer creates a transaction to convey the intention of issuing a -// credential with the given parameters. The transaction is stored in the transaction store and its status is updated as -// the credential issuance progresses. -type Transaction struct { - ID TxID - TransactionData -} - -type TransactionState int16 - type InitiateIssuanceResponseContentType = string -const ( - TransactionStateUnknown = TransactionState(0) - TransactionStateIssuanceInitiated = TransactionState(1) - TransactionStatePreAuthCodeValidated = TransactionState(2) // pre-auth only - TransactionStateAwaitingIssuerOIDCAuthorization = TransactionState(3) // auth only - TransactionStateIssuerOIDCAuthorizationDone = TransactionState(4) - TransactionStateCredentialsIssued = TransactionState(5) -) - const ( ContentTypeApplicationJSON InitiateIssuanceResponseContentType = echo.MIMEApplicationJSONCharsetUTF8 ContentTypeApplicationJWT InitiateIssuanceResponseContentType = "application/jwt" issuerIdentifierParts = 2 ) -type ClaimDataType int16 - -const ( - ClaimDataTypeClaims = ClaimDataType(0) - ClaimDataTypeVC = ClaimDataType(1) -) - -// ClaimData represents user claims in pre-auth code flow. -type ClaimData struct { - EncryptedData *dataprotect.EncryptedData `json:"encrypted_data"` -} - type ClaimDataStore claimDataStore type TransactionStore transactionStore -// TransactionData is the transaction data stored in the underlying storage. -type TransactionData struct { - ProfileID profileapi.ID - ProfileVersion profileapi.Version - IsPreAuthFlow bool - PreAuthCode string - OrgID string - AuthorizationEndpoint string - PushedAuthorizationRequestEndpoint string - TokenEndpoint string - OpState string - RedirectURI string - GrantType string - ResponseType string - Scope []string - IssuerAuthCode string - IssuerToken string - State TransactionState - WebHookURL string - UserPin string - DID string - WalletInitiatedIssuance bool - CredentialConfiguration []*TxCredentialConfiguration -} - -type TxCredentialConfiguration struct { - ID string - CredentialTemplate *profileapi.CredentialTemplate - OIDCCredentialFormat vcsverifiable.OIDCFormat - ClaimEndpoint string - ClaimDataID string - ClaimDataType ClaimDataType - CredentialName string - CredentialDescription string - CredentialExpiresAt *time.Time - PreAuthCodeExpiresAt *time.Time - CredentialConfigurationID string - // AuthorizationDetails may be defined on Authorization Request via using "authorization_details" parameter. - // If "scope" param is used, this field will stay empty. - AuthorizationDetails *AuthorizationDetails - CredentialComposeConfiguration *CredentialComposeConfiguration -} - -type CredentialComposeConfiguration struct { - IDTemplate string `json:"id_template"` - OverrideIssuer bool `json:"override_issuer"` - OverrideSubjectDID bool `json:"override_subject_did"` -} - -// AuthorizationDetails represents the domain model for Authorization Details request. -// This object is used to convey the details about the Credentials the Wallet wants to obtain. -// -// Spec: https://openid.github.io/OpenID4VCI/openid-4-verifiable-credential-issuance-wg-draft.html#section-5.1.1 -type AuthorizationDetails struct { - Type string - Format vcsverifiable.OIDCFormat - Locations []string - CredentialConfigurationID string - CredentialDefinition *CredentialDefinition - CredentialIdentifiers []string -} - -func (ad *AuthorizationDetails) ToDTO() common.AuthorizationDetails { - var credentialDefinition *common.CredentialDefinition - if cd := ad.CredentialDefinition; cd != nil { - credentialDefinition = &common.CredentialDefinition{ - Context: &cd.Context, - CredentialSubject: &cd.CredentialSubject, - Type: cd.Type, - } - } - - return common.AuthorizationDetails{ - CredentialConfigurationId: &ad.CredentialConfigurationID, - CredentialDefinition: credentialDefinition, - CredentialIdentifiers: lo.ToPtr(ad.CredentialIdentifiers), - Format: lo.ToPtr(string(ad.Format)), - Locations: &ad.Locations, - Type: ad.Type, - } -} - -// CredentialDefinition contains the detailed description of the credential type. -type CredentialDefinition struct { - // For ldp_vc only. Array as defined in https://www.w3.org/TR/vc-data-model/#contexts. - Context []string - CredentialSubject map[string]interface{} - Type []string -} - // IssuerIDPOIDCConfiguration represents an Issuer's IDP OIDC configuration // from well-know endpoint (usually: /.well-known/openid-configuration). type IssuerIDPOIDCConfiguration struct { @@ -205,9 +80,9 @@ type InitiateIssuanceComposeCredential struct { // InitiateIssuanceResponse is the response from the Issuer to the Wallet with initiate issuance URL. type InitiateIssuanceResponse struct { InitiateIssuanceURL string - TxID TxID + TxID issuecredential.TxID UserPin string - Tx *Transaction `json:"-"` + Tx *issuecredential.Transaction `json:"-"` ContentType InitiateIssuanceResponseContentType `json:"-"` } @@ -216,14 +91,14 @@ type PrepareClaimDataAuthorizationRequest struct { ResponseType string Scope []string OpState string - AuthorizationDetails []*AuthorizationDetails + AuthorizationDetails []*issuecredential.AuthorizationDetails } type PrepareClaimDataAuthorizationResponse struct { WalletInitiatedFlow *common.WalletInitiatedFlowData ProfileID profileapi.ID ProfileVersion profileapi.Version - TxID TxID + TxID issuecredential.TxID ResponseType string Scope []string AuthorizationEndpoint string @@ -231,7 +106,7 @@ type PrepareClaimDataAuthorizationResponse struct { } type PrepareCredential struct { - TxID TxID + TxID issuecredential.TxID CredentialRequests []*PrepareCredentialRequest } @@ -324,7 +199,7 @@ type ServiceInterface interface { req *InitiateIssuanceRequest, profile *profileapi.Issuer, ) (*InitiateIssuanceResponse, error) - PushAuthorizationDetails(ctx context.Context, opState string, ad []*AuthorizationDetails) error + PushAuthorizationDetails(ctx context.Context, opState string, ad []*issuecredential.AuthorizationDetails) error PrepareClaimDataAuthorizationRequest( ctx context.Context, req *PrepareClaimDataAuthorizationRequest, @@ -334,7 +209,7 @@ type ServiceInterface interface { opState string, code string, flowData *common.WalletInitiatedFlowData, - ) (TxID, error) + ) (issuecredential.TxID, error) ExchangeAuthorizationCode( ctx context.Context, opState, @@ -349,7 +224,7 @@ type ServiceInterface interface { clientID, clientAssertionType, clientAssertion string, - ) (*Transaction, error) + ) (*issuecredential.Transaction, error) PrepareCredential(ctx context.Context, req *PrepareCredential) (*PrepareCredentialResult, error) } @@ -371,10 +246,10 @@ type AckRemote struct { } type ExchangeAuthorizationCodeResult struct { - TxID TxID + TxID issuecredential.TxID // AuthorizationDetails REQUIRED when authorization_details parameter is used to request issuance // of a certain Credential type in Authorization Request. It MUST NOT be used otherwise. - AuthorizationDetails []*AuthorizationDetails + AuthorizationDetails []*issuecredential.AuthorizationDetails } var ErrDataNotFound = errors.New("data not found") diff --git a/pkg/service/oidc4ci/claims.go b/pkg/service/oidc4ci/claims.go index a6f335ce4..fc6519e63 100644 --- a/pkg/service/oidc4ci/claims.go +++ b/pkg/service/oidc4ci/claims.go @@ -8,38 +8,15 @@ package oidc4ci import ( "context" - "encoding/json" - "fmt" - "github.com/trustbloc/vcs/pkg/restapi/resterr" + "github.com/trustbloc/vcs/internal/claims" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) -func (s *Service) EncryptClaims(ctx context.Context, data map[string]interface{}) (*ClaimData, error) { - bytesData, err := json.Marshal(data) - if err != nil { - return nil, err - } - - encrypted, err := s.dataProtector.Encrypt(ctx, bytesData) - if err != nil { - return nil, resterr.NewSystemError(resterr.DataProtectorComponent, "Encrypt", err) - } - - return &ClaimData{ - EncryptedData: encrypted, - }, nil +func (s *Service) EncryptClaims(ctx context.Context, data map[string]interface{}) (*issuecredential.ClaimData, error) { + return claims.EncryptClaims(ctx, data, s.dataProtector) } -func (s *Service) DecryptClaims(ctx context.Context, data *ClaimData) (map[string]interface{}, error) { - resp, err := s.dataProtector.Decrypt(ctx, data.EncryptedData) - if err != nil { - return nil, resterr.NewSystemError(resterr.DataProtectorComponent, "Decrypt", err) - } - - finalMap := map[string]interface{}{} - if err = json.Unmarshal(resp, &finalMap); err != nil { - return nil, fmt.Errorf("unmarshal: %w", err) - } - - return finalMap, nil +func (s *Service) DecryptClaims(ctx context.Context, data *issuecredential.ClaimData) (map[string]interface{}, error) { + return claims.DecryptClaims(ctx, data, s.dataProtector) } diff --git a/pkg/service/oidc4ci/claims_test.go b/pkg/service/oidc4ci/claims_test.go index 4eff5b5d0..10370deb4 100644 --- a/pkg/service/oidc4ci/claims_test.go +++ b/pkg/service/oidc4ci/claims_test.go @@ -16,6 +16,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/trustbloc/vcs/pkg/dataprotect" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/service/oidc4ci" ) @@ -96,7 +97,7 @@ func TestDecrypt(t *testing.T) { DataProtector: crypto, }) - data, err := srv.DecryptClaims(context.TODO(), &oidc4ci.ClaimData{ + data, err := srv.DecryptClaims(context.TODO(), &issuecredential.ClaimData{ EncryptedData: chunks, }) assert.NoError(t, err) @@ -116,7 +117,7 @@ func TestDecrypt(t *testing.T) { DataProtector: crypto, }) - data, err := srv.DecryptClaims(context.TODO(), &oidc4ci.ClaimData{ + data, err := srv.DecryptClaims(context.TODO(), &issuecredential.ClaimData{ EncryptedData: chunks, }) assert.ErrorContains(t, err, "looking for beginning of value") @@ -137,7 +138,7 @@ func TestDecrypt(t *testing.T) { DataProtector: crypto, }) - data, err := srv.DecryptClaims(context.TODO(), &oidc4ci.ClaimData{ + data, err := srv.DecryptClaims(context.TODO(), &issuecredential.ClaimData{ EncryptedData: chunks, }) assert.ErrorContains(t, err, "can not decrypt") diff --git a/pkg/service/oidc4ci/interfaces.go b/pkg/service/oidc4ci/interfaces.go new file mode 100644 index 000000000..480fe7c99 --- /dev/null +++ b/pkg/service/oidc4ci/interfaces.go @@ -0,0 +1,26 @@ +package oidc4ci + +import ( + "context" + + "github.com/trustbloc/vc-go/verifiable" + + "github.com/trustbloc/vcs/pkg/service/issuecredential" +) + +//go:generate mockgen -destination interfaces_mocks_test.go -package oidc4ci_test -source=interfaces.go + +type credentialIssuer interface { + PrepareCredential( + ctx context.Context, + req *issuecredential.PrepareCredentialsRequest, + ) (*verifiable.Credential, error) +} + +type composer interface { // nolint:unused + Compose( + ctx context.Context, + cred *verifiable.Credential, + req *issuecredential.PrepareCredentialsRequest, + ) (*verifiable.Credential, error) +} diff --git a/pkg/service/oidc4ci/oidc4ci_acknowledgement.go b/pkg/service/oidc4ci/oidc4ci_acknowledgement.go index b76989005..30f81baf7 100644 --- a/pkg/service/oidc4ci/oidc4ci_acknowledgement.go +++ b/pkg/service/oidc4ci/oidc4ci_acknowledgement.go @@ -15,6 +15,7 @@ import ( "github.com/google/uuid" "github.com/trustbloc/vcs/pkg/event/spi" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) type AckService struct { @@ -148,7 +149,7 @@ func (s *AckService) Ack( func (s *AckService) sendEvent( ctx context.Context, eventType spi.EventType, - transactionID TxID, + transactionID issuecredential.TxID, ep *EventPayload, ) error { event, err := createEvent(eventType, transactionID, ep) @@ -170,10 +171,10 @@ func (s *AckService) AckEventMap(status string) spi.EventType { return spi.IssuerOIDCInteractionAckRejected } -func extractTransactionID(ackTxID string) TxID { - return TxID(strings.Split(ackTxID, "_")[0]) +func extractTransactionID(ackTxID string) issuecredential.TxID { + return issuecredential.TxID(strings.Split(ackTxID, "_")[0]) } -func generateAckTxID(transactionID TxID) string { +func generateAckTxID(transactionID issuecredential.TxID) string { return fmt.Sprintf("%s_%s", transactionID, strings.Split(uuid.NewString(), "-")[0]) } diff --git a/pkg/service/oidc4ci/oidc4ci_service.go b/pkg/service/oidc4ci/oidc4ci_service.go index b744812f1..4b592e27c 100644 --- a/pkg/service/oidc4ci/oidc4ci_service.go +++ b/pkg/service/oidc4ci/oidc4ci_service.go @@ -21,7 +21,6 @@ import ( "github.com/google/uuid" "github.com/piprate/json-gold/ld" "github.com/samber/lo" - util "github.com/trustbloc/did-go/doc/util/time" "github.com/trustbloc/logutil-go/pkg/log" "github.com/trustbloc/vc-go/verifiable" @@ -33,13 +32,13 @@ import ( profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/restapi/resterr" "github.com/trustbloc/vcs/pkg/restapi/v1/common" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/service/trustregistry" ) const ( defaultGrantType = "authorization_code" defaultResponseType = "token" - defaultCtx = "https://www.w3.org/2018/credentials/v1" attestJWTClientAuthType = "attest_jwt_client_auth" ) @@ -53,31 +52,37 @@ type pinGenerator interface { } type transactionStore interface { + ForceCreate( + ctx context.Context, + profileTransactionDataTTL int32, + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) + Create( ctx context.Context, profileTransactionDataTTL int32, - data *TransactionData, - ) (*Transaction, error) + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) Get( ctx context.Context, - txID TxID, - ) (*Transaction, error) + txID issuecredential.TxID, + ) (*issuecredential.Transaction, error) FindByOpState( ctx context.Context, opState string, - ) (*Transaction, error) + ) (*issuecredential.Transaction, error) Update( ctx context.Context, - tx *Transaction, + tx *issuecredential.Transaction, ) error } type claimDataStore interface { - Create(ctx context.Context, profileTTLSec int32, data *ClaimData) (string, error) - GetAndDelete(ctx context.Context, id string) (*ClaimData, error) + Create(ctx context.Context, profileTTLSec int32, data *issuecredential.ClaimData) (string, error) + GetAndDelete(ctx context.Context, id string) (*issuecredential.ClaimData, error) } type wellKnownService interface { @@ -144,16 +149,6 @@ type ackService interface { ) (*string, error) } -type composer interface { - Compose( - ctx context.Context, - credential *verifiable.Credential, - tx *Transaction, - txCredentialConfiguration *TxCredentialConfiguration, - req *PrepareCredentialRequest, - ) (*verifiable.Credential, error) -} - // DocumentLoader knows how to load remote documents. type documentLoader interface { LoadDocument(u string) (*ld.RemoteDocument, error) @@ -178,8 +173,8 @@ type Config struct { JSONSchemaValidator jsonSchemaValidator TrustRegistry trustRegistry AckService ackService - Composer composer DocumentLoader documentLoader + PrepareCredential credentialIssuer } // Service implements VCS credential interaction API for OIDC credential issuance. @@ -201,8 +196,8 @@ type Service struct { schemaValidator jsonSchemaValidator trustRegistry trustRegistry ackService ackService - composer composer documentLoader documentLoader + credentialIssuer credentialIssuer } // NewService returns a new Service instance. @@ -225,15 +220,15 @@ func NewService(config *Config) (*Service, error) { schemaValidator: config.JSONSchemaValidator, trustRegistry: config.TrustRegistry, ackService: config.AckService, - composer: config.Composer, documentLoader: config.DocumentLoader, + credentialIssuer: config.PrepareCredential, }, nil } func (s *Service) PushAuthorizationDetails( ctx context.Context, opState string, - ad []*AuthorizationDetails, + ad []*issuecredential.AuthorizationDetails, ) error { tx, err := s.store.FindByOpState(ctx, opState) if err != nil { @@ -258,7 +253,7 @@ func (s *Service) PushAuthorizationDetails( return err } - var validTxCredentialConfiguration []*TxCredentialConfiguration + var validTxCredentialConfiguration []*issuecredential.TxCredentialConfiguration // Delete unused entities from tx.CredentialConfiguration for _, txCredentialConfiguration := range tx.CredentialConfiguration { if _, ok := requestedTxCredentialConfigurationsIDs[txCredentialConfiguration.ID]; ok { @@ -284,7 +279,7 @@ func (s *Service) checkScopes( profile *profileapi.Issuer, reqScopes []string, txScope []string, - txCredentialConfigurations []*TxCredentialConfiguration, + txCredentialConfigurations []*issuecredential.TxCredentialConfiguration, requestedTxCredentialConfigurationIDsViaAuthDetails map[string]struct{}, ) ([]string, error) { var credentialsConfigurationSupported map[string]*profileapi.CredentialsConfigurationSupported @@ -377,7 +372,7 @@ func (s *Service) PrepareClaimDataAuthorizationRequest( return nil, err } - newState := TransactionStateAwaitingIssuerOIDCAuthorization + newState := issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization if err = s.validateStateTransition(tx.State, newState); err != nil { s.sendFailedTransactionEvent(ctx, tx, err) return nil, err @@ -424,7 +419,7 @@ func (s *Service) PrepareClaimDataAuthorizationRequest( tx.Scope = validScopes - var validTxCredentialConfiguration []*TxCredentialConfiguration + var validTxCredentialConfiguration []*issuecredential.TxCredentialConfiguration // Delete unused entities from tx.CredentialConfiguration for _, txCredentialConfiguration := range tx.CredentialConfiguration { if _, ok := requestedTxCredentialConfigurationIDs[txCredentialConfiguration.ID]; ok { @@ -524,8 +519,8 @@ func (s *Service) prepareClaimDataAuthorizationRequestWalletInitiated( //nolint:gocognit,nolintlint func (s *Service) enrichTxCredentialConfigurationsWithAuthorizationDetails( profile *profileapi.Issuer, - txCredentialConfigurations []*TxCredentialConfiguration, - authorizationDetails []*AuthorizationDetails, + txCredentialConfigurations []*issuecredential.TxCredentialConfiguration, + authorizationDetails []*issuecredential.AuthorizationDetails, ) (map[string]struct{}, error) { requestedTxCredentialConfigurationIDs := make(map[string]struct{}) @@ -614,7 +609,7 @@ func (s *Service) ValidatePreAuthorizedCodeRequest( //nolint:gocognit,nolintlint clientID, clientAssertionType, clientAssertion string, -) (*Transaction, error) { +) (*issuecredential.Transaction, error) { tx, err := s.store.FindByOpState(ctx, preAuthorizedCode) if err != nil { return nil, resterr.NewCustomError(resterr.OIDCTxNotFound, fmt.Errorf("find tx by op state: %w", err)) @@ -647,7 +642,7 @@ func (s *Service) ValidatePreAuthorizedCodeRequest( //nolint:gocognit,nolintlint } } - newState := TransactionStatePreAuthCodeValidated + newState := issuecredential.TransactionStatePreAuthCodeValidated if err = s.validateStateTransition(tx.State, newState); err != nil { return nil, err } @@ -685,7 +680,7 @@ func (s *Service) ValidatePreAuthorizedCodeRequest( //nolint:gocognit,nolintlint func (s *Service) checkPolicy( ctx context.Context, profile *profileapi.Issuer, - tx *Transaction, + tx *issuecredential.Transaction, clientAssertionType, clientAssertion string, ) error { @@ -765,7 +760,7 @@ func (s *Service) PrepareCredential( //nolint:funlen return nil, err } - var txCredentialConfiguration *TxCredentialConfiguration + var txCredentialConfiguration *issuecredential.TxCredentialConfiguration txCredentialConfiguration, err = s.findTxCredentialConfiguration( requestedTxCredentialConfigurationIDs, tx.CredentialConfiguration, @@ -802,7 +797,7 @@ func (s *Service) PrepareCredential( //nolint:funlen prepareCredentialResult.Credentials = append(prepareCredentialResult.Credentials, prepareCredentialResultData) } - tx.State = TransactionStateCredentialsIssued + tx.State = issuecredential.TransactionStateCredentialsIssued if err = s.store.Update(ctx, tx); err != nil { e := resterr.NewSystemError(resterr.TransactionStoreComponent, "Update", err) @@ -818,13 +813,54 @@ func (s *Service) PrepareCredential( //nolint:funlen return prepareCredentialResult, nil } +func (s *Service) prepareCredential( //nolint:funlen + ctx context.Context, + tx *issuecredential.Transaction, + txCredentialConfiguration *issuecredential.TxCredentialConfiguration, + prepareCredentialRequest *PrepareCredentialRequest, +) (*verifiable.Credential, *string, error) { + claimData, err := s.getClaimsData(ctx, tx, txCredentialConfiguration) + if err != nil { + return nil, nil, fmt.Errorf("get claims data: %w", err) + } + + finalCred, err := s.credentialIssuer.PrepareCredential(ctx, &issuecredential.PrepareCredentialsRequest{ + TxID: string(tx.ID), + ClaimData: claimData, + IssuerDID: tx.DID, + SubjectDID: prepareCredentialRequest.DID, + CredentialConfiguration: txCredentialConfiguration, + IssuerID: tx.ProfileID, + IssuerVersion: tx.ProfileVersion, + RefreshServiceEnabled: tx.RefreshServiceEnabled, + }) + if err != nil { + return nil, nil, fmt.Errorf("prepare credential: %w", err) + } + + // Create credential-specific record. + ack, err := s.ackService.CreateAck(ctx, &Ack{ + HashedToken: prepareCredentialRequest.HashedToken, + ProfileID: tx.ProfileID, + ProfileVersion: tx.ProfileVersion, + TxID: generateAckTxID(tx.ID), + WebHookURL: tx.WebHookURL, + OrgID: tx.OrgID, + }) + if err != nil { // its not critical and should not break the flow + logger.Errorc(ctx, errors.Join(err, errors.New("can not create ack")).Error()) + } + + return finalCred, ack, nil +} + func (s *Service) findTxCredentialConfiguration( //nolint:funlen requestedTxCredentialConfigurationIDs map[string]struct{}, - txCredentialConfigurations []*TxCredentialConfiguration, + txCredentialConfigurations []*issuecredential.TxCredentialConfiguration, credentialFormat vcsverifiable.OIDCFormat, credentialTypes []string, -) (*TxCredentialConfiguration, error) { - var txCredentialConfiguration *TxCredentialConfiguration +) (*issuecredential.TxCredentialConfiguration, error) { + var txCredentialConfiguration *issuecredential.TxCredentialConfiguration for _, credentialConfiguration := range txCredentialConfigurations { if _, ok := requestedTxCredentialConfigurationIDs[credentialConfiguration.ID]; ok { continue @@ -867,126 +903,10 @@ func (s *Service) validateRequestAudienceClaim( //nolint:funlen return nil } -func (s *Service) prepareCredentialFromClaims( - _ context.Context, - claimData map[string]interface{}, - tx *Transaction, - txCredentialConfiguration *TxCredentialConfiguration, - prepareCredentialRequest *PrepareCredentialRequest, -) (*verifiable.Credential, error) { - contexts := txCredentialConfiguration.CredentialTemplate.Contexts - if len(contexts) == 0 { - contexts = []string{defaultCtx} - } - - // prepare credential for signing - vcc := verifiable.CredentialContents{ - Context: contexts, - ID: uuid.New().URN(), - Types: []string{"VerifiableCredential", txCredentialConfiguration.CredentialTemplate.Type}, - Issuer: &verifiable.Issuer{ID: tx.DID}, - Issued: util.NewTime(time.Now()), - } - - customFields := map[string]interface{}{} - - if txCredentialConfiguration.CredentialDescription != "" { - customFields["description"] = txCredentialConfiguration.CredentialDescription - } - if txCredentialConfiguration.CredentialName != "" { - customFields["name"] = txCredentialConfiguration.CredentialName - } - - if txCredentialConfiguration.CredentialExpiresAt != nil { - vcc.Expired = util.NewTime(*txCredentialConfiguration.CredentialExpiresAt) - } - - if claimData != nil { - vcc.Subject = []verifiable.Subject{{ - ID: prepareCredentialRequest.DID, - CustomFields: claimData, - }} - } else { - vcc.Subject = []verifiable.Subject{{ID: prepareCredentialRequest.DID}} - } - - return verifiable.CreateCredential(vcc, customFields) -} - -func (s *Service) prepareCredentialFromCompose( - ctx context.Context, - claimData map[string]interface{}, - tx *Transaction, - txCredentialConfiguration *TxCredentialConfiguration, - req *PrepareCredentialRequest, -) (*verifiable.Credential, error) { - cred, err := verifiable.ParseCredentialJSON(claimData, - verifiable.WithCredDisableValidation(), - verifiable.WithDisabledProofCheck(), - ) - if err != nil { - return nil, fmt.Errorf("parse credential json: %w", err) - } - - return s.composer.Compose(ctx, cred, tx, txCredentialConfiguration, req) -} - -func (s *Service) prepareCredential( //nolint:funlen - ctx context.Context, - tx *Transaction, - txCredentialConfiguration *TxCredentialConfiguration, - prepareCredentialRequest *PrepareCredentialRequest, -) (*verifiable.Credential, *string, error) { - claimData, err := s.getClaimsData(ctx, tx, txCredentialConfiguration) - if err != nil { - return nil, nil, fmt.Errorf("get claims data: %w", err) - } - - var finalCred *verifiable.Credential - - switch txCredentialConfiguration.ClaimDataType { - case ClaimDataTypeClaims: - finalCred, err = s.prepareCredentialFromClaims( - ctx, - claimData, - tx, - txCredentialConfiguration, - prepareCredentialRequest, - ) - case ClaimDataTypeVC: - finalCred, err = s.prepareCredentialFromCompose( - ctx, - claimData, - tx, - txCredentialConfiguration, - prepareCredentialRequest, - ) - } - - if err != nil { - return nil, nil, fmt.Errorf("prepare credential: %w", err) - } - - // Create cpredential-specific record. - ack, err := s.ackService.CreateAck(ctx, &Ack{ - HashedToken: prepareCredentialRequest.HashedToken, - ProfileID: tx.ProfileID, - ProfileVersion: tx.ProfileVersion, - TxID: generateAckTxID(tx.ID), - WebHookURL: tx.WebHookURL, - OrgID: tx.OrgID, - }) - if err != nil { // its not critical and should not break the flow - logger.Errorc(ctx, errors.Join(err, errors.New("can not create ack")).Error()) - } - - return finalCred, ack, nil -} - func (s *Service) getClaimsData( ctx context.Context, - tx *Transaction, - txCredentialConfiguration *TxCredentialConfiguration, + tx *issuecredential.Transaction, + txCredentialConfiguration *issuecredential.TxCredentialConfiguration, ) (map[string]interface{}, error) { if !tx.IsPreAuthFlow { claims, err := s.requestClaims(ctx, tx, txCredentialConfiguration) @@ -1012,8 +932,8 @@ func (s *Service) getClaimsData( func (s *Service) requestClaims( ctx context.Context, - tx *Transaction, - txCredentialConfiguration *TxCredentialConfiguration, + tx *issuecredential.Transaction, + txCredentialConfiguration *issuecredential.TxCredentialConfiguration, ) (map[string]interface{}, error) { r, err := http.NewRequestWithContext(ctx, http.MethodPost, txCredentialConfiguration.ClaimEndpoint, http.NoBody) if err != nil { @@ -1054,7 +974,7 @@ func (s *Service) requestClaims( func createEvent( eventType spi.EventType, - transactionID TxID, + transactionID issuecredential.TxID, ep *EventPayload, ) (*spi.Event, error) { payload, err := json.Marshal(ep) @@ -1071,7 +991,7 @@ func createEvent( func (s *Service) sendEvent( ctx context.Context, eventType spi.EventType, - transactionID TxID, + transactionID issuecredential.TxID, ep *EventPayload) error { event, err := createEvent(eventType, transactionID, ep) if err != nil { @@ -1083,7 +1003,7 @@ func (s *Service) sendEvent( func (s *Service) sendTransactionEvent( ctx context.Context, - tx *Transaction, + tx *issuecredential.Transaction, eventType spi.EventType, ) error { return s.sendEvent(ctx, eventType, tx.ID, createTxEventPayload(tx)) @@ -1091,7 +1011,7 @@ func (s *Service) sendTransactionEvent( func (s *Service) sendFailedTransactionEvent( ctx context.Context, - tx *Transaction, + tx *issuecredential.Transaction, e error, ) { ep := &EventPayload{ @@ -1108,7 +1028,7 @@ func (s *Service) sendFailedTransactionEvent( } } -func createTxEventPayload(tx *Transaction) *EventPayload { +func createTxEventPayload(tx *issuecredential.Transaction) *EventPayload { var ( credentialTemplateID string credentialFormat vcsverifiable.OIDCFormat @@ -1148,7 +1068,7 @@ func createTxEventPayload(tx *Transaction) *EventPayload { func (s *Service) sendInitiateIssuanceEvent( ctx context.Context, - tx *Transaction, + tx *issuecredential.Transaction, initiateURL string, ) error { payload := createTxEventPayload(tx) @@ -1159,7 +1079,7 @@ func (s *Service) sendInitiateIssuanceEvent( func (s *Service) sendIssuanceAuthRequestPreparedTxEvent( ctx context.Context, - tx *Transaction, + tx *issuecredential.Transaction, ) error { payload := createTxEventPayload(tx) payload.AuthorizationEndpoint = tx.AuthorizationEndpoint diff --git a/pkg/service/oidc4ci/oidc4ci_service_exchange_code.go b/pkg/service/oidc4ci/oidc4ci_service_exchange_code.go index 43349d677..97ca19cfd 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_exchange_code.go +++ b/pkg/service/oidc4ci/oidc4ci_service_exchange_code.go @@ -15,6 +15,7 @@ import ( "github.com/trustbloc/vcs/pkg/event/spi" "github.com/trustbloc/vcs/pkg/restapi/resterr" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) func (s *Service) ExchangeAuthorizationCode( @@ -29,7 +30,7 @@ func (s *Service) ExchangeAuthorizationCode( return nil, fmt.Errorf("get transaction by opstate: %w", err) } - newState := TransactionStateIssuerOIDCAuthorizationDone + newState := issuecredential.TransactionStateIssuerOIDCAuthorizationDone if err = s.validateStateTransition(tx.State, newState); err != nil { s.sendFailedTransactionEvent(ctx, tx, err) return nil, err diff --git a/pkg/service/oidc4ci/oidc4ci_service_exchange_code_test.go b/pkg/service/oidc4ci/oidc4ci_service_exchange_code_test.go index 710955e27..67603de66 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_exchange_code_test.go +++ b/pkg/service/oidc4ci/oidc4ci_service_exchange_code_test.go @@ -20,6 +20,7 @@ import ( "github.com/trustbloc/vcs/pkg/event/spi" "github.com/trustbloc/vcs/pkg/profile" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/service/oidc4ci" ) @@ -60,21 +61,21 @@ func TestExchangeCode(t *testing.T) { }, ) - baseTx := &oidc4ci.Transaction{ - ID: oidc4ci.TxID("id"), - TransactionData: oidc4ci.TransactionData{ + baseTx := &issuecredential.Transaction{ + ID: issuecredential.TxID("id"), + TransactionData: issuecredential.TransactionData{ TokenEndpoint: "https://localhost/token", IssuerAuthCode: authCode, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, }, } store.EXPECT().FindByOpState(gomock.Any(), opState).Return(baseTx, nil) store.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { assert.Equal(t, baseTx, tx) assert.Equal(t, "SlAV32hkKG", tx.IssuerToken) - assert.Equal(t, oidc4ci.TransactionStateIssuerOIDCAuthorizationDone, tx.State) + assert.Equal(t, issuecredential.TransactionStateIssuerOIDCAuthorizationDone, tx.State) return nil }) @@ -116,9 +117,9 @@ func TestExchangeCodeProfileGetError(t *testing.T) { }) assert.NoError(t, err) - store.EXPECT().FindByOpState(gomock.Any(), gomock.Any()).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + store.EXPECT().FindByOpState(gomock.Any(), gomock.Any()).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, TokenEndpoint: "https://localhost/token", }, }, nil) @@ -153,9 +154,9 @@ func TestExchangeCodeCheckPolicyError(t *testing.T) { }) assert.NoError(t, err) - store.EXPECT().FindByOpState(gomock.Any(), gomock.Any()).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + store.EXPECT().FindByOpState(gomock.Any(), gomock.Any()).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, TokenEndpoint: "https://localhost/token", }, }, nil) @@ -209,9 +210,9 @@ func TestExchangeCodeIssuerError(t *testing.T) { }) assert.NoError(t, err) - store.EXPECT().FindByOpState(gomock.Any(), gomock.Any()).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + store.EXPECT().FindByOpState(gomock.Any(), gomock.Any()).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, TokenEndpoint: "https://localhost/token", }, }, nil) @@ -265,10 +266,10 @@ func TestExchangeCodeStoreUpdateErr(t *testing.T) { opState := uuid.NewString() authCode := uuid.NewString() - baseTx := &oidc4ci.Transaction{ - ID: oidc4ci.TxID("id"), - TransactionData: oidc4ci.TransactionData{ - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + baseTx := &issuecredential.Transaction{ + ID: issuecredential.TxID("id"), + TransactionData: issuecredential.TransactionData{ + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, TokenEndpoint: "https://localhost/token", IssuerAuthCode: authCode, }, @@ -309,9 +310,9 @@ func TestExchangeCodeInvalidState(t *testing.T) { }) assert.NoError(t, err) - store.EXPECT().FindByOpState(gomock.Any(), gomock.Any()).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ - State: oidc4ci.TransactionStateCredentialsIssued, + store.EXPECT().FindByOpState(gomock.Any(), gomock.Any()).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ + State: issuecredential.TransactionStateCredentialsIssued, TokenEndpoint: "https://localhost/token", }, }, nil) @@ -365,21 +366,21 @@ func TestExchangeCodePublishError(t *testing.T) { return errors.New("publish error") }) - baseTx := &oidc4ci.Transaction{ - ID: oidc4ci.TxID("id"), - TransactionData: oidc4ci.TransactionData{ + baseTx := &issuecredential.Transaction{ + ID: issuecredential.TxID("id"), + TransactionData: issuecredential.TransactionData{ TokenEndpoint: "https://localhost/token", IssuerAuthCode: authCode, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, }, } store.EXPECT().FindByOpState(gomock.Any(), opState).Return(baseTx, nil) store.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { assert.Equal(t, baseTx, tx) assert.Equal(t, "SlAV32hkKG", tx.IssuerToken) - assert.Equal(t, oidc4ci.TransactionStateIssuerOIDCAuthorizationDone, tx.State) + assert.Equal(t, issuecredential.TransactionStateIssuerOIDCAuthorizationDone, tx.State) return nil }) @@ -433,11 +434,11 @@ func TestExchangeCode_Success(t *testing.T) { return nil }) - authorizationDetails := &oidc4ci.AuthorizationDetails{ + authorizationDetails := &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "", Locations: []string{"https://example.com/rs1", "https://example.com/rs2"}, Type: "openid_credential", - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Context: []string{"https://example.com/context/1", "https://example.com/context/2"}, CredentialSubject: map[string]interface{}{ "key": "value", @@ -447,13 +448,13 @@ func TestExchangeCode_Success(t *testing.T) { Format: "", } - baseTx := &oidc4ci.Transaction{ - ID: oidc4ci.TxID("id"), - TransactionData: oidc4ci.TransactionData{ + baseTx := &issuecredential.Transaction{ + ID: issuecredential.TxID("id"), + TransactionData: issuecredential.TransactionData{ TokenEndpoint: "https://localhost/token", IssuerAuthCode: authCode, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { CredentialConfigurationID: "ConfigurationID", AuthorizationDetails: authorizationDetails, @@ -464,10 +465,10 @@ func TestExchangeCode_Success(t *testing.T) { store.EXPECT().FindByOpState(gomock.Any(), opState).Return(baseTx, nil) store.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { assert.Equal(t, baseTx, tx) assert.Equal(t, "SlAV32hkKG", tx.IssuerToken) - assert.Equal(t, oidc4ci.TransactionStateIssuerOIDCAuthorizationDone, tx.State) + assert.Equal(t, issuecredential.TransactionStateIssuerOIDCAuthorizationDone, tx.State) return nil }) @@ -484,7 +485,7 @@ func TestExchangeCode_Success(t *testing.T) { assert.NoError(t, err) assert.Equal(t, &oidc4ci.ExchangeAuthorizationCodeResult{ TxID: "id", - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{authorizationDetails}, + AuthorizationDetails: []*issuecredential.AuthorizationDetails{authorizationDetails}, }, resp) } diff --git a/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go b/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go index 08b6ee574..5b09680fd 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go +++ b/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance.go @@ -26,6 +26,7 @@ import ( "github.com/trustbloc/vcs/pkg/doc/verifiable" profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/restapi/resterr" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) const ( @@ -64,7 +65,11 @@ func (s *Service) InitiateIssuance( // nolint:funlen,gocyclo,gocognit return nil, resterr.ErrAuthorizedCodeFlowNotSupported } - issuedCredentialConfiguration := make([]*TxCredentialConfiguration, 0, len(req.CredentialConfiguration)) + issuedCredentialConfiguration := make( + []*issuecredential.TxCredentialConfiguration, + 0, + len(req.CredentialConfiguration), + ) for _, credentialConfiguration := range req.CredentialConfiguration { txCredentialConf, err := s.newTxCredentialConf( @@ -76,9 +81,9 @@ func (s *Service) InitiateIssuance( // nolint:funlen,gocyclo,gocognit issuedCredentialConfiguration = append(issuedCredentialConfiguration, txCredentialConf) } - txState := TransactionStateIssuanceInitiated + txState := issuecredential.TransactionStateIssuanceInitiated if req.WalletInitiatedIssuance { - txState = TransactionStateAwaitingIssuerOIDCAuthorization + txState = issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization } opState := req.OpState @@ -89,13 +94,14 @@ func (s *Service) InitiateIssuance( // nolint:funlen,gocyclo,gocognit opState = preAuthCode // set opState as it will be empty for pre-auth } - txData := &TransactionData{ + txData := &issuecredential.TransactionData{ ProfileID: profile.ID, ProfileVersion: profile.Version, OrgID: profile.OrganizationID, ResponseType: req.ResponseType, State: txState, WebHookURL: profile.WebHook, + RefreshServiceEnabled: profile.VCConfig.RefreshServiceEnabled, DID: profile.SigningDID.DID, WalletInitiatedIssuance: req.WalletInitiatedIssuance, IsPreAuthFlow: isPreAuthFlow, @@ -173,7 +179,7 @@ func (s *Service) newTxCredentialConf( credentialConfiguration InitiateIssuanceCredentialConfiguration, isPreAuthFlow bool, profile *profileapi.Issuer, -) (*TxCredentialConfiguration, error) { +) (*issuecredential.TxCredentialConfiguration, error) { err := s.validateFlowSpecificRequestParams( isPreAuthFlow, credentialConfiguration, @@ -212,7 +218,7 @@ func (s *Service) newTxCredentialConf( metaCredentialConfiguration := profileMeta.CredentialsConfigurationSupported[credentialConfigurationID] - txCredentialConfiguration := &TxCredentialConfiguration{ + txCredentialConfiguration := &issuecredential.TxCredentialConfiguration{ ID: uuid.NewString(), CredentialTemplate: targetCredentialTemplate, OIDCCredentialFormat: metaCredentialConfiguration.Format, @@ -301,7 +307,7 @@ func (s *Service) applyPreAuthFlowModifications( profileClaimDataTTLSec int32, req InitiateIssuanceCredentialConfiguration, credentialTemplate *profileapi.CredentialTemplate, - txCredentialConfiguration *TxCredentialConfiguration, + txCredentialConfiguration *issuecredential.TxCredentialConfiguration, ) error { var targetClaims map[string]interface{} if req.ClaimData != nil { @@ -320,13 +326,13 @@ func (s *Service) applyPreAuthFlowModifications( } targetClaims = req.ClaimData - txCredentialConfiguration.ClaimDataType = ClaimDataTypeClaims + txCredentialConfiguration.ClaimDataType = issuecredential.ClaimDataTypeClaims } else if req.ComposeCredential != nil { targetClaims = lo.FromPtr(req.ComposeCredential.Credential) - txCredentialConfiguration.ClaimDataType = ClaimDataTypeVC + txCredentialConfiguration.ClaimDataType = issuecredential.ClaimDataTypeVC - txCredentialConfiguration.CredentialComposeConfiguration = &CredentialComposeConfiguration{ + txCredentialConfiguration.CredentialComposeConfiguration = &issuecredential.CredentialComposeConfiguration{ IDTemplate: req.ComposeCredential.IDTemplate, OverrideIssuer: req.ComposeCredential.OverrideIssuer, OverrideSubjectDID: req.ComposeCredential.OverrideSubjectDID, @@ -352,7 +358,7 @@ func (s *Service) applyPreAuthFlowModifications( return nil } -func setScopes(data *TransactionData, scopesSupported []string, requestScopes []string) error { +func setScopes(data *issuecredential.TransactionData, scopesSupported []string, requestScopes []string) error { if len(requestScopes) == 0 { data.Scope = scopesSupported return nil @@ -370,7 +376,7 @@ func setScopes(data *TransactionData, scopesSupported []string, requestScopes [] return nil } -func setGrantType(data *TransactionData, grantTypesSupported []string, requestGrantType string) error { +func setGrantType(data *issuecredential.TransactionData, grantTypesSupported []string, requestGrantType string) error { if !lo.Contains(grantTypesSupported, requestGrantType) { return resterr.NewValidationError(resterr.InvalidValue, "grant-type", fmt.Errorf("unsupported grant type %s", requestGrantType)) @@ -417,7 +423,7 @@ func (s *Service) GetCredentialsExpirationTime( func (s *Service) extendTransactionWithOIDCConfig( ctx context.Context, profile *profileapi.Issuer, - data *TransactionData, + data *issuecredential.TransactionData, ) error { if profile.OIDCConfig == nil || profile.OIDCConfig.IssuerWellKnownURL == "" { return nil @@ -497,12 +503,12 @@ func findCredentialConfigurationID( func (s *Service) prepareCredentialOffer( req *InitiateIssuanceRequest, - tx *Transaction, + tx *issuecredential.Transaction, ) *CredentialOfferResponse { issuerURL, _ := url.JoinPath(s.issuerVCSPublicHost, "oidc/idp", tx.ProfileID, tx.ProfileVersion) credentialConfigurationIDs := lo.Map(tx.CredentialConfiguration, - func(item *TxCredentialConfiguration, index int) string { + func(item *issuecredential.TxCredentialConfiguration, index int) string { return item.CredentialConfigurationID }) @@ -612,7 +618,7 @@ func (s *Service) getSignedCredentialOfferJWT( func (s *Service) buildInitiateIssuanceURL( ctx context.Context, req *InitiateIssuanceRequest, - tx *Transaction, + tx *issuecredential.Transaction, profile *profileapi.Issuer, ) (string, InitiateIssuanceResponseContentType, error) { credentialOffer := s.prepareCredentialOffer(req, tx) diff --git a/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance_test.go b/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance_test.go index b30f38270..e62de09f2 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance_test.go +++ b/pkg/service/oidc4ci/oidc4ci_service_initiate_issuance_test.go @@ -29,6 +29,7 @@ import ( vcskms "github.com/trustbloc/vcs/pkg/kms" profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/restapi/resterr" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/service/oidc4ci" ) @@ -49,12 +50,12 @@ type mocks struct { wellKnownService *MockWellKnownService claimDataStore *MockClaimDataStore eventService *MockEventService - composer *MockComposer pinGenerator *MockPinGenerator crypto *MockDataProtector jsonSchemaValidator *MockJSONSchemaValidator ackService *MockAckService documentLoader *jsonld.DefaultDocumentLoader + composer *Mockcomposer } func TestService_InitiateIssuance(t *testing.T) { @@ -90,9 +91,9 @@ func TestService_InitiateIssuance(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { - assert.Equal(t, oidc4ci.TransactionStateIssuanceInitiated, data.State) + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { + assert.Equal(t, issuecredential.TransactionStateIssuanceInitiated, data.State) assert.Equal(t, "test_issuer", data.ProfileID) assert.Equal(t, "1.1", data.ProfileVersion) assert.Equal(t, false, data.IsPreAuthFlow) @@ -109,7 +110,7 @@ func TestService_InitiateIssuance(t *testing.T) { assert.Equal(t, []string{"openid", "profile"}, data.Scope) assert.Empty(t, data.IssuerAuthCode) assert.Empty(t, data.IssuerToken) - assert.Equal(t, oidc4ci.TransactionStateIssuanceInitiated, data.State) + assert.Equal(t, issuecredential.TransactionStateIssuanceInitiated, data.State) assert.Empty(t, data.WebHookURL) assert.Equal(t, "123456789", data.UserPin) assert.Equal(t, "did:123", data.DID) @@ -141,10 +142,10 @@ func TestService_InitiateIssuance(t *testing.T) { assert.Empty(t, prcCredConf.PreAuthCodeExpiresAt) assert.Empty(t, prcCredConf.AuthorizationDetails) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -257,9 +258,9 @@ func TestService_InitiateIssuance(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { - assert.Equal(t, oidc4ci.TransactionStateIssuanceInitiated, data.State) + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { + assert.Equal(t, issuecredential.TransactionStateIssuanceInitiated, data.State) assert.Equal(t, "test_issuer", data.ProfileID) assert.Equal(t, "1.1", data.ProfileVersion) assert.Equal(t, true, data.IsPreAuthFlow) @@ -276,7 +277,7 @@ func TestService_InitiateIssuance(t *testing.T) { assert.Equal(t, []string{"openid", "profile"}, data.Scope) assert.Empty(t, data.IssuerAuthCode) assert.Empty(t, data.IssuerToken) - assert.Equal(t, oidc4ci.TransactionStateIssuanceInitiated, data.State) + assert.Equal(t, issuecredential.TransactionStateIssuanceInitiated, data.State) assert.Empty(t, data.WebHookURL) assert.Equal(t, "123456789", data.UserPin) assert.Equal(t, "did:123", data.DID) @@ -320,10 +321,10 @@ func TestService_InitiateIssuance(t *testing.T) { assert.NotEmpty(t, prcCredConf.PreAuthCodeExpiresAt) assert.Empty(t, prcCredConf.AuthorizationDetails) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -362,7 +363,7 @@ func TestService_InitiateIssuance(t *testing.T) { Return(chunks, nil) mocks.claimDataStore.EXPECT().Create(gomock.Any(), int32(0), gomock.Any()).Times(3).DoAndReturn( - func(ctx context.Context, profileTTL int32, data *oidc4ci.ClaimData) (string, error) { + func(ctx context.Context, profileTTL int32, data *issuecredential.ClaimData) (string, error) { assert.Equal(t, chunks, data.EncryptedData) return "claimDataID", nil @@ -467,15 +468,15 @@ func TestService_InitiateIssuance(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, data.State) + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, data.State) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ State: data.State, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -523,7 +524,7 @@ func TestService_InitiateIssuance(t *testing.T) { check: func(t *testing.T, resp *oidc4ci.InitiateIssuanceResponse, err error) { require.NoError(t, err) assert.NotNil(t, resp.Tx) - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, resp.Tx.State) + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, resp.Tx.State) require.Equal(t, "https://wallet.example.com/initiate_issuance?credential_offer=%7B%22credential_issuer%22%3A%22https%3A%2F%2Fvcs.pb.example.com%2Foidc%2Fidp%22%2C%22credential_configuration_ids%22%3A%5B%22PermanentResidentCard%22%5D%2C%22grants%22%3A%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%22eyJhbGciOiJSU0Et%22%7D%7D%7D", resp.InitiateIssuanceURL) //nolint }, }, @@ -553,26 +554,26 @@ func TestService_InitiateIssuance(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { assert.NotEqual(t, data.OpState, initialOpState) assert.Equal(t, data.OpState, data.PreAuthCode) assert.True(t, len(data.UserPin) > 0) assert.Equal(t, true, data.IsPreAuthFlow) assert.NotEmpty(t, data.CredentialConfiguration[0].ClaimDataID) assert.Equal(t, "PermanentResidentCardIdentifier", data.CredentialConfiguration[0].CredentialConfigurationID) - assert.Equal(t, oidc4ci.TransactionStateIssuanceInitiated, data.State) + assert.Equal(t, issuecredential.TransactionStateIssuanceInitiated, data.State) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: profile.ID, PreAuthCode: expectedCode, IsPreAuthFlow: true, UserPin: "123456789", GrantType: "authorization_code", Scope: []string{"openid", "profile"}, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -586,7 +587,7 @@ func TestService_InitiateIssuance(t *testing.T) { }) mocks.claimDataStore.EXPECT().Create(gomock.Any(), int32(0), gomock.Any()).DoAndReturn( - func(ctx context.Context, profileTTL int32, data *oidc4ci.ClaimData) (string, error) { + func(ctx context.Context, profileTTL int32, data *issuecredential.ClaimData) (string, error) { assert.Equal(t, chunks, data.EncryptedData) return "claimDataID", nil @@ -645,8 +646,8 @@ func TestService_InitiateIssuance(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { assert.NotEqual(t, data.OpState, initialOpState) assert.Equal(t, data.OpState, data.PreAuthCode) assert.Empty(t, data.UserPin) @@ -654,13 +655,13 @@ func TestService_InitiateIssuance(t *testing.T) { assert.NotEmpty(t, data.CredentialConfiguration[0].ClaimDataID) assert.Equal(t, "PermanentResidentCardIdentifier", data.CredentialConfiguration[0].CredentialConfigurationID) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: profile.ID, PreAuthCode: expectedCode, IsPreAuthFlow: true, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -740,8 +741,8 @@ func TestService_InitiateIssuance(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { assert.NotEqual(t, data.OpState, initialOpState) assert.Equal(t, data.OpState, data.PreAuthCode) assert.Empty(t, data.UserPin) @@ -750,20 +751,20 @@ func TestService_InitiateIssuance(t *testing.T) { configuration := data.CredentialConfiguration[0] assert.NotEmpty(t, configuration.ClaimDataID) - assert.EqualValues(t, oidc4ci.ClaimDataTypeVC, configuration.ClaimDataType) + assert.EqualValues(t, issuecredential.ClaimDataTypeVC, configuration.ClaimDataType) assert.EqualValues(t, "some-template", configuration.CredentialComposeConfiguration.IDTemplate) assert.True(t, configuration.CredentialComposeConfiguration.OverrideIssuer) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: profile.ID, PreAuthCode: expectedCode, IsPreAuthFlow: true, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -863,8 +864,8 @@ func TestService_InitiateIssuance(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { assert.NotEqual(t, data.OpState, initialOpState) assert.Equal(t, data.OpState, data.PreAuthCode) assert.Empty(t, data.UserPin) @@ -873,20 +874,20 @@ func TestService_InitiateIssuance(t *testing.T) { configuration := data.CredentialConfiguration[0] assert.NotEmpty(t, configuration.ClaimDataID) - assert.EqualValues(t, oidc4ci.ClaimDataTypeVC, configuration.ClaimDataType) + assert.EqualValues(t, issuecredential.ClaimDataTypeVC, configuration.ClaimDataType) assert.EqualValues(t, "some-template", configuration.CredentialComposeConfiguration.IDTemplate) assert.True(t, configuration.CredentialComposeConfiguration.OverrideIssuer) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: profile.ID, PreAuthCode: expectedCode, IsPreAuthFlow: true, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -1044,15 +1045,15 @@ func TestService_InitiateIssuance(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { - return &oidc4ci.Transaction{ + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: profile.ID, PreAuthCode: expectedCode, IsPreAuthFlow: true, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -1345,20 +1346,20 @@ func TestService_InitiateIssuance(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { assert.NotEqual(t, data.OpState, initialOpState) assert.Equal(t, data.OpState, data.PreAuthCode) assert.Empty(t, data.UserPin) assert.Equal(t, true, data.IsPreAuthFlow) assert.NotEmpty(t, data.CredentialConfiguration[0].ClaimDataID) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: expectedCode, IsPreAuthFlow: true, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { CredentialTemplate: &profileapi.CredentialTemplate{ ID: "templateID", @@ -1542,9 +1543,9 @@ func TestService_InitiateIssuance(t *testing.T) { name: "Client initiate issuance URL takes precedence over client well-known parameter", setup: func(mocks *mocks) { mocks.transactionStore.EXPECT().Create(gomock.Any(), int32(0), gomock.Any()). - Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, }, @@ -1589,9 +1590,9 @@ func TestService_InitiateIssuance(t *testing.T) { name: "Custom initiate issuance URL when fail to do well-known request", setup: func(mocks *mocks) { mocks.transactionStore.EXPECT().Create(gomock.Any(), int32(0), gomock.Any()).Return( - &oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + &issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, }, @@ -1879,7 +1880,6 @@ func TestService_InitiateIssuance(t *testing.T) { PinGenerator: m.pinGenerator, DataProtector: m.crypto, JSONSchemaValidator: m.jsonSchemaValidator, - Composer: m.composer, DocumentLoader: m.documentLoader, }) require.NoError(t, err) @@ -1959,14 +1959,14 @@ func TestService_InitiateIssuanceWithRemoteStore(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { - assert.Equal(t, oidc4ci.TransactionStateIssuanceInitiated, data.State) + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { + assert.Equal(t, issuecredential.TransactionStateIssuanceInitiated, data.State) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -2031,14 +2031,14 @@ func TestService_InitiateIssuanceWithRemoteStore(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { - assert.Equal(t, oidc4ci.TransactionStateIssuanceInitiated, data.State) + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { + assert.Equal(t, issuecredential.TransactionStateIssuanceInitiated, data.State) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -2091,14 +2091,14 @@ func TestService_InitiateIssuanceWithRemoteStore(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { - assert.Equal(t, oidc4ci.TransactionStateIssuanceInitiated, data.State) + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { + assert.Equal(t, issuecredential.TransactionStateIssuanceInitiated, data.State) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -2204,14 +2204,14 @@ func TestService_InitiateIssuanceWithRemoteStore(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { - assert.Equal(t, oidc4ci.TransactionStateIssuanceInitiated, data.State) + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { + assert.Equal(t, issuecredential.TransactionStateIssuanceInitiated, data.State) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -2262,14 +2262,14 @@ func TestService_InitiateIssuanceWithRemoteStore(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { - assert.Equal(t, oidc4ci.TransactionStateIssuanceInitiated, data.State) + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { + assert.Equal(t, issuecredential.TransactionStateIssuanceInitiated, data.State) - return &oidc4ci.Transaction{ + return &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: verifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ diff --git a/pkg/service/oidc4ci/oidc4ci_service_state.go b/pkg/service/oidc4ci/oidc4ci_service_state.go index a1fd52695..bb0c62033 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_state.go +++ b/pkg/service/oidc4ci/oidc4ci_service_state.go @@ -10,34 +10,35 @@ import ( "fmt" "github.com/trustbloc/vcs/pkg/restapi/resterr" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) func (s *Service) validateStateTransition( - oldState TransactionState, - newState TransactionState, + oldState issuecredential.TransactionState, + newState issuecredential.TransactionState, ) error { - if oldState == TransactionStateIssuanceInitiated && - newState == TransactionStatePreAuthCodeValidated { + if oldState == issuecredential.TransactionStateIssuanceInitiated && + newState == issuecredential.TransactionStatePreAuthCodeValidated { return nil // pre-auth 1 } - if oldState == TransactionStateIssuanceInitiated && - newState == TransactionStateAwaitingIssuerOIDCAuthorization { + if oldState == issuecredential.TransactionStateIssuanceInitiated && + newState == issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization { return nil // auth 1 } - if oldState == TransactionStateAwaitingIssuerOIDCAuthorization && - newState == TransactionStateIssuerOIDCAuthorizationDone { + if oldState == issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization && + newState == issuecredential.TransactionStateIssuerOIDCAuthorizationDone { return nil } - if oldState == TransactionStatePreAuthCodeValidated && - newState == TransactionStateCredentialsIssued { + if oldState == issuecredential.TransactionStatePreAuthCodeValidated && + newState == issuecredential.TransactionStateCredentialsIssued { return nil } - if oldState == TransactionStateIssuerOIDCAuthorizationDone && - newState == TransactionStateCredentialsIssued { + if oldState == issuecredential.TransactionStateIssuerOIDCAuthorizationDone && + newState == issuecredential.TransactionStateCredentialsIssued { return nil } diff --git a/pkg/service/oidc4ci/oidc4ci_service_state_test.go b/pkg/service/oidc4ci/oidc4ci_service_state_test.go index eadd43428..6d228e212 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_state_test.go +++ b/pkg/service/oidc4ci/oidc4ci_service_state_test.go @@ -11,6 +11,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) func TestValidateTransition(t *testing.T) { @@ -18,28 +20,28 @@ func TestValidateTransition(t *testing.T) { assert.NoError(t, err) testCases := []struct { - from TransactionState - to TransactionState + from issuecredential.TransactionState + to issuecredential.TransactionState }{ { - from: TransactionStateIssuanceInitiated, - to: TransactionStatePreAuthCodeValidated, + from: issuecredential.TransactionStateIssuanceInitiated, + to: issuecredential.TransactionStatePreAuthCodeValidated, }, { - from: TransactionStateIssuanceInitiated, - to: TransactionStateAwaitingIssuerOIDCAuthorization, + from: issuecredential.TransactionStateIssuanceInitiated, + to: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, }, { - from: TransactionStateAwaitingIssuerOIDCAuthorization, - to: TransactionStateIssuerOIDCAuthorizationDone, + from: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, + to: issuecredential.TransactionStateIssuerOIDCAuthorizationDone, }, { - from: TransactionStatePreAuthCodeValidated, - to: TransactionStateCredentialsIssued, + from: issuecredential.TransactionStatePreAuthCodeValidated, + to: issuecredential.TransactionStateCredentialsIssued, }, { - from: TransactionStateIssuerOIDCAuthorizationDone, - to: TransactionStateCredentialsIssued, + from: issuecredential.TransactionStateIssuerOIDCAuthorizationDone, + to: issuecredential.TransactionStateCredentialsIssued, }, } @@ -54,6 +56,8 @@ func TestInvalidTransition(t *testing.T) { s, err := NewService(&Config{}) assert.NoError(t, err) - assert.ErrorContains(t, s.validateStateTransition(TransactionStateUnknown, TransactionStateIssuanceInitiated), - "unexpected transition from 0 to 1") + assert.ErrorContains(t, s.validateStateTransition( + issuecredential.TransactionStateUnknown, + issuecredential.TransactionStateIssuanceInitiated, + ), "unexpected transition from 0 to 1") } diff --git a/pkg/service/oidc4ci/oidc4ci_service_store_auth_code.go b/pkg/service/oidc4ci/oidc4ci_service_store_auth_code.go index e60530615..8b4876c93 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_store_auth_code.go +++ b/pkg/service/oidc4ci/oidc4ci_service_store_auth_code.go @@ -16,6 +16,7 @@ import ( "github.com/trustbloc/vcs/pkg/event/spi" "github.com/trustbloc/vcs/pkg/restapi/resterr" "github.com/trustbloc/vcs/pkg/restapi/v1/common" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) // StoreAuthorizationCode stores authorization code from issuer provider. @@ -24,8 +25,8 @@ func (s *Service) StoreAuthorizationCode( opState string, code string, flowData *common.WalletInitiatedFlowData, -) (TxID, error) { - var tx *Transaction +) (issuecredential.TxID, error) { + var tx *issuecredential.Transaction var err error if flowData != nil { // it's wallet initiated issuance, first we need to initiate issuance tx, err = s.initiateIssuanceWithWalletFlow(ctx, flowData) @@ -53,7 +54,7 @@ func (s *Service) StoreAuthorizationCode( func (s *Service) initiateIssuanceWithWalletFlow( ctx context.Context, flowData *common.WalletInitiatedFlowData, -) (*Transaction, error) { +) (*issuecredential.Transaction, error) { profile, err := s.profileService.GetProfile(flowData.ProfileId, flowData.ProfileVersion) if err != nil { if strings.Contains(err.Error(), "not found") { diff --git a/pkg/service/oidc4ci/oidc4ci_service_store_auth_code_test.go b/pkg/service/oidc4ci/oidc4ci_service_store_auth_code_test.go index 15038a199..b0933205c 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_store_auth_code_test.go +++ b/pkg/service/oidc4ci/oidc4ci_service_store_auth_code_test.go @@ -19,6 +19,7 @@ import ( "github.com/trustbloc/vcs/pkg/event/spi" profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/restapi/v1/common" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/service/oidc4ci" ) @@ -47,14 +48,14 @@ func TestStoreAuthCode(t *testing.T) { opState := uuid.NewString() code := uuid.NewString() - tx := oidc4ci.Transaction{ - ID: oidc4ci.TxID(uuid.NewString()), + tx := issuecredential.Transaction{ + ID: issuecredential.TxID(uuid.NewString()), } store.EXPECT().FindByOpState(gomock.Any(), opState). Return(&tx, nil) store.EXPECT().Update(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, req *oidc4ci.Transaction) error { + func(ctx context.Context, req *issuecredential.Transaction) error { assert.Equal(t, tx.ID, req.ID) assert.Equal(t, code, req.IssuerAuthCode) @@ -78,14 +79,14 @@ func TestStoreAuthCode(t *testing.T) { opState := uuid.NewString() code := uuid.NewString() - tx := oidc4ci.Transaction{ - ID: oidc4ci.TxID(uuid.NewString()), + tx := issuecredential.Transaction{ + ID: issuecredential.TxID(uuid.NewString()), } store.EXPECT().FindByOpState(gomock.Any(), opState). Return(&tx, nil) store.EXPECT().Update(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, req *oidc4ci.Transaction) error { + func(ctx context.Context, req *issuecredential.Transaction) error { assert.Equal(t, tx.ID, req.ID) assert.Equal(t, code, req.IssuerAuthCode) @@ -109,14 +110,14 @@ func TestStoreAuthCode(t *testing.T) { opState := uuid.NewString() code := uuid.NewString() - tx := oidc4ci.Transaction{ - ID: oidc4ci.TxID(uuid.NewString()), + tx := issuecredential.Transaction{ + ID: issuecredential.TxID(uuid.NewString()), } store.EXPECT().FindByOpState(gomock.Any(), opState). Return(&tx, nil) store.EXPECT().Update(gomock.Any(), gomock.Any()).DoAndReturn( - func(ctx context.Context, req *oidc4ci.Transaction) error { + func(ctx context.Context, req *issuecredential.Transaction) error { assert.Equal(t, tx.ID, req.ID) assert.Equal(t, code, req.IssuerAuthCode) @@ -217,7 +218,7 @@ func TestInitiateWalletFlowFromStoreCode(t *testing.T) { }, nil) store.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any()). - Return(&oidc4ci.Transaction{}, nil) + Return(&issuecredential.Transaction{}, nil) wellKnown.EXPECT().GetOIDCConfiguration(gomock.Any(), gomock.Any()). Return(&oidc4ci.IssuerIDPOIDCConfiguration{}, nil) eventMock.EXPECT().Publish(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() @@ -297,10 +298,10 @@ func TestInitiateWalletFlowFromStoreCode(t *testing.T) { DoAndReturn(func( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, - ) (*oidc4ci.Transaction, error) { + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { assert.Equal(t, "v1.latest", data.ProfileVersion) - return &oidc4ci.Transaction{}, nil + return &issuecredential.Transaction{}, nil }) wellKnown.EXPECT().GetOIDCConfiguration(gomock.Any(), gomock.Any()). diff --git a/pkg/service/oidc4ci/oidc4ci_service_test.go b/pkg/service/oidc4ci/oidc4ci_service_test.go index 17b97ec90..b924d537d 100644 --- a/pkg/service/oidc4ci/oidc4ci_service_test.go +++ b/pkg/service/oidc4ci/oidc4ci_service_test.go @@ -27,6 +27,7 @@ import ( "github.com/trustbloc/vcs/pkg/event/spi" profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/restapi/resterr" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/service/oidc4ci" "github.com/trustbloc/vcs/pkg/service/trustregistry" ) @@ -35,7 +36,7 @@ func TestService_PushAuthorizationDetails(t *testing.T) { var ( mockTransactionStore = NewMockTransactionStore(gomock.NewController(t)) profileSvc = NewMockProfileService(gomock.NewController(t)) - ad *oidc4ci.AuthorizationDetails + ad *issuecredential.AuthorizationDetails ) tests := []struct { @@ -48,12 +49,12 @@ func TestService_PushAuthorizationDetails(t *testing.T) { setup: func() { prcUUID, udUUID := uuid.NewString(), uuid.NewString() - mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -76,12 +77,12 @@ func TestService_PushAuthorizationDetails(t *testing.T) { }, }, nil) - mockTransactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mockTransactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { // only single CredentialConfiguration expected. ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -89,8 +90,8 @@ func TestService_PushAuthorizationDetails(t *testing.T) { ID: "UniversityDegreeCredentialID", Type: "UniversityDegreeCredential", }, - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ - CredentialDefinition: &oidc4ci.CredentialDefinition{ + AuthorizationDetails: &issuecredential.AuthorizationDetails{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -108,8 +109,8 @@ func TestService_PushAuthorizationDetails(t *testing.T) { }, }, nil) - ad = &oidc4ci.AuthorizationDetails{ - CredentialDefinition: &oidc4ci.CredentialDefinition{ + ad = &issuecredential.AuthorizationDetails{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -124,12 +125,12 @@ func TestService_PushAuthorizationDetails(t *testing.T) { setup: func() { prcUUID, udUUID := uuid.NewString(), uuid.NewString() - mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -176,12 +177,12 @@ func TestService_PushAuthorizationDetails(t *testing.T) { }, }, nil) - mockTransactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mockTransactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { // only single CredentialConfiguration expected. ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -189,7 +190,7 @@ func TestService_PushAuthorizationDetails(t *testing.T) { ID: "UniversityDegreeCredentialID", Type: "UniversityDegreeCredential", }, - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ + AuthorizationDetails: &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", }, CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", @@ -198,7 +199,7 @@ func TestService_PushAuthorizationDetails(t *testing.T) { }, }).Return(nil) - ad = &oidc4ci.AuthorizationDetails{ + ad = &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", } }, @@ -212,8 +213,8 @@ func TestService_PushAuthorizationDetails(t *testing.T) { mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return( nil, errors.New("find tx error")) - ad = &oidc4ci.AuthorizationDetails{ - CredentialDefinition: &oidc4ci.CredentialDefinition{ + ad = &issuecredential.AuthorizationDetails{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "universitydegreecredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -226,11 +227,11 @@ func TestService_PushAuthorizationDetails(t *testing.T) { { name: "Error AuthorizationDetails contains CredentialConfigurationID field: get profile not found", setup: func() { - mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -245,7 +246,7 @@ func TestService_PushAuthorizationDetails(t *testing.T) { profileSvc.EXPECT().GetProfile("bank_issuer1", "v1.0").Return( nil, errors.New("not found")) - ad = &oidc4ci.AuthorizationDetails{ + ad = &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "UniversityDegreeCredential", } }, @@ -263,11 +264,11 @@ func TestService_PushAuthorizationDetails(t *testing.T) { { name: "Error AuthorizationDetails contains CredentialConfigurationID field: get profile common error", setup: func() { - mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -282,7 +283,7 @@ func TestService_PushAuthorizationDetails(t *testing.T) { profileSvc.EXPECT().GetProfile("bank_issuer1", "v1.0").Return( nil, errors.New("some error")) - ad = &oidc4ci.AuthorizationDetails{ + ad = &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "UniversityDegreeCredential", } }, @@ -300,12 +301,12 @@ func TestService_PushAuthorizationDetails(t *testing.T) { { name: "Error AuthorizationDetails contains CredentialConfigurationID field: empty CredentialMetaData", setup: func() { - mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -322,7 +323,7 @@ func TestService_PushAuthorizationDetails(t *testing.T) { CredentialMetaData: nil, }, nil) - ad = &oidc4ci.AuthorizationDetails{ + ad = &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "UniversityDegreeCredential", } }, @@ -341,12 +342,12 @@ func TestService_PushAuthorizationDetails(t *testing.T) { name: "Error AuthorizationDetails contains CredentialConfigurationID field: " + "CredentialMetaData for different VC type", setup: func() { - mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -374,7 +375,7 @@ func TestService_PushAuthorizationDetails(t *testing.T) { }, }, nil) - ad = &oidc4ci.AuthorizationDetails{ + ad = &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "UniversityDegreeCredential", } }, @@ -392,12 +393,12 @@ func TestService_PushAuthorizationDetails(t *testing.T) { { name: "Error AuthorizationDetails contains CredentialConfigurationID field: invalid OIDC format", setup: func() { - mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -425,7 +426,7 @@ func TestService_PushAuthorizationDetails(t *testing.T) { }, }, nil) - ad = &oidc4ci.AuthorizationDetails{ + ad = &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", } }, @@ -443,12 +444,12 @@ func TestService_PushAuthorizationDetails(t *testing.T) { { name: "Error AuthorizationDetails contains CredentialConfigurationID field: Credential type not supported", setup: func() { - mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -476,7 +477,7 @@ func TestService_PushAuthorizationDetails(t *testing.T) { }, }, nil) - ad = &oidc4ci.AuthorizationDetails{ + ad = &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", } }, @@ -494,12 +495,12 @@ func TestService_PushAuthorizationDetails(t *testing.T) { { name: "Error AuthorizationDetails contains Format field: Credential format not supported", setup: func() { - mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -527,8 +528,8 @@ func TestService_PushAuthorizationDetails(t *testing.T) { }, }, nil) - ad = &oidc4ci.AuthorizationDetails{ - CredentialDefinition: &oidc4ci.CredentialDefinition{ + ad = &issuecredential.AuthorizationDetails{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -541,12 +542,12 @@ func TestService_PushAuthorizationDetails(t *testing.T) { { name: "Fail to update transaction", setup: func() { - mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -577,8 +578,8 @@ func TestService_PushAuthorizationDetails(t *testing.T) { }, }, nil) - ad = &oidc4ci.AuthorizationDetails{ - CredentialDefinition: &oidc4ci.CredentialDefinition{ + ad = &issuecredential.AuthorizationDetails{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -591,12 +592,12 @@ func TestService_PushAuthorizationDetails(t *testing.T) { { name: "Error neither credentialFormat nor credentialConfigurationID supplied", setup: func() { - mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mockTransactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, CredentialTemplate: &profileapi.CredentialTemplate{ @@ -624,8 +625,8 @@ func TestService_PushAuthorizationDetails(t *testing.T) { }, }, nil) - ad = &oidc4ci.AuthorizationDetails{ - CredentialDefinition: &oidc4ci.CredentialDefinition{ + ad = &issuecredential.AuthorizationDetails{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, } @@ -645,7 +646,7 @@ func TestService_PushAuthorizationDetails(t *testing.T) { }) assert.NoError(t, err) - err = svc.PushAuthorizationDetails(context.Background(), "opState", []*oidc4ci.AuthorizationDetails{ad}) + err = svc.PushAuthorizationDetails(context.Background(), "opState", []*issuecredential.AuthorizationDetails{ad}) tt.check(t, err) }) } @@ -667,15 +668,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Success AuthorizationDetails contains duplicated Format field - same credential with different type", setup: func(mocks *mocks) { prcUUID, udUUID := uuid.NewString(), uuid.NewString() - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -698,15 +699,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { // Expect only single CredentialConfiguration. ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -715,8 +716,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { Type: "UniversityDegreeCredential", }, CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ // Expect AuthorizationDetails field. - CredentialDefinition: &oidc4ci.CredentialDefinition{ + AuthorizationDetails: &issuecredential.AuthorizationDetails{ // Expect AuthorizationDetails field. + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -725,8 +726,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -747,15 +748,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, }, { - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -773,15 +774,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Success AuthorizationDetails contains duplicated Format field - same credential with same type", setup: func(mocks *mocks) { udUUID1, udUUID2 := uuid.NewString(), uuid.NewString() - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID1, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -804,15 +805,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { // Expect 2 CredentialConfigurations. ID: udUUID1, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -821,8 +822,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { Type: "UniversityDegreeCredential", }, CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ // Expect AuthorizationDetails field. - CredentialDefinition: &oidc4ci.CredentialDefinition{ + AuthorizationDetails: &issuecredential.AuthorizationDetails{ // Expect AuthorizationDetails field. + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -836,8 +837,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { Type: "UniversityDegreeCredential", }, CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ // Expect AuthorizationDetails field. - CredentialDefinition: &oidc4ci.CredentialDefinition{ + AuthorizationDetails: &issuecredential.AuthorizationDetails{ // Expect AuthorizationDetails field. + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -846,8 +847,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -868,9 +869,9 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -889,15 +890,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { "CredentialConfigurationID field - different credentials", setup: func(mocks *mocks) { prcUUID, udUUID := uuid.NewString(), uuid.NewString() - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -920,15 +921,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { // Expect UniversityDegreeCredentialIdentifier CredentialConfiguration. ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -936,8 +937,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { ID: "credetnialTempalteID", Type: "UniversityDegreeCredential", }, - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ // Expect AuthorizationDetails field. - CredentialDefinition: &oidc4ci.CredentialDefinition{ + AuthorizationDetails: &issuecredential.AuthorizationDetails{ // Expect AuthorizationDetails field. + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -951,7 +952,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { ID: "credetnialTempalteID", Type: "PermanentResidentCard", }, - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ // Expect AuthorizationDetails field. + AuthorizationDetails: &issuecredential.AuthorizationDetails{ // Expect AuthorizationDetails field. CredentialConfigurationID: "PermanentResidentCardIdentifier", }, CredentialConfigurationID: "PermanentResidentCardIdentifier", @@ -959,8 +960,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -991,9 +992,9 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -1015,15 +1016,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { "Same credentials: case 1 of order of AuthorizationDetails in reqeust", setup: func(mocks *mocks) { udUUID, prcUUID := uuid.NewString(), uuid.NewString() - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1046,15 +1047,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { // Expect only PermanentResidentCardIdentifier CredentialConfiguration. ID: prcUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1062,7 +1063,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { ID: "credetnialTempalteID", Type: "PermanentResidentCard", }, - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ // Expect AuthorizationDetails field. + AuthorizationDetails: &issuecredential.AuthorizationDetails{ // Expect AuthorizationDetails field. CredentialConfigurationID: "PermanentResidentCardIdentifier", }, CredentialConfigurationID: "PermanentResidentCardIdentifier", @@ -1070,8 +1071,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -1102,9 +1103,9 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "PermanentResidentCard"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -1126,15 +1127,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { "Same credentials: case 2 of order of AuthorizationDetails in reqeust", setup: func(mocks *mocks) { udUUID, prcUUID := uuid.NewString(), uuid.NewString() - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1157,15 +1158,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { // Expect only PermanentResidentCardIdentifier CredentialConfiguration. ID: prcUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1173,9 +1174,9 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { ID: "credetnialTempalteID", Type: "PermanentResidentCard", }, - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ + AuthorizationDetails: &issuecredential.AuthorizationDetails{ Format: vcsverifiable.JwtVCJsonLD, - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "PermanentResidentCard"}, }, }, @@ -1184,8 +1185,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -1216,12 +1217,12 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { CredentialConfigurationID: "PermanentResidentCardIdentifier", }, { - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "PermanentResidentCard"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -1240,15 +1241,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { "single credential", setup: func(mocks *mocks) { prcUUID, udUUID := uuid.NewString(), uuid.NewString() - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{"openid", "profile", "address"}, - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1298,15 +1299,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { // Expect single CredentialConfiguration. ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1314,7 +1315,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { ID: "templateID", Type: "UniversityDegreeCredential", }, - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ // Expect AuthorizationDetails field + AuthorizationDetails: &issuecredential.AuthorizationDetails{ // Expect AuthorizationDetails field CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", }, CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", @@ -1322,8 +1323,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -1334,7 +1335,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", }, @@ -1355,15 +1356,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { "multiple credentials with same type", setup: func(mocks *mocks) { udUUID1, udUUID2 := uuid.NewString(), uuid.NewString() - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{"openid", "profile", "address"}, - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID1, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1405,15 +1406,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { // Expect 2 CredentialConfigurations. ID: udUUID1, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1421,7 +1422,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { ID: "templateID", Type: "UniversityDegreeCredential", }, - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ // Expect AuthorizationDetails field + AuthorizationDetails: &issuecredential.AuthorizationDetails{ // Expect AuthorizationDetails field CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", }, CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", @@ -1433,7 +1434,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { ID: "templateID", Type: "UniversityDegreeCredential", }, - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ // Expect AuthorizationDetails field + AuthorizationDetails: &issuecredential.AuthorizationDetails{ // Expect AuthorizationDetails field CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", }, CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", @@ -1441,8 +1442,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -1453,7 +1454,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", }, @@ -1470,9 +1471,9 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Success Scope based (AuthorizationDetails not supplied) with duplicated and unknown request scopes", setup: func(mocks *mocks) { udUUID1, udUUID2, udUUID3 := uuid.NewString(), uuid.NewString(), uuid.NewString() - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{ "openid", @@ -1483,10 +1484,10 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { "UniversityDegreeCredential_004", "UniversityDegreeCredential_005", }, - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID1, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1562,18 +1563,18 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{ "UniversityDegreeCredential_001", // expect only valid scopes. "UniversityDegreeCredential_002", }, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ // expect 2 CredentialConfigurations. + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ // expect 2 CredentialConfigurations. { // do not expect AuthorizationDetails. ID: udUUID1, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1595,8 +1596,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -1625,19 +1626,19 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { { name: "Success Scope based (AuthorizationDetails not supplied) with format mismatch", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{ "openid", "profile", "UniversityDegreeCredential_001", }, - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.LdpVC, // Error cause. @@ -1675,21 +1676,21 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{ "UniversityDegreeCredential_001", // expect only valid scopes. }, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", CredentialConfiguration: nil, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -1714,19 +1715,19 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { { name: "Success Scope based (AuthorizationDetails not supplied) with type mismatch", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{ "openid", "profile", "UniversityDegreeCredential_001", }, - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1764,21 +1765,21 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{ "UniversityDegreeCredential_001", // expect only valid scopes. }, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", CredentialConfiguration: nil, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -1804,19 +1805,19 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Success Scope and AuthorizationDetails based - different credentials", setup: func(mocks *mocks) { udUUID, prcUUID := uuid.NewString(), uuid.NewString() - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{ "openid", "profile", "UniversityDegreeCredential_001", }, - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1871,17 +1872,17 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{ "UniversityDegreeCredential_001", // expect only valid scopes. }, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { // Do not expect AuthorizationDetails. ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1898,7 +1899,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { ID: "TemplateID2", Type: "PermanentResidentCard", }, - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ + AuthorizationDetails: &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "PermanentResidentCardIdentifier_2", }, CredentialConfigurationID: "PermanentResidentCardIdentifier_2", @@ -1906,8 +1907,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -1918,7 +1919,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"UniversityDegreeCredential_001"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { CredentialConfigurationID: "PermanentResidentCardIdentifier_2", }, @@ -1935,19 +1936,19 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Success Scope and AuthorizationDetails based - same credentials", setup: func(mocks *mocks) { udUUID := uuid.NewString() - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{ "openid", "profile", "UniversityDegreeCredential_001", }, - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -1985,17 +1986,17 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{ "UniversityDegreeCredential_001", // expect only valid scopes. }, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { // Do not expect AuthorizationDetails. ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -2003,7 +2004,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { ID: "TemplateID1", Type: "UniversityDegreeCredential", }, - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ + AuthorizationDetails: &issuecredential.AuthorizationDetails{ CredentialConfigurationID: "UniversityDegreeCredentialIdentifier_1", }, CredentialConfigurationID: "UniversityDegreeCredentialIdentifier_1", @@ -2011,8 +2012,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -2023,7 +2024,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"UniversityDegreeCredential_001"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { CredentialConfigurationID: "UniversityDegreeCredentialIdentifier_1", }, @@ -2055,10 +2056,10 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { { name: "invalid tx state", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ - State: oidc4ci.TransactionStateCredentialsIssued, + TransactionData: issuecredential.TransactionData{ + State: issuecredential.TransactionStateCredentialsIssued, }, }, nil) @@ -2083,10 +2084,10 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { { name: "Response type mismatch", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ - State: oidc4ci.TransactionStateIssuanceInitiated, + TransactionData: issuecredential.TransactionData{ + State: issuecredential.TransactionStateIssuanceInitiated, ResponseType: "code", Scope: []string{"openid"}, }, @@ -2105,15 +2106,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { { name: "Error invalid scope: AuthorizationDetails supplied: request scope is unexpected", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", ProfileID: "bank_issuer1", ProfileVersion: "v1.0", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), CredentialTemplate: &profileapi.CredentialTemplate{ @@ -2151,11 +2152,11 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { { name: "get profile: not found", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", }, @@ -2183,11 +2184,11 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { { name: "get profile: system error", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", }, @@ -2217,14 +2218,14 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Error AuthorizationDetails contains CredentialConfigurationID field: " + "empty profile.CredentialMetaData: resterr.ErrInvalidCredentialConfigurationID", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, }, }, nil) @@ -2249,7 +2250,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", }, @@ -2265,15 +2266,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Error AuthorizationDetails contains CredentialConfigurationID field: " + "format mismatch: resterr.ErrCredentialFormatNotSupported", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.LdpVC, // error cause. @@ -2319,7 +2320,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", }, @@ -2335,15 +2336,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Error AuthorizationDetails contains CredentialConfigurationID field: " + "empty meta credential definition: resterr.ErrCredentialTypeNotSupported", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -2385,7 +2386,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", }, @@ -2401,15 +2402,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Error AuthorizationDetails contains CredentialConfigurationID field: " + "CredentialDefinition.Type mismatch: resterr.ErrCredentialTypeNotSupported", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -2453,7 +2454,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", }, @@ -2470,15 +2471,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Error AuthorizationDetails contains duplicated CredentialConfigurationID field: " + "txCredentialConfiguration is not fund with given CredentialConfigurationID", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{"openid", "profile", "address"}, - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -2523,7 +2524,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { CredentialConfigurationID: "PermanentResidentCardIdentifier", }, @@ -2539,15 +2540,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Error AuthorizationDetails contains Format field: no txCredentialConfigurations: " + "requested credential format is not valid", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{}, + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{}, }, }, nil) @@ -2572,9 +2573,9 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -2591,15 +2592,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Error AuthorizationDetails contains Format field: CredentialTemplate.Type mismatch: " + "requested credential format is not valid", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -2634,9 +2635,9 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -2653,15 +2654,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Error AuthorizationDetails contains Format field: txCredentialConfig.OIDCCredentialFormat mismatch: " + "requested credential format is not valid", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.LdpVC, // cause of mismatch. @@ -2696,9 +2697,9 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -2714,15 +2715,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { { name: "Error invalid Authorization Details: neither credentialFormat nor credentialConfigurationID supplied", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile", "address"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.LdpVC, @@ -2758,7 +2759,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{{}}, + AuthorizationDetails: []*issuecredential.AuthorizationDetails{{}}, } }, check: func(t *testing.T, resp *oidc4ci.PrepareClaimDataAuthorizationResponse, err error) { @@ -2770,18 +2771,18 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { { name: "Error Scope based: scope is not in tx", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{ "openid", "profile", }, - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -2829,15 +2830,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { name: "Error tx store update", setup: func(mocks *mocks) { udUUID := uuid.NewString() - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{"openid", "profile", "address"}, - State: oidc4ci.TransactionStateIssuanceInitiated, + State: issuecredential.TransactionStateIssuanceInitiated, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -2870,15 +2871,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, nil) - mocks.transactionStore.EXPECT().Update(gomock.Any(), &oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().Update(gomock.Any(), &issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, + State: issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, ProfileID: "bank_issuer1", ProfileVersion: "v1.0", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { // Expect single CredentialConfiguration. ID: udUUID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -2886,7 +2887,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { ID: "templateID", Type: "UniversityDegreeCredential", }, - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ // Expect AuthorizationDetails field + AuthorizationDetails: &issuecredential.AuthorizationDetails{ // Expect AuthorizationDetails field CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", }, CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", @@ -2894,8 +2895,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, }, }). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return errors.New("some error") }).Times(1) @@ -2912,7 +2913,7 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { CredentialConfigurationID: "UniversityDegreeCredentialIdentifier", }, @@ -2933,15 +2934,15 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { { name: "Error sending event", setup: func(mocks *mocks) { - mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&oidc4ci.Transaction{ + mocks.transactionStore.EXPECT().FindByOpState(gomock.Any(), "opState").Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ ProfileID: "bank_issuer1", ProfileVersion: "v1.0", ResponseType: "code", Scope: []string{"openid", "profile"}, - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -2956,8 +2957,8 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { }, nil) mocks.transactionStore.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateAwaitingIssuerOIDCAuthorization, tx.State) return nil }).Times(1) @@ -2982,9 +2983,9 @@ func TestService_PrepareClaimDataAuthorizationRequest(t *testing.T) { OpState: "opState", ResponseType: "code", Scope: []string{"openid", "profile"}, - AuthorizationDetails: []*oidc4ci.AuthorizationDetails{ + AuthorizationDetails: []*issuecredential.AuthorizationDetails{ { - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"VerifiableCredential", "UniversityDegreeCredential"}, }, Format: vcsverifiable.JwtVCJsonLD, @@ -3394,12 +3395,12 @@ func TestValidatePreAuthCode(t *testing.T) { }, nil) pinGenerator.EXPECT().Validate("567", "567").Return(true) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ - State: oidc4ci.TransactionStateIssuanceInitiated, + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ + State: issuecredential.TransactionStateIssuanceInitiated, PreAuthCode: "1234", UserPin: "567", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialConfigurationID: "ConfigurationID", @@ -3445,12 +3446,12 @@ func TestValidatePreAuthCode(t *testing.T) { return nil }) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialConfigurationID: "ConfigurationID", @@ -3509,12 +3510,12 @@ func TestValidatePreAuthCode(t *testing.T) { return nil }) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialTemplate: &profileapi.CredentialTemplate{ @@ -3556,12 +3557,12 @@ func TestValidatePreAuthCode(t *testing.T) { return errors.New("unexpected error") }) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(20 * time.Second)), CredentialConfigurationID: "ConfigurationID", @@ -3592,12 +3593,12 @@ func TestValidatePreAuthCode(t *testing.T) { pinGenerator.EXPECT().Validate("567", "111").Return(false) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "567", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialConfigurationID: "ConfigurationID", @@ -3637,12 +3638,12 @@ func TestValidatePreAuthCode(t *testing.T) { profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).Return(&profileapi.Issuer{}, nil) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "567", - State: oidc4ci.TransactionStateCredentialsIssued, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateCredentialsIssued, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialConfigurationID: "ConfigurationID", @@ -3666,11 +3667,11 @@ func TestValidatePreAuthCode(t *testing.T) { }) assert.NoError(t, err) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialConfigurationID: "ConfigurationID", @@ -3694,12 +3695,12 @@ func TestValidatePreAuthCode(t *testing.T) { }) assert.NoError(t, err) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "123", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialConfigurationID: "ConfigurationID", @@ -3723,12 +3724,12 @@ func TestValidatePreAuthCode(t *testing.T) { }) assert.NoError(t, err) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "123", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialConfigurationID: "ConfigurationID", @@ -3755,12 +3756,12 @@ func TestValidatePreAuthCode(t *testing.T) { }) assert.NoError(t, err) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "123", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialConfigurationID: "ConfigurationID", @@ -3796,12 +3797,12 @@ func TestValidatePreAuthCode(t *testing.T) { profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).Return(&profileapi.Issuer{}, nil) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "12345", UserPin: "123", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialConfigurationID: "ConfigurationID", @@ -3827,12 +3828,12 @@ func TestValidatePreAuthCode(t *testing.T) { profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).Return(&profileapi.Issuer{}, nil) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "123", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(-10 * time.Second)), CredentialConfigurationID: "ConfigurationID", @@ -3858,12 +3859,12 @@ func TestValidatePreAuthCode(t *testing.T) { profileService.EXPECT().GetProfile(gomock.Any(), gomock.Any()).Return(&profileapi.Issuer{}, nil) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialConfigurationID: "ConfigurationID", @@ -3906,12 +3907,12 @@ func TestValidatePreAuthCode(t *testing.T) { trustRegistry.EXPECT().ValidateIssuance(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialTemplate: &profileapi.CredentialTemplate{ @@ -3954,12 +3955,12 @@ func TestValidatePreAuthCode(t *testing.T) { trustRegistry.EXPECT().ValidateIssuance(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialTemplate: &profileapi.CredentialTemplate{ @@ -4003,12 +4004,12 @@ func TestValidatePreAuthCode(t *testing.T) { trustRegistry.EXPECT().ValidateIssuance(gomock.Any(), gomock.Any(), gomock.Any()).Times(0) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialTemplate: &profileapi.CredentialTemplate{ @@ -4053,12 +4054,12 @@ func TestValidatePreAuthCode(t *testing.T) { trustRegistry.EXPECT().ValidateIssuance(gomock.Any(), gomock.Any(), gomock.Any()). Return(errors.New("validate issuance error")) - storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ + storeMock.EXPECT().FindByOpState(gomock.Any(), "1234").Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ PreAuthCode: "1234", UserPin: "", - State: oidc4ci.TransactionStateIssuanceInitiated, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + State: issuecredential.TransactionStateIssuanceInitiated, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { PreAuthCodeExpiresAt: lo.ToPtr(time.Now().UTC().Add(10 * time.Second)), CredentialTemplate: &profileapi.CredentialTemplate{ @@ -4092,11 +4093,11 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Success multiple credentials different type", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ IssuerToken: "issuer-access-token", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -4138,8 +4139,8 @@ func TestService_PrepareCredential(t *testing.T) { } m.transactionStore.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateCredentialsIssued, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateCredentialsIssued, tx.State) return nil }) @@ -4193,11 +4194,11 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Success multiple credentials same type", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ IssuerToken: "issuer-access-token", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -4239,8 +4240,8 @@ func TestService_PrepareCredential(t *testing.T) { } m.transactionStore.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateCredentialsIssued, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateCredentialsIssued, tx.State) return nil }) @@ -4294,11 +4295,11 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Success LDP", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ IssuerToken: "issuer-access-token", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.LdpVC, @@ -4332,8 +4333,8 @@ func TestService_PrepareCredential(t *testing.T) { } m.transactionStore.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateCredentialsIssued, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateCredentialsIssued, tx.State) return nil }) @@ -4367,11 +4368,11 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Success LDP with name and description", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ IssuerToken: "issuer-access-token", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.LdpVC, @@ -4407,8 +4408,8 @@ func TestService_PrepareCredential(t *testing.T) { } m.transactionStore.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateCredentialsIssued, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateCredentialsIssued, tx.State) return nil }) @@ -4447,13 +4448,13 @@ func TestService_PrepareCredential(t *testing.T) { name: "Success pre-authorized flow - multiple credentials different type", setup: func(m *mocks) { claimID := uuid.NewString() - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ IsPreAuthFlow: true, OrgID: "asdasd", WebHookURL: "aaaaa", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), ClaimDataID: claimID, @@ -4495,12 +4496,12 @@ func TestService_PrepareCredential(t *testing.T) { }) m.transactionStore.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateCredentialsIssued, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateCredentialsIssued, tx.State) return nil }) - clData := &oidc4ci.ClaimData{ + clData := &issuecredential.ClaimData{ EncryptedData: &dataprotect.EncryptedData{ Encrypted: []byte{0x1, 0x2, 0x3}, EncryptedNonce: []byte{0x0, 0x2}, @@ -4558,23 +4559,23 @@ func TestService_PrepareCredential(t *testing.T) { name: "Success pre-authorized flow - multiple credentials same type with compose", setup: func(m *mocks) { claimID := uuid.NewString() - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ IsPreAuthFlow: true, OrgID: "asdasd", WebHookURL: "aaaaa", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), ClaimDataID: claimID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, - ClaimDataType: oidc4ci.ClaimDataTypeVC, + ClaimDataType: issuecredential.ClaimDataTypeVC, CredentialTemplate: &profileapi.CredentialTemplate{ ID: "VerifiedEmployee", Type: "VerifiedEmployee", }, - CredentialComposeConfiguration: &oidc4ci.CredentialComposeConfiguration{ + CredentialComposeConfiguration: &issuecredential.CredentialComposeConfiguration{ IDTemplate: "some-template", OverrideIssuer: true, }, @@ -4584,12 +4585,12 @@ func TestService_PrepareCredential(t *testing.T) { ID: uuid.NewString(), ClaimDataID: claimID, OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, - ClaimDataType: oidc4ci.ClaimDataTypeVC, + ClaimDataType: issuecredential.ClaimDataTypeVC, CredentialTemplate: &profileapi.CredentialTemplate{ ID: "VerifiedEmployee", Type: "VerifiedEmployee", }, - CredentialComposeConfiguration: &oidc4ci.CredentialComposeConfiguration{ + CredentialComposeConfiguration: &issuecredential.CredentialComposeConfiguration{ IDTemplate: "some-template", OverrideIssuer: true, }, @@ -4616,23 +4617,21 @@ func TestService_PrepareCredential(t *testing.T) { }) m.transactionStore.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateCredentialsIssued, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateCredentialsIssued, tx.State) return nil }) - m.composer.EXPECT().Compose(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + m.composer.EXPECT().Compose(gomock.Any(), gomock.Any(), gomock.Any()). Times(2).DoAndReturn(func( ctx context.Context, credential *verifiable.Credential, - transaction *oidc4ci.Transaction, - configuration *oidc4ci.TxCredentialConfiguration, - request *oidc4ci.PrepareCredentialRequest, + req *issuecredential.PrepareCredentialsRequest, ) (*verifiable.Credential, error) { assert.EqualValues(t, "some-template", - configuration.CredentialComposeConfiguration.IDTemplate) + req.CredentialConfiguration.CredentialComposeConfiguration.IDTemplate) - assert.True(t, configuration.CredentialComposeConfiguration.OverrideIssuer) + assert.True(t, req.CredentialConfiguration.CredentialComposeConfiguration.OverrideIssuer) return credential, nil }) @@ -4656,7 +4655,7 @@ func TestService_PrepareCredential(t *testing.T) { }, verifiable.CustomFields{}) assert.NoError(t, err) - clData := &oidc4ci.ClaimData{ + clData := &issuecredential.ClaimData{ EncryptedData: &dataprotect.EncryptedData{ Encrypted: []byte{0x1, 0x2, 0x3}, EncryptedNonce: []byte{0x0, 0x2}, @@ -4716,14 +4715,14 @@ func TestService_PrepareCredential(t *testing.T) { name: "Can not create ack", setup: func(m *mocks) { claimID := uuid.NewString() - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ IssuerToken: "issuer-access-token", IsPreAuthFlow: true, OrgID: "asdasd", WebHookURL: "aaaaa", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), ClaimDataID: claimID, @@ -4750,12 +4749,12 @@ func TestService_PrepareCredential(t *testing.T) { }) m.transactionStore.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateCredentialsIssued, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateCredentialsIssued, tx.State) return nil }) - clData := &oidc4ci.ClaimData{ + clData := &issuecredential.ClaimData{ EncryptedData: &dataprotect.EncryptedData{ Encrypted: []byte{0x1, 0x2, 0x3}, EncryptedNonce: []byte{0x0, 0x2}, @@ -4790,12 +4789,12 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Failed to get claims for pre-authorized flow", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ IssuerToken: "issuer-access-token", IsPreAuthFlow: true, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), ClaimDataID: uuid.NewString(), @@ -4841,12 +4840,12 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Failed to send event for pre-authorized flow", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ IssuerToken: "issuer-access-token", IsPreAuthFlow: true, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), ClaimDataID: uuid.NewString(), @@ -4865,11 +4864,11 @@ func TestService_PrepareCredential(t *testing.T) { Return(lo.ToPtr("123"), nil) m.transactionStore.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateCredentialsIssued, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateCredentialsIssued, tx.State) return nil }) - clData := &oidc4ci.ClaimData{ + clData := &issuecredential.ClaimData{ EncryptedData: &dataprotect.EncryptedData{ Encrypted: []byte{0x1, 0x2, 0x3}, EncryptedNonce: []byte{0x0, 0x2}, @@ -4909,12 +4908,12 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Failed to update tx state", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ IssuerToken: "issuer-access-token", IsPreAuthFlow: true, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), ClaimDataID: uuid.NewString(), @@ -4935,12 +4934,12 @@ func TestService_PrepareCredential(t *testing.T) { }) m.transactionStore.EXPECT().Update(gomock.Any(), gomock.Any()). - DoAndReturn(func(ctx context.Context, tx *oidc4ci.Transaction) error { - assert.Equal(t, oidc4ci.TransactionStateCredentialsIssued, tx.State) + DoAndReturn(func(ctx context.Context, tx *issuecredential.Transaction) error { + assert.Equal(t, issuecredential.TransactionStateCredentialsIssued, tx.State) return errors.New("store err") }) - clData := &oidc4ci.ClaimData{ + clData := &issuecredential.ClaimData{ EncryptedData: &dataprotect.EncryptedData{ Encrypted: []byte{0x1, 0x2, 0x3}, EncryptedNonce: []byte{0x0, 0x2}, @@ -4980,7 +4979,7 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Fail to find transaction by op state", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return( + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return( nil, errors.New("get error")) req = &oidc4ci.PrepareCredential{ @@ -4995,9 +4994,9 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Fail to make request to claim endpoint", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -5046,9 +5045,9 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Claim endpoint returned other than 200 OK status code", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -5100,9 +5099,9 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Fail to read response body from claim endpoint when status is not 200 OK", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -5155,9 +5154,9 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Fail to decode claim data", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ - TransactionData: oidc4ci.TransactionData{ - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ + TransactionData: issuecredential.TransactionData{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -5209,11 +5208,11 @@ func TestService_PrepareCredential(t *testing.T) { { name: "Invalid audience claim", setup: func(m *mocks) { - m.transactionStore.EXPECT().Get(gomock.Any(), oidc4ci.TxID("txID")).Return(&oidc4ci.Transaction{ + m.transactionStore.EXPECT().Get(gomock.Any(), issuecredential.TxID("txID")).Return(&issuecredential.Transaction{ ID: "txID", - TransactionData: oidc4ci.TransactionData{ + TransactionData: issuecredential.TransactionData{ IssuerToken: "issuer-access-token", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), OIDCCredentialFormat: vcsverifiable.JwtVCJsonLD, @@ -5274,7 +5273,7 @@ func TestService_PrepareCredential(t *testing.T) { eventService: NewMockEventService(gomock.NewController(t)), crypto: NewMockDataProtector(gomock.NewController(t)), ackService: NewMockAckService(gomock.NewController(t)), - composer: NewMockComposer(gomock.NewController(t)), + composer: NewMockcomposer(gomock.NewController(t)), } tt.setup(m) @@ -5287,7 +5286,10 @@ func TestService_PrepareCredential(t *testing.T) { EventTopic: spi.IssuerEventTopic, DataProtector: m.crypto, AckService: m.ackService, - Composer: m.composer, + PrepareCredential: issuecredential.NewPrepareCredentialService( + &issuecredential.PrepareCredentialServiceConfig{ + Composer: m.composer, + }), }) assert.NoError(t, err) diff --git a/pkg/service/refresh/interfaces.go b/pkg/service/refresh/interfaces.go new file mode 100644 index 000000000..668fb8c72 --- /dev/null +++ b/pkg/service/refresh/interfaces.go @@ -0,0 +1,75 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package refresh + +import ( + "context" + + "github.com/trustbloc/vc-go/verifiable" + + "github.com/trustbloc/vcs/pkg/dataprotect" + "github.com/trustbloc/vcs/pkg/event/spi" + profileapi "github.com/trustbloc/vcs/pkg/profile" + "github.com/trustbloc/vcs/pkg/service/issuecredential" + "github.com/trustbloc/vcs/pkg/service/verifypresentation" +) + +//go:generate mockgen -destination interfaces_mocks_test.go -package refresh_test -source=interfaces.go + +type credentialIssuer interface { + PrepareCredential( + ctx context.Context, + req *issuecredential.PrepareCredentialsRequest, + ) (*verifiable.Credential, error) +} + +type presentationVerifier interface { + VerifyPresentation( + ctx context.Context, + presentation *verifiable.Presentation, + opts *verifypresentation.Options, + profile *profileapi.Verifier, + ) ( + []verifypresentation.PresentationVerificationCheckResult, map[string][]string, error, + ) +} + +type claimDataStore interface { + Create(ctx context.Context, profileTTLSec int32, data *issuecredential.ClaimData) (string, error) + GetAndDelete(ctx context.Context, id string) (*issuecredential.ClaimData, error) +} + +type dataProtector interface { + Encrypt(ctx context.Context, msg []byte) (*dataprotect.EncryptedData, error) + Decrypt(ctx context.Context, encryptedData *dataprotect.EncryptedData) ([]byte, error) +} + +type transactionStore1 interface { + ForceCreate( + ctx context.Context, + profileTransactionDataTTL int32, + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) + + FindByOpState( + ctx context.Context, + opState string, + ) (*issuecredential.Transaction, error) +} + +type IssueCredService interface { + IssueCredential( + ctx context.Context, + credential *verifiable.Credential, + profile *profileapi.Issuer, + opts ...issuecredential.Opts, + ) (*verifiable.Credential, error) +} + +type EventPublisher interface { + Publish(ctx context.Context, topic string, messages ...*spi.Event) error +} diff --git a/pkg/service/refresh/refresh.go b/pkg/service/refresh/refresh.go new file mode 100644 index 000000000..d7e7a7030 --- /dev/null +++ b/pkg/service/refresh/refresh.go @@ -0,0 +1,399 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package refresh + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + "github.com/davecgh/go-spew/spew" + "github.com/google/uuid" + "github.com/samber/lo" + "github.com/trustbloc/logutil-go/pkg/log" + "github.com/trustbloc/vc-go/presexch" + "github.com/trustbloc/vc-go/verifiable" + + "github.com/trustbloc/vcs/internal/claims" + "github.com/trustbloc/vcs/internal/logfields" + "github.com/trustbloc/vcs/pkg/event/spi" + "github.com/trustbloc/vcs/pkg/profile" + "github.com/trustbloc/vcs/pkg/service/issuecredential" +) + +var logger = log.New("refresh-service") + +const ( + ServiceComponent = "RefreshService" +) + +type Config struct { + VcsAPIURL string + TxStore transactionStore1 + ClaimsStore claimDataStore + DataProtector dataProtector + PresentationVerifier presentationVerifier + CredentialIssuer credentialIssuer + IssueCredentialService IssueCredService + EventPublisher EventPublisher + EventTopic string +} + +type Service struct { + cfg *Config +} + +func NewRefreshService(cfg *Config) *Service { + return &Service{ + cfg: cfg, + } +} + +func (s *Service) getEvent( + eventType spi.EventType, + payloadData *Event, + txID string, +) (*spi.Event, error) { + payload, err := json.Marshal(payloadData) + if err != nil { + return nil, err + } + + event := spi.NewEventWithPayload(uuid.NewString(), "RefreshService", eventType, payload) + event.TransactionID = txID + + return event, nil +} + +func (s *Service) publishEvent( + ctx context.Context, + eventType spi.EventType, + payloadData *Event, + txID string, +) error { + event, err := s.getEvent(eventType, payloadData, txID) + if err != nil { + return err + } + + return s.cfg.EventPublisher.Publish(ctx, s.cfg.EventTopic, event) +} + +func (s *Service) tryPublish( + ctx context.Context, + eventType spi.EventType, + payloadData *Event, + txID string, + errSource error, +) { + if errSource != nil { + payloadData.Error = errSource.Error() + payloadData.ErrorComponent = ServiceComponent + } + + if err := s.publishEvent(ctx, eventType, payloadData, txID); err != nil { + logger.Errorc(ctx, fmt.Sprintf("failed to publish event: %s", err), + logfields.WithProfileID(payloadData.ProfileID), + logfields.WithTransactionID(txID), + ) + } +} + +//nolint:funlen +func (s *Service) GetRefreshedCredential( + ctx context.Context, + presentation *verifiable.Presentation, + issuer profile.Issuer, +) (*verifiable.Credential, error) { + resultEvent := &Event{ + WebHook: issuer.WebHook, + ProfileID: issuer.ID, + ProfileVersion: issuer.Version, + OrgID: issuer.OrganizationID, + } + + verifyResult, _, err := s.cfg.PresentationVerifier.VerifyPresentation(ctx, presentation, nil, &profile.Verifier{ + Checks: &profile.VerificationChecks{ + Presentation: &profile.PresentationChecks{ + Proof: true, + }, + Credential: profile.CredentialChecks{ + Proof: true, + CredentialExpiry: true, + Status: true, + LinkedDomain: false, + }, + }, + }) + if err != nil { + s.tryPublish(ctx, spi.CredentialRefreshFailed, resultEvent, "", err) + + return nil, err + } + + if len(verifyResult) > 0 { + err = fmt.Errorf("presentation verification failed. %s", spew.Sdump(verifyResult)) + s.tryPublish(ctx, spi.CredentialRefreshFailed, resultEvent, "", err) + + return nil, err + } + + if len(presentation.Credentials()) == 0 { + err = errors.New("no credentials in presentation") + s.tryPublish(ctx, spi.CredentialRefreshFailed, resultEvent, "", err) + + return nil, err + } + cred := presentation.Credentials()[0] + + template, err := s.findCredentialTemplate(cred.Contents().Types, issuer) + if err != nil { + s.tryPublish(ctx, spi.CredentialRefreshFailed, resultEvent, "", err) + + return nil, err + } + + config, configID := s.findCredConfigSupported(issuer, template.Type) + if config == nil { + err = fmt.Errorf("no credential configuration found for credential type %v", template.Type) + s.tryPublish(ctx, spi.CredentialRefreshFailed, resultEvent, "", err) + + return nil, err + } + + tx, err := s.cfg.TxStore.FindByOpState(ctx, s.getOpState(cred.Contents().ID, issuer.ID)) + if err != nil { + s.tryPublish(ctx, spi.CredentialRefreshFailed, resultEvent, "", err) + + return nil, err + } + + tempClaimData, err := s.cfg.ClaimsStore.GetAndDelete(ctx, tx.CredentialConfiguration[0].ClaimDataID) + if err != nil { + s.tryPublish(ctx, spi.CredentialRefreshFailed, resultEvent, "", err) + + return nil, err + } + + decryptedClaims, decryptErr := claims.DecryptClaims(ctx, tempClaimData, s.cfg.DataProtector) + if decryptErr != nil { + decryptErr = fmt.Errorf("decrypt claims: %w", decryptErr) + s.tryPublish(ctx, spi.CredentialRefreshFailed, resultEvent, "", decryptErr) + + return nil, decryptErr + } + + subj := cred.Contents().Subject + if len(subj) == 0 { + err = errors.New("no subject in credential") + s.tryPublish(ctx, spi.CredentialRefreshFailed, resultEvent, "", err) + + return nil, err + } + + credConfig := tx.CredentialConfiguration[0] + + credConfig.CredentialConfigurationID = configID + credConfig.OIDCCredentialFormat = config.Format + credConfig.CredentialTemplate = template + + refreshServiceEnabled := false + if issuer.VCConfig != nil { + refreshServiceEnabled = true + } + + updatedCred, err := s.cfg.CredentialIssuer.PrepareCredential(ctx, &issuecredential.PrepareCredentialsRequest{ + TxID: string(tx.ID), + ClaimData: decryptedClaims, + IssuerDID: tx.DID, + SubjectDID: subj[0].ID, + CredentialConfiguration: credConfig, + IssuerID: issuer.ID, + IssuerVersion: issuer.Version, + RefreshServiceEnabled: refreshServiceEnabled, + }) + if err != nil { + err = errors.Join(errors.New("failed to prepare credential"), err) + s.tryPublish(ctx, spi.CredentialRefreshFailed, resultEvent, "", err) + + return nil, err + } + + updatedCred, err = s.cfg.IssueCredentialService.IssueCredential(ctx, updatedCred, &issuer, + issuecredential.WithTransactionID(string(tx.ID)), + issuecredential.WithSkipIDPrefix(), + ) + + if err != nil { + s.tryPublish(ctx, spi.CredentialRefreshFailed, resultEvent, string(tx.ID), err) + } else { + s.tryPublish(ctx, spi.CredentialRefreshSuccessful, resultEvent, string(tx.ID), nil) + } + + return updatedCred, err +} + +func (s *Service) findCredentialTemplate( + allTypes []string, + issuer profile.Issuer, +) (*profile.CredentialTemplate, error) { + if len(allTypes) == 0 { + return nil, errors.New("no types in credential") + } + + lastType := allTypes[len(allTypes)-1] + + var template *profile.CredentialTemplate + for _, t := range issuer.CredentialTemplates { + if t.Type == lastType { + template = t + break + } + } + + if template == nil { + return nil, fmt.Errorf("no credential template found for credential type %v", lastType) + } + + return template, nil +} + +func (s *Service) findCredConfigSupported( + issuer profile.Issuer, + lastType string, +) (*profile.CredentialsConfigurationSupported, string) { + var config *profile.CredentialsConfigurationSupported + var configID string + + for k, v := range issuer.CredentialMetaData.CredentialsConfigurationSupported { + if v.CredentialDefinition == nil { + continue + } + + if lo.Contains(v.CredentialDefinition.Type, lastType) { + config = v + configID = k + break + } + } + + return config, configID +} + +func (s *Service) RequestRefreshStatus( + ctx context.Context, + credentialID string, + issuer profile.Issuer, +) (*GetRefreshStateResponse, error) { + tx, _ := s.cfg.TxStore.FindByOpState(ctx, s.getOpState(credentialID, issuer.ID)) + if tx == nil { + return nil, nil //nolint: nilnil + } + + purpose := "The verifier needs to see your existing credentials to verify your identity" + + s.tryPublish(ctx, spi.CredentialRefreshInitiated, &Event{ + WebHook: issuer.WebHook, + ProfileID: issuer.ID, + ProfileVersion: issuer.Version, + OrgID: issuer.OrganizationID, + }, string(tx.ID), nil) + + return &GetRefreshStateResponse{ + RefreshServiceType: ServiceType{ + Type: "VerifiableCredentialRefreshService2021", + }, + VerifiablePresentationRequest: VerifiablePresentationRequest{ + Query: presexch.PresentationDefinition{ + ID: "Query", + Name: "We need to see your existing credentials", + Purpose: purpose, + Frame: nil, + SubmissionRequirements: nil, + InputDescriptors: []*presexch.InputDescriptor{ + { + ID: "DescriptorID", + Name: "We need to see your existing credentials", + Purpose: purpose, + Constraints: &presexch.Constraints{ + Fields: []*presexch.Field{ + { + Path: []string{ + "$.id", + }, + ID: "cred_id", + Purpose: purpose, + Filter: &presexch.Filter{ + Type: lo.ToPtr("string"), + Const: credentialID, + }, + Optional: false, + }, + }, + }, + }, + }, + }, + }, + Challenge: uuid.NewString(), + Domain: s.cfg.VcsAPIURL, + }, nil +} + +func (s *Service) CreateRefreshState( + ctx context.Context, + req *CreateRefreshStateRequest, +) (string, error) { + encrypted, err := claims.EncryptClaims(ctx, req.Claims, s.cfg.DataProtector) + if err != nil { + return "", err + } + + ttl := req.Issuer.DataConfig.OIDC4CITransactionDataTTL + + claimData, err := s.cfg.ClaimsStore.Create(ctx, ttl, &issuecredential.ClaimData{ + EncryptedData: encrypted.EncryptedData, + }) + if err != nil { + return "", errors.Join(errors.New("failed to create claim data"), err) + } + + opState := s.getOpState(req.CredentialID, req.Issuer.ID) + + refreshServiceEnabled := false + if req.Issuer.VCConfig != nil { + refreshServiceEnabled = req.Issuer.VCConfig.RefreshServiceEnabled + } + + tx, err := s.cfg.TxStore.ForceCreate(ctx, ttl, &issuecredential.TransactionData{ + ProfileID: req.Issuer.ID, + ProfileVersion: req.Issuer.Version, + IsPreAuthFlow: true, + OrgID: req.Issuer.OrganizationID, + OpState: opState, + WebHookURL: req.Issuer.WebHook, + RefreshServiceEnabled: refreshServiceEnabled, + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ + { + ClaimDataType: issuecredential.ClaimDataTypeClaims, + ClaimDataID: claimData, + CredentialName: lo.FromPtr(req.CredentialName), + CredentialDescription: lo.FromPtr(req.CredentialDescription), + }, + }, + }) + if err != nil { + return "", errors.Join(errors.New("failed to create transaction"), err) + } + + return string(tx.ID), nil +} + +func (s *Service) getOpState(refreshID string, issuerID string) string { + return fmt.Sprintf("%s-%s", issuerID, refreshID) +} diff --git a/pkg/service/refresh/refresh_test.go b/pkg/service/refresh/refresh_test.go new file mode 100644 index 000000000..e3642d692 --- /dev/null +++ b/pkg/service/refresh/refresh_test.go @@ -0,0 +1,482 @@ +package refresh_test + +import ( + "context" + _ "embed" + "encoding/json" + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/trustbloc/vc-go/verifiable" + + "github.com/trustbloc/vcs/pkg/dataprotect" + vcs "github.com/trustbloc/vcs/pkg/doc/verifiable" + "github.com/trustbloc/vcs/pkg/internal/testutil" + profileapi "github.com/trustbloc/vcs/pkg/profile" + "github.com/trustbloc/vcs/pkg/service/issuecredential" + "github.com/trustbloc/vcs/pkg/service/refresh" + "github.com/trustbloc/vcs/pkg/service/verifypresentation" +) + +var ( + //go:embed testdata/requested_credentials_vp.jsonld + requestedCredentialsVP []byte +) + +func TestCreateRefreshState(t *testing.T) { + t.Run("success", func(t *testing.T) { + dataProtect := NewMockdataProtector(gomock.NewController(t)) + store := NewMockclaimDataStore(gomock.NewController(t)) + txStore := NewMocktransactionStore1(gomock.NewController(t)) + + srv := refresh.NewRefreshService(&refresh.Config{ + DataProtector: dataProtect, + ClaimsStore: store, + TxStore: txStore, + }) + + claims := map[string]interface{}{ + "a": "b", + } + claimsJSON, err := json.Marshal(claims) // nolint + assert.NoError(t, err) + + dataProtect.EXPECT().Encrypt(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, bytes []byte) (*dataprotect.EncryptedData, error) { + assert.EqualValues(t, claimsJSON, bytes) + + return &dataprotect.EncryptedData{ + Encrypted: claimsJSON, + }, nil + }) + + store.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, i int32, data *issuecredential.ClaimData) (string, error) { + assert.EqualValues(t, data.EncryptedData.Encrypted, claimsJSON) + return "some_claims_id", nil + }) + + issuer := profileapi.Issuer{ + ID: "some_issuer", + Version: "2.0", + OrganizationID: "org1", + WebHook: "webhook", + } + + txStore.EXPECT().ForceCreate(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func( + ctx context.Context, + _ int32, + data *issuecredential.TransactionData, + ) (*issuecredential.Transaction, error) { + assert.EqualValues(t, issuer.ID, data.ProfileID) + assert.EqualValues(t, issuer.Version, data.ProfileVersion) + assert.True(t, data.IsPreAuthFlow) + assert.EqualValues(t, issuer.OrganizationID, data.OrgID) + assert.EqualValues(t, "some_issuer-some_cred_id", data.OpState) + assert.EqualValues(t, issuer.WebHook, data.WebHookURL) + + assert.Len(t, data.CredentialConfiguration, 1) + assert.EqualValues(t, issuecredential.ClaimDataTypeClaims, + data.CredentialConfiguration[0].ClaimDataType) + assert.EqualValues(t, "some_claims_id", data.CredentialConfiguration[0].ClaimDataID) + + return &issuecredential.Transaction{ + ID: "some_tx_id", + }, nil + }) + + resp, err := srv.CreateRefreshState(context.TODO(), &refresh.CreateRefreshStateRequest{ + CredentialID: "some_cred_id", + Issuer: issuer, + Claims: claims, + CredentialName: nil, + CredentialDescription: nil, + }) + + assert.NoError(t, err) + assert.EqualValues(t, "some_tx_id", resp) + }) +} + +func TestRequestRefreshState(t *testing.T) { + t.Run("success", func(t *testing.T) { + txStore := NewMocktransactionStore1(gomock.NewController(t)) + + eventSvc := NewMockEventPublisher(gomock.NewController(t)) + eventSvc.EXPECT().Publish(gomock.Any(), "some-topic", gomock.Any()). + Return(errors.New("ignored")) + + srv := refresh.NewRefreshService(&refresh.Config{ + TxStore: txStore, + VcsAPIURL: "https://localhost/api", + EventPublisher: eventSvc, + EventTopic: "some-topic", + }) + + txStore.EXPECT().FindByOpState(gomock.Any(), "some_issuer-some_cred_id"). + Return(&issuecredential.Transaction{}, nil) + + resp, err := srv.RequestRefreshStatus(context.TODO(), "some_cred_id", profileapi.Issuer{ + ID: "some_issuer", + }) + assert.NoError(t, err) + + assert.EqualValues(t, "VerifiableCredentialRefreshService2021", resp.RefreshServiceType.Type) + assert.EqualValues(t, "We need to see your existing credentials", + resp.VerifiablePresentationRequest.Query.Name) + + assert.Len(t, resp.VerifiablePresentationRequest.Query.InputDescriptors, 1) + assert.Len(t, resp.VerifiablePresentationRequest.Query.InputDescriptors[0].Constraints.Fields, 1) + + field := resp.VerifiablePresentationRequest.Query.InputDescriptors[0].Constraints.Fields[0] + + assert.EqualValues(t, []string{ + "$.id", + }, field.Path) + + assert.EqualValues(t, "string", *field.Filter.Type) + assert.EqualValues(t, "some_cred_id", field.Filter.Const) + + assert.EqualValues(t, "https://localhost/api", resp.Domain) + assert.NotEmpty(t, resp.Challenge) + }) + + t.Run("no state", func(t *testing.T) { + txStore := NewMocktransactionStore1(gomock.NewController(t)) + + srv := refresh.NewRefreshService(&refresh.Config{ + TxStore: txStore, + }) + + txStore.EXPECT().FindByOpState(gomock.Any(), "some_issuer-some_cred_id"). + Return(nil, nil) + + resp, err := srv.RequestRefreshStatus(context.TODO(), "some_cred_id", profileapi.Issuer{ + ID: "some_issuer", + }) + assert.NoError(t, err) + assert.Nil(t, resp) + }) +} + +func TestGetRefreshedCredential(t *testing.T) { + t.Run("success", func(t *testing.T) { + verifier := NewMockpresentationVerifier(gomock.NewController(t)) + txStore := NewMocktransactionStore1(gomock.NewController(t)) + claimStore := NewMockclaimDataStore(gomock.NewController(t)) + dataProtector := NewMockdataProtector(gomock.NewController(t)) + credIssuer := NewMockcredentialIssuer(gomock.NewController(t)) + issueCred := NewMockIssueCredService(gomock.NewController(t)) + + signedRequestedCredentialsVP := testutil.SignedVP(t, requestedCredentialsVP, vcs.Ldp) + + eventSvc := NewMockEventPublisher(gomock.NewController(t)) + eventSvc.EXPECT().Publish(gomock.Any(), gomock.Any(), gomock.Any()). + Return(errors.New("ignored")) + + srv := refresh.NewRefreshService(&refresh.Config{ + PresentationVerifier: verifier, + TxStore: txStore, + ClaimsStore: claimStore, + DataProtector: dataProtector, + CredentialIssuer: credIssuer, + IssueCredentialService: issueCred, + EventPublisher: eventSvc, + }) + + targetClaims := map[string]any{ + "a": "b", + } + targetClaimsBytes, err := json.Marshal(targetClaims) + assert.NoError(t, err) + + targetCred := signedRequestedCredentialsVP.Presentation.Credentials()[0] + + credIssuer.EXPECT().PrepareCredential(gomock.Any(), gomock.Any()). + DoAndReturn(func( + ctx context.Context, + request *issuecredential.PrepareCredentialsRequest, + ) (*verifiable.Credential, error) { + assert.EqualValues(t, "some-id", request.TxID) + assert.EqualValues(t, targetClaims, request.ClaimData) + assert.EqualValues(t, "did:example:ebfeb1f712ebc6f1c276e12ec21", request.SubjectDID) + + assert.EqualValues(t, "some-did", request.IssuerDID) + assert.EqualValues(t, "some-issuer", request.IssuerID) + assert.EqualValues(t, "v1", request.IssuerVersion) + + return targetCred, nil + }) + + issueCred.EXPECT().IssueCredential(gomock.Any(), targetCred, gomock.Any(), gomock.Any(), gomock.Any()). + Return(targetCred, nil) + + dataProtector.EXPECT().Decrypt(gomock.Any(), gomock.Any()). + Return(targetClaimsBytes, nil) + + claimStore.EXPECT().GetAndDelete(gomock.Any(), "some-claim-id"). + Return(&issuecredential.ClaimData{}, nil) + + txStore.EXPECT().FindByOpState(gomock.Any(), "some-issuer-http://example.edu/credentials/58473"). + Return(&issuecredential.Transaction{ + ID: "some-id", + TransactionData: issuecredential.TransactionData{ + DID: "some-did", + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ + { + ClaimDataID: "some-claim-id", + }, + }, + }, + }, nil) + + targetIssuer := profileapi.Issuer{ + ID: "some-issuer", + Version: "v1", + CredentialMetaData: &profileapi.CredentialMetaData{ + CredentialsConfigurationSupported: map[string]*profileapi.CredentialsConfigurationSupported{ + "UniversityDegreeCredential": { + CredentialDefinition: &profileapi.CredentialDefinition{ + Type: []string{"UniversityDegreeCredential"}, + }, + }, + }, + }, + CredentialTemplates: []*profileapi.CredentialTemplate{ + { + Type: "UniversityDegreeCredential", + }, + }, + } + + verifier.EXPECT().VerifyPresentation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func( + ctx context.Context, + presentation *verifiable.Presentation, + options *verifypresentation.Options, + verifier *profileapi.Verifier, + ) ([]verifypresentation.PresentationVerificationCheckResult, map[string][]string, error) { + assert.True(t, verifier.Checks.Presentation.Proof) + + assert.True(t, verifier.Checks.Credential.Proof) + assert.True(t, verifier.Checks.Credential.CredentialExpiry) + assert.True(t, verifier.Checks.Credential.Status) + + return nil, nil, nil + }) + + cred, err := srv.GetRefreshedCredential(context.TODO(), signedRequestedCredentialsVP.Presentation, targetIssuer) + assert.NoError(t, err) + assert.Equal(t, cred, targetCred) + }) + + t.Run("issue err", func(t *testing.T) { + verifier := NewMockpresentationVerifier(gomock.NewController(t)) + txStore := NewMocktransactionStore1(gomock.NewController(t)) + claimStore := NewMockclaimDataStore(gomock.NewController(t)) + dataProtector := NewMockdataProtector(gomock.NewController(t)) + credIssuer := NewMockcredentialIssuer(gomock.NewController(t)) + issueCred := NewMockIssueCredService(gomock.NewController(t)) + + signedRequestedCredentialsVP := testutil.SignedVP(t, requestedCredentialsVP, vcs.Ldp) + + eventSvc := NewMockEventPublisher(gomock.NewController(t)) + eventSvc.EXPECT().Publish(gomock.Any(), gomock.Any(), gomock.Any()). + Return(errors.New("ignored")) + + srv := refresh.NewRefreshService(&refresh.Config{ + PresentationVerifier: verifier, + TxStore: txStore, + ClaimsStore: claimStore, + DataProtector: dataProtector, + CredentialIssuer: credIssuer, + IssueCredentialService: issueCred, + EventPublisher: eventSvc, + }) + + targetClaims := map[string]any{ + "a": "b", + } + targetClaimsBytes, err := json.Marshal(targetClaims) + assert.NoError(t, err) + + targetCred := signedRequestedCredentialsVP.Presentation.Credentials()[0] + + credIssuer.EXPECT().PrepareCredential(gomock.Any(), gomock.Any()). + DoAndReturn(func( + ctx context.Context, + request *issuecredential.PrepareCredentialsRequest, + ) (*verifiable.Credential, error) { + assert.EqualValues(t, "some-id", request.TxID) + assert.EqualValues(t, targetClaims, request.ClaimData) + assert.EqualValues(t, "did:example:ebfeb1f712ebc6f1c276e12ec21", request.SubjectDID) + + assert.EqualValues(t, "some-did", request.IssuerDID) + assert.EqualValues(t, "some-issuer", request.IssuerID) + assert.EqualValues(t, "v1", request.IssuerVersion) + + return targetCred, nil + }) + + issueCred.EXPECT().IssueCredential(gomock.Any(), targetCred, gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil, errors.New("failed to issue credential")) + + dataProtector.EXPECT().Decrypt(gomock.Any(), gomock.Any()). + Return(targetClaimsBytes, nil) + + claimStore.EXPECT().GetAndDelete(gomock.Any(), "some-claim-id"). + Return(&issuecredential.ClaimData{}, nil) + + txStore.EXPECT().FindByOpState(gomock.Any(), "some-issuer-http://example.edu/credentials/58473"). + Return(&issuecredential.Transaction{ + ID: "some-id", + TransactionData: issuecredential.TransactionData{ + DID: "some-did", + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ + { + ClaimDataID: "some-claim-id", + }, + }, + }, + }, nil) + + targetIssuer := profileapi.Issuer{ + ID: "some-issuer", + Version: "v1", + CredentialMetaData: &profileapi.CredentialMetaData{ + CredentialsConfigurationSupported: map[string]*profileapi.CredentialsConfigurationSupported{ + "UniversityDegreeCredential": { + CredentialDefinition: &profileapi.CredentialDefinition{ + Type: []string{"UniversityDegreeCredential"}, + }, + }, + }, + }, + CredentialTemplates: []*profileapi.CredentialTemplate{ + { + Type: "UniversityDegreeCredential", + }, + }, + } + + verifier.EXPECT().VerifyPresentation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func( + ctx context.Context, + presentation *verifiable.Presentation, + options *verifypresentation.Options, + verifier *profileapi.Verifier, + ) ([]verifypresentation.PresentationVerificationCheckResult, map[string][]string, error) { + assert.True(t, verifier.Checks.Presentation.Proof) + + assert.True(t, verifier.Checks.Credential.Proof) + assert.True(t, verifier.Checks.Credential.CredentialExpiry) + assert.True(t, verifier.Checks.Credential.Status) + + return nil, nil, nil + }) + + cred, err := srv.GetRefreshedCredential(context.TODO(), signedRequestedCredentialsVP.Presentation, targetIssuer) + assert.ErrorContains(t, err, "failed to issue credential") + assert.Nil(t, cred) + }) + + t.Run("prepare err", func(t *testing.T) { + verifier := NewMockpresentationVerifier(gomock.NewController(t)) + txStore := NewMocktransactionStore1(gomock.NewController(t)) + claimStore := NewMockclaimDataStore(gomock.NewController(t)) + dataProtector := NewMockdataProtector(gomock.NewController(t)) + credIssuer := NewMockcredentialIssuer(gomock.NewController(t)) + issueCred := NewMockIssueCredService(gomock.NewController(t)) + + signedRequestedCredentialsVP := testutil.SignedVP(t, requestedCredentialsVP, vcs.Ldp) + + eventSvc := NewMockEventPublisher(gomock.NewController(t)) + eventSvc.EXPECT().Publish(gomock.Any(), gomock.Any(), gomock.Any()). + Return(errors.New("ignored")) + + srv := refresh.NewRefreshService(&refresh.Config{ + PresentationVerifier: verifier, + TxStore: txStore, + ClaimsStore: claimStore, + DataProtector: dataProtector, + CredentialIssuer: credIssuer, + IssueCredentialService: issueCred, + EventPublisher: eventSvc, + }) + + targetClaims := map[string]any{ + "a": "b", + } + targetClaimsBytes, err := json.Marshal(targetClaims) + assert.NoError(t, err) + + credIssuer.EXPECT().PrepareCredential(gomock.Any(), gomock.Any()). + DoAndReturn(func( + ctx context.Context, + request *issuecredential.PrepareCredentialsRequest, + ) (*verifiable.Credential, error) { + return nil, errors.New("failed to prepare credential") + }) + + dataProtector.EXPECT().Decrypt(gomock.Any(), gomock.Any()). + Return(targetClaimsBytes, nil) + + claimStore.EXPECT().GetAndDelete(gomock.Any(), "some-claim-id"). + Return(&issuecredential.ClaimData{}, nil) + + txStore.EXPECT().FindByOpState(gomock.Any(), "some-issuer-http://example.edu/credentials/58473"). + Return(&issuecredential.Transaction{ + ID: "some-id", + TransactionData: issuecredential.TransactionData{ + DID: "some-did", + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ + { + ClaimDataID: "some-claim-id", + }, + }, + }, + }, nil) + + targetIssuer := profileapi.Issuer{ + ID: "some-issuer", + Version: "v1", + CredentialMetaData: &profileapi.CredentialMetaData{ + CredentialsConfigurationSupported: map[string]*profileapi.CredentialsConfigurationSupported{ + "UniversityDegreeCredential": { + CredentialDefinition: &profileapi.CredentialDefinition{ + Type: []string{"UniversityDegreeCredential"}, + }, + }, + }, + }, + CredentialTemplates: []*profileapi.CredentialTemplate{ + { + Type: "UniversityDegreeCredential", + }, + }, + } + + verifier.EXPECT().VerifyPresentation(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func( + ctx context.Context, + presentation *verifiable.Presentation, + options *verifypresentation.Options, + verifier *profileapi.Verifier, + ) ([]verifypresentation.PresentationVerificationCheckResult, map[string][]string, error) { + assert.True(t, verifier.Checks.Presentation.Proof) + + assert.True(t, verifier.Checks.Credential.Proof) + assert.True(t, verifier.Checks.Credential.CredentialExpiry) + assert.True(t, verifier.Checks.Credential.Status) + + return nil, nil, nil + }) + + cred, err := srv.GetRefreshedCredential(context.TODO(), signedRequestedCredentialsVP.Presentation, targetIssuer) + assert.ErrorContains(t, err, "failed to prepare credential") + assert.Nil(t, cred) + }) +} diff --git a/pkg/service/refresh/testdata/requested_credentials_vp.jsonld b/pkg/service/refresh/testdata/requested_credentials_vp.jsonld new file mode 100644 index 000000000..34fdb95e2 --- /dev/null +++ b/pkg/service/refresh/testdata/requested_credentials_vp.jsonld @@ -0,0 +1,42 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + "https://trustbloc.github.io/context/vc/examples-v1.jsonld", + "https://w3id.org/vc/status-list/2021/v1", + "https://w3id.org/security/suites/jws-2020/v1" + ], + "id": "urn:uuid:3978344f-8596-4c3a-a978-8fcaba3903c5", + "type": "VerifiablePresentation", + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "id": "http://example.edu/credentials/58473", + "type": [ + "VerifiableCredential", + "UniversityDegreeCredential" + ], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "alumniOf": "Example University" + }, + "credentialStatus": { + "id": "urn:uuid:71d57ab4-9e3a-4267-a3fd-e64d661c0df3", + "statusListCredential": "https://test.com/2.json", + "statusListIndex": "17", + "statusPurpose": "revocation", + "type": "StatusList2021Entry" + }, + "proof": { + "type": "RsaSignature2018" + } + } + ], + "holder": "did:trustblock:abc" +} diff --git a/pkg/service/refresh/types.go b/pkg/service/refresh/types.go new file mode 100644 index 000000000..60596e653 --- /dev/null +++ b/pkg/service/refresh/types.go @@ -0,0 +1,52 @@ +package refresh + +import ( + "github.com/trustbloc/vc-go/presexch" + + "github.com/trustbloc/vcs/pkg/profile" +) + +type CreateRefreshStateRequest struct { + CredentialID string + Issuer profile.Issuer + Claims map[string]interface{} + + CredentialName *string + CredentialDescription *string +} + +type GetRefreshStateResponse struct { + VerifiablePresentationRequest VerifiablePresentationRequest `json:"verifiablePresentationRequest"` + Challenge string `json:"challenge"` + Domain string `json:"domain"` + RefreshServiceType ServiceType `json:"refreshServiceType"` +} + +type Event struct { + WebHook string `json:"webHook,omitempty"` + ProfileID string `json:"profileID,omitempty"` + ProfileVersion string `json:"profileVersion,omitempty"` + OrgID string `json:"orgID,omitempty"` + + Error string `json:"error,omitempty"` + ErrorCode string `json:"errorCode,omitempty"` + ErrorComponent string `json:"errorComponent,omitempty"` +} + +type ServiceType struct { + Type string `json:"type"` + URL string `json:"url"` +} + +type Interact struct { + Interact []InteractService `json:"interact"` +} + +type InteractService struct { + Type string `json:"type"` + ServiceEndpoint string `json:"serviceEndpoint"` +} + +type VerifiablePresentationRequest struct { + Query presexch.PresentationDefinition `json:"query"` +} diff --git a/pkg/service/verifypresentation/verifypresentation_service.go b/pkg/service/verifypresentation/verifypresentation_service.go index 310252173..b49be4b6d 100644 --- a/pkg/service/verifypresentation/verifypresentation_service.go +++ b/pkg/service/verifypresentation/verifypresentation_service.go @@ -218,6 +218,7 @@ func (s *Service) checkCredentialStrict( if err = validator.ValidateJSONLDMap(credMap, validator.WithDocumentLoader(s.documentLoader), validator.WithStrictValidation(true), + validator.WithJSONLDIncludeDetailedStructureDiffOnError(), ); err != nil { return claimKeysDict, err } diff --git a/pkg/storage/mongodb/oidc4ciclaimdatastore/claim_data_store.go b/pkg/storage/mongodb/oidc4ciclaimdatastore/claim_data_store.go index 2baa7d1b9..ec7d13755 100644 --- a/pkg/storage/mongodb/oidc4ciclaimdatastore/claim_data_store.go +++ b/pkg/storage/mongodb/oidc4ciclaimdatastore/claim_data_store.go @@ -18,7 +18,7 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" "github.com/trustbloc/vcs/pkg/restapi/resterr" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/storage/mongodb" ) @@ -27,9 +27,9 @@ const ( ) type mongoDocument struct { - ID primitive.ObjectID `bson:"_id,omitempty"` - ExpireAt time.Time `bson:"expire_at"` - ClaimData oidc4ci.ClaimData `bson:"claim_data"` + ID primitive.ObjectID `bson:"_id,omitempty"` + ExpireAt time.Time `bson:"expire_at"` + ClaimData issuecredential.ClaimData `bson:"claim_data"` } // Store stores claim data with expiration. @@ -66,7 +66,11 @@ func (s *Store) migrate(ctx context.Context) error { return nil } -func (s *Store) Create(ctx context.Context, profileClaimDataTTL int32, data *oidc4ci.ClaimData) (string, error) { +func (s *Store) Create( + ctx context.Context, + profileClaimDataTTL int32, + data *issuecredential.ClaimData, +) (string, error) { claimDataTTL := s.defaultTTL if profileClaimDataTTL > 0 { claimDataTTL = profileClaimDataTTL @@ -85,7 +89,7 @@ func (s *Store) Create(ctx context.Context, profileClaimDataTTL int32, data *oid return result.InsertedID.(primitive.ObjectID).Hex(), nil } -func (s *Store) GetAndDelete(ctx context.Context, claimDataID string) (*oidc4ci.ClaimData, error) { +func (s *Store) GetAndDelete(ctx context.Context, claimDataID string) (*issuecredential.ClaimData, error) { id, err := primitive.ObjectIDFromHex(claimDataID) if err != nil { return nil, fmt.Errorf("parse id %s: %w", claimDataID, err) diff --git a/pkg/storage/mongodb/oidc4ciclaimdatastore/claim_data_store_test.go b/pkg/storage/mongodb/oidc4ciclaimdatastore/claim_data_store_test.go index ac3e2a113..7aeb2ac33 100644 --- a/pkg/storage/mongodb/oidc4ciclaimdatastore/claim_data_store_test.go +++ b/pkg/storage/mongodb/oidc4ciclaimdatastore/claim_data_store_test.go @@ -26,7 +26,7 @@ import ( "github.com/trustbloc/vcs/pkg/dataprotect" "github.com/trustbloc/vcs/pkg/restapi/resterr" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/storage/mongodb" ) @@ -51,7 +51,7 @@ func TestStore(t *testing.T) { assert.NoError(t, createErr) t.Run("test create and get", func(t *testing.T) { - claims := &oidc4ci.ClaimData{ + claims := &issuecredential.ClaimData{ EncryptedData: &dataprotect.EncryptedData{ Encrypted: []byte{0x1}, EncryptedNonce: []byte{0x2}, @@ -84,7 +84,7 @@ func TestStore(t *testing.T) { storeExpired, err := New(context.Background(), client, 0) assert.NoError(t, err) - claims := &oidc4ci.ClaimData{ + claims := &issuecredential.ClaimData{ EncryptedData: &dataprotect.EncryptedData{ Encrypted: []byte{0x1}, EncryptedNonce: []byte{0x2}, @@ -103,7 +103,7 @@ func TestStore(t *testing.T) { storeExpired, err := New(context.Background(), client, 1000) assert.NoError(t, err) - claims := &oidc4ci.ClaimData{ + claims := &issuecredential.ClaimData{ EncryptedData: &dataprotect.EncryptedData{ Encrypted: []byte{0x1}, EncryptedNonce: []byte{0x2}, diff --git a/pkg/storage/mongodb/oidc4cinoncestore/oidc4vc_store.go b/pkg/storage/mongodb/oidc4cinoncestore/oidc4vc_store.go index 704bae7db..8b4fb8039 100644 --- a/pkg/storage/mongodb/oidc4cinoncestore/oidc4vc_store.go +++ b/pkg/storage/mongodb/oidc4cinoncestore/oidc4vc_store.go @@ -17,7 +17,7 @@ import ( "go.mongodb.org/mongo-driver/mongo/options" "github.com/trustbloc/vcs/pkg/restapi/resterr" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/storage/mongodb" ) @@ -44,12 +44,13 @@ type mongoDocument struct { IssuerToken string IsPreAuthFlow bool PreAuthCode string - Status oidc4ci.TransactionState + Status issuecredential.TransactionState WebHookURL string DID string UserPin string WalletInitiatedIssuance bool - CredentialConfiguration []*oidc4ci.TxCredentialConfiguration + CredentialConfiguration []*issuecredential.TxCredentialConfiguration + RefreshServiceEnabled bool } // Store stores oidc transactions in mongo. @@ -94,11 +95,28 @@ func (s *Store) migrate(ctx context.Context) error { return nil } +func (s *Store) ForceCreate( + ctx context.Context, + profileTransactionDataTTL int32, + data *issuecredential.TransactionData, +) (*issuecredential.Transaction, error) { + return s.createInternal(ctx, profileTransactionDataTTL, data, true) +} + func (s *Store) Create( ctx context.Context, profileTransactionDataTTL int32, - data *oidc4ci.TransactionData, -) (*oidc4ci.Transaction, error) { + data *issuecredential.TransactionData, +) (*issuecredential.Transaction, error) { + return s.createInternal(ctx, profileTransactionDataTTL, data, false) +} + +func (s *Store) createInternal( + ctx context.Context, + profileTransactionDataTTL int32, + data *issuecredential.TransactionData, + _ bool, +) (*issuecredential.Transaction, error) { obj := s.mapTransactionDataToMongoDocument(data) if profileTransactionDataTTL != 0 { @@ -108,7 +126,6 @@ func (s *Store) Create( collection := s.mongoClient.Database().Collection(collectionName) result, err := collection.InsertOne(ctx, obj) - if err != nil && mongo.IsDuplicateKeyError(err) { return nil, resterr.NewCustomError(resterr.DataNotFound, resterr.ErrDataNotFound) } @@ -119,16 +136,16 @@ func (s *Store) Create( insertedID := result.InsertedID.(primitive.ObjectID) //nolint: errcheck - return &oidc4ci.Transaction{ - ID: oidc4ci.TxID(insertedID.Hex()), + return &issuecredential.Transaction{ + ID: issuecredential.TxID(insertedID.Hex()), TransactionData: *data, }, nil } func (s *Store) Get( ctx context.Context, - txID oidc4ci.TxID, -) (*oidc4ci.Transaction, error) { + txID issuecredential.TxID, +) (*issuecredential.Transaction, error) { id, err := primitive.ObjectIDFromHex(string(txID)) if err != nil { return nil, err @@ -137,11 +154,11 @@ func (s *Store) Get( return s.findOne(ctx, bson.M{"_id": id}) } -func (s *Store) FindByOpState(ctx context.Context, opState string) (*oidc4ci.Transaction, error) { +func (s *Store) FindByOpState(ctx context.Context, opState string) (*issuecredential.Transaction, error) { return s.findOne(ctx, bson.M{"opState": opState}) } -func (s *Store) findOne(ctx context.Context, filter interface{}) (*oidc4ci.Transaction, error) { +func (s *Store) findOne(ctx context.Context, filter interface{}) (*issuecredential.Transaction, error) { collection := s.mongoClient.Database().Collection(collectionName) var doc mongoDocument @@ -162,7 +179,7 @@ func (s *Store) findOne(ctx context.Context, filter interface{}) (*oidc4ci.Trans return mapDocumentToTransaction(&doc), nil } -func (s *Store) Update(ctx context.Context, tx *oidc4ci.Transaction) error { +func (s *Store) Update(ctx context.Context, tx *issuecredential.Transaction) error { collection := s.mongoClient.Database().Collection(collectionName) id, err := primitive.ObjectIDFromHex(string(tx.ID)) @@ -180,7 +197,7 @@ func (s *Store) Update(ctx context.Context, tx *oidc4ci.Transaction) error { return err } -func (s *Store) mapTransactionDataToMongoDocument(data *oidc4ci.TransactionData) *mongoDocument { +func (s *Store) mapTransactionDataToMongoDocument(data *issuecredential.TransactionData) *mongoDocument { return &mongoDocument{ ID: primitive.ObjectID{}, ExpireAt: time.Now().UTC().Add(s.defaultTTL), @@ -205,13 +222,14 @@ func (s *Store) mapTransactionDataToMongoDocument(data *oidc4ci.TransactionData) DID: data.DID, WalletInitiatedIssuance: data.WalletInitiatedIssuance, CredentialConfiguration: data.CredentialConfiguration, + RefreshServiceEnabled: data.RefreshServiceEnabled, } } -func mapDocumentToTransaction(doc *mongoDocument) *oidc4ci.Transaction { - return &oidc4ci.Transaction{ - ID: oidc4ci.TxID(doc.ID.Hex()), - TransactionData: oidc4ci.TransactionData{ +func mapDocumentToTransaction(doc *mongoDocument) *issuecredential.Transaction { + return &issuecredential.Transaction{ + ID: issuecredential.TxID(doc.ID.Hex()), + TransactionData: issuecredential.TransactionData{ ProfileID: doc.ProfileID, ProfileVersion: doc.ProfileVersion, OrgID: doc.OrgID, @@ -230,6 +248,7 @@ func mapDocumentToTransaction(doc *mongoDocument) *oidc4ci.Transaction { PreAuthCode: doc.PreAuthCode, State: doc.Status, WebHookURL: doc.WebHookURL, + RefreshServiceEnabled: doc.RefreshServiceEnabled, DID: doc.DID, WalletInitiatedIssuance: doc.WalletInitiatedIssuance, CredentialConfiguration: doc.CredentialConfiguration, diff --git a/pkg/storage/mongodb/oidc4cinoncestore/oidc4vc_store_test.go b/pkg/storage/mongodb/oidc4cinoncestore/oidc4vc_store_test.go index 4712ce7a3..ddc1196c7 100644 --- a/pkg/storage/mongodb/oidc4cinoncestore/oidc4vc_store_test.go +++ b/pkg/storage/mongodb/oidc4cinoncestore/oidc4vc_store_test.go @@ -28,7 +28,7 @@ import ( vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/restapi/resterr" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/storage/mongodb" ) @@ -56,7 +56,7 @@ func TestStore(t *testing.T) { t.Run("try insert duplicate op_state", func(t *testing.T) { id := uuid.New().String() - toInsert := &oidc4ci.TransactionData{ + toInsert := &issuecredential.TransactionData{ OpState: id, } @@ -72,7 +72,7 @@ func TestStore(t *testing.T) { t.Run("test default expiration", func(t *testing.T) { id := uuid.New().String() - toInsert := &oidc4ci.TransactionData{ + toInsert := &issuecredential.TransactionData{ OpState: id, } @@ -91,7 +91,7 @@ func TestStore(t *testing.T) { t.Run("test profile expiration", func(t *testing.T) { id := uuid.New().String() - toInsert := &oidc4ci.TransactionData{ + toInsert := &issuecredential.TransactionData{ OpState: id, } @@ -112,7 +112,7 @@ func TestStore(t *testing.T) { t.Run("test insert and find", func(t *testing.T) { id := uuid.New().String() - toInsert := &oidc4ci.TransactionData{ + toInsert := &issuecredential.TransactionData{ ProfileID: "profileID", AuthorizationEndpoint: "authEndpoint", PushedAuthorizationRequestEndpoint: "pushedAuth", @@ -129,7 +129,7 @@ func TestStore(t *testing.T) { WebHookURL: "http://remote-url", DID: "did:123", WalletInitiatedIssuance: true, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), CredentialTemplate: &profileapi.CredentialTemplate{ @@ -143,17 +143,17 @@ func TestStore(t *testing.T) { ClaimDataID: uuid.NewString(), CredentialName: uuid.NewString(), CredentialDescription: uuid.NewString(), - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ + AuthorizationDetails: &issuecredential.AuthorizationDetails{ Type: "321", CredentialConfigurationID: "CredentialConfigurationID", - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"fdsfsd"}, }, Format: "vxcxzcz", Locations: []string{"loc1", "loc2"}, }, CredentialConfigurationID: "CredentialConfigurationID", - CredentialComposeConfiguration: &oidc4ci.CredentialComposeConfiguration{ + CredentialComposeConfiguration: &issuecredential.CredentialComposeConfiguration{ IDTemplate: uuid.NewString(), OverrideIssuer: true, }, @@ -161,7 +161,7 @@ func TestStore(t *testing.T) { }, } - var resp *oidc4ci.Transaction + var resp *issuecredential.Transaction resp, err = store.Create(context.Background(), 0, toInsert) assert.NoError(t, err) @@ -200,16 +200,16 @@ func TestStore(t *testing.T) { t.Run("test update", func(t *testing.T) { id := uuid.NewString() - toInsert := &oidc4ci.TransactionData{ + toInsert := &issuecredential.TransactionData{ GrantType: "342", ResponseType: "123", Scope: []string{"213", "321"}, OpState: id, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), ClaimEndpoint: "432", - AuthorizationDetails: &oidc4ci.AuthorizationDetails{Type: "321"}, + AuthorizationDetails: &issuecredential.AuthorizationDetails{Type: "321"}, CredentialConfigurationID: "CredentialConfigurationID", }, }, @@ -229,10 +229,10 @@ func TestStore(t *testing.T) { CredentialSubject: []byte(`{"sub_1" : "abcd"}`), } - ad := &oidc4ci.AuthorizationDetails{ + ad := &issuecredential.AuthorizationDetails{ Type: "321", CredentialConfigurationID: "CredentialConfigurationID", - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"fdsfsd"}, }, Format: "vxcxzcz", @@ -286,7 +286,7 @@ func TestWithTimeouts(t *testing.T) { defer cancel() t.Run("Create timeout", func(t *testing.T) { - resp, err := store.Create(ctx, 0, &oidc4ci.TransactionData{}) + resp, err := store.Create(ctx, 0, &issuecredential.TransactionData{}) assert.Empty(t, resp) assert.ErrorContains(t, err, "context deadline exceeded") @@ -300,7 +300,7 @@ func TestWithTimeouts(t *testing.T) { }) t.Run("Update InvalidKey", func(t *testing.T) { - err := store.Update(context.TODO(), &oidc4ci.Transaction{ID: "1"}) + err := store.Update(context.TODO(), &issuecredential.Transaction{ID: "1"}) assert.ErrorContains(t, err, "the provided hex string is not a valid ObjectID") }) } diff --git a/pkg/storage/mongodb/util.go b/pkg/storage/mongodb/util.go index d4b6ead19..2cbac9099 100644 --- a/pkg/storage/mongodb/util.go +++ b/pkg/storage/mongodb/util.go @@ -6,22 +6,14 @@ SPDX-License-Identifier: Apache-2.0 package mongodb -import "encoding/json" +import ( + "encoding/json" -func StructureToMap(obj interface{}) (map[string]interface{}, error) { - b, err := json.Marshal(obj) - if err != nil { - return nil, err - } - - var result map[string]interface{} + "github.com/trustbloc/vcs/internal/utils" +) - err = json.Unmarshal(b, &result) - if err != nil { - return nil, err - } - - return result, nil +func StructureToMap(obj interface{}) (map[string]interface{}, error) { + return utils.StructureToMap(obj) } func MapToStructure(in map[string]interface{}, out interface{}) error { diff --git a/pkg/storage/redis/oidc4ciclaimdatastore/claim_data_store.go b/pkg/storage/redis/oidc4ciclaimdatastore/claim_data_store.go index 5d4e9697d..4721a4e8d 100644 --- a/pkg/storage/redis/oidc4ciclaimdatastore/claim_data_store.go +++ b/pkg/storage/redis/oidc4ciclaimdatastore/claim_data_store.go @@ -17,7 +17,7 @@ import ( redisapi "github.com/redis/go-redis/v9" "github.com/trustbloc/vcs/pkg/restapi/resterr" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/storage/redis" ) @@ -39,7 +39,7 @@ func New(redisClient *redis.Client, ttlSec int32) *Store { } } -func (s *Store) Create(ctx context.Context, profileTTLSec int32, data *oidc4ci.ClaimData) (string, error) { +func (s *Store) Create(ctx context.Context, profileTTLSec int32, data *issuecredential.ClaimData) (string, error) { expireAt := s.defaultTTL if profileTTLSec > 0 { expireAt = time.Duration(profileTTLSec) * time.Second @@ -55,7 +55,7 @@ func (s *Store) Create(ctx context.Context, profileTTLSec int32, data *oidc4ci.C return key, s.redisClient.API().Set(ctx, key, doc, expireAt).Err() } -func (s *Store) GetAndDelete(ctx context.Context, claimDataID string) (*oidc4ci.ClaimData, error) { +func (s *Store) GetAndDelete(ctx context.Context, claimDataID string) (*issuecredential.ClaimData, error) { clientAPI := s.redisClient.API() b, err := clientAPI.Get(ctx, claimDataID).Bytes() if err != nil { diff --git a/pkg/storage/redis/oidc4ciclaimdatastore/claim_data_store_test.go b/pkg/storage/redis/oidc4ciclaimdatastore/claim_data_store_test.go index 2ccc25be5..b9e9929ca 100644 --- a/pkg/storage/redis/oidc4ciclaimdatastore/claim_data_store_test.go +++ b/pkg/storage/redis/oidc4ciclaimdatastore/claim_data_store_test.go @@ -22,7 +22,7 @@ import ( "github.com/trustbloc/vcs/pkg/dataprotect" "github.com/trustbloc/vcs/pkg/restapi/resterr" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/storage/redis" ) @@ -45,7 +45,7 @@ func TestStore(t *testing.T) { store := New(client, defaultClaimsTTL) t.Run("test create and get", func(t *testing.T) { - claims := &oidc4ci.ClaimData{ + claims := &issuecredential.ClaimData{ EncryptedData: &dataprotect.EncryptedData{ Encrypted: []byte{0x1}, EncryptedNonce: []byte{0x2}, @@ -76,7 +76,7 @@ func TestStore(t *testing.T) { t.Run("test default expiration", func(t *testing.T) { storeExpired := New(client, 0) - claims := &oidc4ci.ClaimData{ + claims := &issuecredential.ClaimData{ EncryptedData: &dataprotect.EncryptedData{ Encrypted: []byte{0x1}, EncryptedNonce: []byte{0x2}, @@ -94,7 +94,7 @@ func TestStore(t *testing.T) { t.Run("test profile expiration", func(t *testing.T) { storeExpired := New(client, 1000) - claims := &oidc4ci.ClaimData{ + claims := &issuecredential.ClaimData{ EncryptedData: &dataprotect.EncryptedData{ Encrypted: []byte{0x1}, EncryptedNonce: []byte{0x2}, diff --git a/pkg/storage/redis/oidc4ciclaimdatastore/doc.go b/pkg/storage/redis/oidc4ciclaimdatastore/doc.go index 28d65e524..7ea339701 100644 --- a/pkg/storage/redis/oidc4ciclaimdatastore/doc.go +++ b/pkg/storage/redis/oidc4ciclaimdatastore/doc.go @@ -10,12 +10,12 @@ import ( "encoding/json" "time" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) type redisDocument struct { - ClaimData oidc4ci.ClaimData `json:"claimData"` - ExpireAt time.Time `json:"expireAt,omitempty"` + ClaimData issuecredential.ClaimData `json:"claimData"` + ExpireAt time.Time `json:"expireAt,omitempty"` } func (d *redisDocument) MarshalBinary() ([]byte, error) { diff --git a/pkg/storage/redis/oidc4cinoncestore/doc.go b/pkg/storage/redis/oidc4cinoncestore/doc.go index 4ba579df9..2c39002ab 100644 --- a/pkg/storage/redis/oidc4cinoncestore/doc.go +++ b/pkg/storage/redis/oidc4cinoncestore/doc.go @@ -10,13 +10,13 @@ import ( "encoding/json" "time" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" ) type redisDocument struct { ID string ExpireAt time.Time - TransactionData *oidc4ci.TransactionData + TransactionData *issuecredential.TransactionData } func (d *redisDocument) MarshalBinary() ([]byte, error) { diff --git a/pkg/storage/redis/oidc4cinoncestore/oidc4vc_store.go b/pkg/storage/redis/oidc4cinoncestore/oidc4vc_store.go index 1cef263ac..cde4f40e1 100644 --- a/pkg/storage/redis/oidc4cinoncestore/oidc4vc_store.go +++ b/pkg/storage/redis/oidc4cinoncestore/oidc4vc_store.go @@ -17,7 +17,7 @@ import ( redisapi "github.com/redis/go-redis/v9" "github.com/trustbloc/vcs/pkg/restapi/resterr" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/storage/redis" ) @@ -40,20 +40,39 @@ func New(redisClient *redis.Client, ttlSec int32) *Store { } } +func (s *Store) ForceCreate( + ctx context.Context, + profileTransactionDataTTL int32, + transactionData *issuecredential.TransactionData, +) (*issuecredential.Transaction, error) { + return s.createInternal(ctx, profileTransactionDataTTL, transactionData, true) +} + func (s *Store) Create( ctx context.Context, profileTransactionDataTTL int32, - transactionData *oidc4ci.TransactionData, -) (*oidc4ci.Transaction, error) { + transactionData *issuecredential.TransactionData, +) (*issuecredential.Transaction, error) { + return s.createInternal(ctx, profileTransactionDataTTL, transactionData, false) +} + +func (s *Store) createInternal( + ctx context.Context, + profileTransactionDataTTL int32, + transactionData *issuecredential.TransactionData, + force bool, +) (*issuecredential.Transaction, error) { // Check opStatueBasedKey key existence. opStatueBasedKey := resolveRedisKey(keyPrefix, transactionData.OpState) - b, err := s.redisClient.API().Exists(ctx, opStatueBasedKey).Result() - if err != nil { - return nil, fmt.Errorf("exist: %w", err) - } + if !force { + b, err := s.redisClient.API().Exists(ctx, opStatueBasedKey).Result() + if err != nil { + return nil, fmt.Errorf("exist: %w", err) + } - if b > 0 { - return nil, resterr.ErrDataNotFound + if b > 0 { + return nil, resterr.ErrDataNotFound + } } ttl := s.defaultTTL @@ -80,20 +99,20 @@ func (s *Store) Create( // Set intermediateKey that points to redisDocument pipeline.Set(ctx, intermediateKey, doc, ttl) - if _, err = pipeline.Exec(ctx); err != nil { + if _, err := pipeline.Exec(ctx); err != nil { return nil, fmt.Errorf("transactionData create: %w", err) } - return &oidc4ci.Transaction{ - ID: oidc4ci.TxID(transactionID), + return &issuecredential.Transaction{ + ID: issuecredential.TxID(transactionID), TransactionData: *transactionData, }, nil } func (s *Store) Get( ctx context.Context, - txID oidc4ci.TxID, -) (*oidc4ci.Transaction, error) { + txID issuecredential.TxID, +) (*issuecredential.Transaction, error) { transactionIDBasedKey := resolveRedisKey(keyPrefix, string(txID)) intermediateKey, err := s.redisClient.API().Get(ctx, transactionIDBasedKey).Result() @@ -108,11 +127,11 @@ func (s *Store) Get( return s.findOne(ctx, intermediateKey) } -func (s *Store) FindByOpState(ctx context.Context, opState string) (*oidc4ci.Transaction, error) { - return s.Get(ctx, oidc4ci.TxID(opState)) +func (s *Store) FindByOpState(ctx context.Context, opState string) (*issuecredential.Transaction, error) { + return s.Get(ctx, issuecredential.TxID(opState)) } -func (s *Store) findOne(ctx context.Context, intermediateKey string) (*oidc4ci.Transaction, error) { +func (s *Store) findOne(ctx context.Context, intermediateKey string) (*issuecredential.Transaction, error) { clientAPI := s.redisClient.API() b, err := clientAPI.Get(ctx, intermediateKey).Bytes() if err != nil { @@ -132,13 +151,13 @@ func (s *Store) findOne(ctx context.Context, intermediateKey string) (*oidc4ci.T return nil, resterr.ErrDataNotFound } - return &oidc4ci.Transaction{ - ID: oidc4ci.TxID(doc.ID), + return &issuecredential.Transaction{ + ID: issuecredential.TxID(doc.ID), TransactionData: *doc.TransactionData, }, nil } -func (s *Store) Update(ctx context.Context, tx *oidc4ci.Transaction) error { +func (s *Store) Update(ctx context.Context, tx *issuecredential.Transaction) error { transactionIDBasedKey := resolveRedisKey(keyPrefix, string(tx.ID)) opStatueBasedKey := resolveRedisKey(keyPrefix, tx.OpState) diff --git a/pkg/storage/redis/oidc4cinoncestore/oidc4vc_store_test.go b/pkg/storage/redis/oidc4cinoncestore/oidc4vc_store_test.go index 0a8087172..2a699eb49 100644 --- a/pkg/storage/redis/oidc4cinoncestore/oidc4vc_store_test.go +++ b/pkg/storage/redis/oidc4cinoncestore/oidc4vc_store_test.go @@ -23,7 +23,7 @@ import ( vcsverifiable "github.com/trustbloc/vcs/pkg/doc/verifiable" profileapi "github.com/trustbloc/vcs/pkg/profile" "github.com/trustbloc/vcs/pkg/restapi/resterr" - "github.com/trustbloc/vcs/pkg/service/oidc4ci" + "github.com/trustbloc/vcs/pkg/service/issuecredential" "github.com/trustbloc/vcs/pkg/storage/redis" ) @@ -48,7 +48,7 @@ func TestStore(t *testing.T) { t.Run("try insert duplicate op_state", func(t *testing.T) { id := uuid.New().String() - toInsert := &oidc4ci.TransactionData{ + toInsert := &issuecredential.TransactionData{ OpState: id, } @@ -61,10 +61,31 @@ func TestStore(t *testing.T) { assert.Empty(t, resp2) }) + t.Run("try insert duplicate op_state [force]", func(t *testing.T) { + id := uuid.New().String() + + toInsert := &issuecredential.TransactionData{ + OpState: id, + } + + resp1, err1 := store.Create(context.Background(), 0, toInsert) + assert.NoError(t, err1) + assert.NotEmpty(t, resp1) + + toInsert.DID = "xxx" + resp2, err2 := store.ForceCreate(context.Background(), 0, toInsert) + assert.NoError(t, err2) + assert.NotEmpty(t, resp2) + + fRes, fErr := store.FindByOpState(context.Background(), id) + assert.NoError(t, fErr) + assert.Equal(t, toInsert.DID, fRes.DID) + }) + t.Run("test default expiration", func(t *testing.T) { id := uuid.New().String() - toInsert := &oidc4ci.TransactionData{ + toInsert := &issuecredential.TransactionData{ OpState: id, } @@ -82,7 +103,7 @@ func TestStore(t *testing.T) { t.Run("test profile expiration", func(t *testing.T) { id := uuid.New().String() - toInsert := &oidc4ci.TransactionData{ + toInsert := &issuecredential.TransactionData{ OpState: id, } @@ -102,7 +123,7 @@ func TestStore(t *testing.T) { t.Run("test insert and find", func(t *testing.T) { id := uuid.New().String() - toInsert := &oidc4ci.TransactionData{ + toInsert := &issuecredential.TransactionData{ ProfileID: "profileID", AuthorizationEndpoint: "authEndpoint", PushedAuthorizationRequestEndpoint: "pushedAuth", @@ -118,7 +139,7 @@ func TestStore(t *testing.T) { PreAuthCode: uuid.NewString(), WebHookURL: "http://remote-url", DID: "did:123", - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ID: uuid.NewString(), CredentialTemplate: &profileapi.CredentialTemplate{ @@ -132,17 +153,17 @@ func TestStore(t *testing.T) { ClaimDataID: uuid.NewString(), CredentialName: uuid.NewString(), CredentialDescription: uuid.NewString(), - AuthorizationDetails: &oidc4ci.AuthorizationDetails{ + AuthorizationDetails: &issuecredential.AuthorizationDetails{ Type: "321", CredentialConfigurationID: "CredentialConfigurationID", - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"fdsfsd"}, }, Format: "vxcxzcz", Locations: []string{"loc1", "loc2"}, }, CredentialConfigurationID: "CredentialConfigurationID", - CredentialComposeConfiguration: &oidc4ci.CredentialComposeConfiguration{ + CredentialComposeConfiguration: &issuecredential.CredentialComposeConfiguration{ IDTemplate: uuid.NewString(), OverrideIssuer: true, }, @@ -150,7 +171,7 @@ func TestStore(t *testing.T) { }, } - var resp *oidc4ci.Transaction + var resp *issuecredential.Transaction resp, err = store.Create(context.Background(), 0, toInsert) assert.NoError(t, err) @@ -172,15 +193,15 @@ func TestStore(t *testing.T) { t.Run("test update", func(t *testing.T) { id := uuid.NewString() - toInsert := &oidc4ci.TransactionData{ + toInsert := &issuecredential.TransactionData{ GrantType: "342", ResponseType: "123", Scope: []string{"213", "321"}, OpState: id, - CredentialConfiguration: []*oidc4ci.TxCredentialConfiguration{ + CredentialConfiguration: []*issuecredential.TxCredentialConfiguration{ { ClaimEndpoint: "432", - AuthorizationDetails: &oidc4ci.AuthorizationDetails{Type: "321"}, + AuthorizationDetails: &issuecredential.AuthorizationDetails{Type: "321"}, CredentialConfigurationID: "CredentialConfigurationID", }, }, @@ -200,10 +221,10 @@ func TestStore(t *testing.T) { CredentialSubject: json.RawMessage(`{"sub_1":"abcd"}`), } - ad := &oidc4ci.AuthorizationDetails{ + ad := &issuecredential.AuthorizationDetails{ Type: "321", CredentialConfigurationID: "CredentialConfigurationID", - CredentialDefinition: &oidc4ci.CredentialDefinition{ + CredentialDefinition: &issuecredential.CredentialDefinition{ Type: []string{"fdsfsd"}, }, Format: "vxcxzcz", @@ -249,7 +270,7 @@ func TestWithTimeouts(t *testing.T) { defer cancel() t.Run("Create timeout", func(t *testing.T) { - resp, err := store.Create(ctx, 0, &oidc4ci.TransactionData{}) + resp, err := store.Create(ctx, 0, &issuecredential.TransactionData{}) assert.Empty(t, resp) assert.ErrorContains(t, err, "context deadline exceeded") diff --git a/scripts/check_integration.sh b/scripts/check_integration.sh index 50340053d..6e9bf0e2f 100755 --- a/scripts/check_integration.sh +++ b/scripts/check_integration.sh @@ -15,6 +15,6 @@ cd test/bdd echo "Running vcs integration tests with tag=$TAGS" #export DISABLE_COMPOSITION=true -#export TAGS=@oidc4vc_rest_pre_auth_flow_cwt_TODO +#export TAGS=@oidc4vc_rest_pre_auth_flow_credential_refresh go test -count=1 -v -cover . -p 1 -timeout=40m $TAGS cd $PWD \ No newline at end of file diff --git a/test/bdd/attestation/go.mod b/test/bdd/attestation/go.mod index 7975fdbdc..ee2049bd6 100644 --- a/test/bdd/attestation/go.mod +++ b/test/bdd/attestation/go.mod @@ -12,8 +12,8 @@ require ( github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 github.com/trustbloc/cmdutil-go v1.0.0 - github.com/trustbloc/did-go v1.2.1 - github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f + github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 + github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b ) diff --git a/test/bdd/attestation/go.sum b/test/bdd/attestation/go.sum index f0e1b554f..09cff8844 100644 --- a/test/bdd/attestation/go.sum +++ b/test/bdd/attestation/go.sum @@ -226,14 +226,14 @@ github.com/trustbloc/bbs-signature-go v1.0.2 h1:gepEsbLiZHv/vva9FKG5gF38mGtOIyGe github.com/trustbloc/bbs-signature-go v1.0.2/go.mod h1:xYotcXHAbcE0TO+SteW0J6XI3geQaXq4wdnXR2k+XCU= github.com/trustbloc/cmdutil-go v1.0.0 h1:QCe7wVEIASWmy9ZDD0l0tsQCEsX6fx+kBFX5UqCVRdk= github.com/trustbloc/cmdutil-go v1.0.0/go.mod h1:o/v7C1z6d/5UrjaC6GAUc1hk0XVuE3M4tpyvsMMUw5k= -github.com/trustbloc/did-go v1.2.1 h1:SEOmPX+x2JlE6+jjjJp82yCCusoJ4/67zQ2ST6ytfSc= -github.com/trustbloc/did-go v1.2.1/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 h1:LpX6reFIcahgTxuDLrV9yro1gENtXQLv2NsneX5hWkc= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= github.com/trustbloc/kms-go v1.1.2 h1:nAlhDoHkSyX1eQFRz/sJsdgmJuNadyX7FJEy/9ROwys= github.com/trustbloc/kms-go v1.1.2/go.mod h1:OKOtsLbE6W5s4mpjWkvk8XEqcmt9vTgVmDNkHELpWO0= github.com/trustbloc/logutil-go v0.0.0-20221124174025-c46110e3ea42 h1:Mzg9wvEoUIWPoI/GHz3YlVbd4nKWeSPGc6+3l95eOZU= github.com/trustbloc/logutil-go v0.0.0-20221124174025-c46110e3ea42/go.mod h1:HRaXVV1caceumbDBwLO3ByiCcAc18KwrNvZ7JQBvDIQ= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f h1:v/Gf89EK2vCk2uj9bK+zmoTAQiJEXH9I2p3fYBiNLrg= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f/go.mod h1:7GkmcXtK5FNbfxypAZAVg8eM9PVnGJ/lmQo7gQgsjKk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc h1:6/AZPtL61aybL8FsAgZBtaq89zX4ahgRqa20aEKP1fk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc/go.mod h1:l70VUyzJ+jAxGSlS+V8jiP5GnVcxFfV4bPxJ0grlu14= github.com/veraison/go-cose v1.1.1-0.20240126165338-2300d5c96dbd h1:QhdCHSW1/oosJbzBTEYLU6xcKxXbQzzqFnhCtW2UWbA= github.com/veraison/go-cose v1.1.1-0.20240126165338-2300d5c96dbd/go.mod h1:D1wnviyjdmcF8AO5Y9kVGU6OGuvXUMGiE0Auo/fYRYo= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= diff --git a/test/bdd/features/oidc4vc_api.feature b/test/bdd/features/oidc4vc_api.feature index 16ff96def..fa6da5057 100644 --- a/test/bdd/features/oidc4vc_api.feature +++ b/test/bdd/features/oidc4vc_api.feature @@ -181,6 +181,31 @@ Feature: OIDC4VC REST API | awesome_cwt/v1.0 | PermanentResidentCard | permanentResidentCardTemplateID | awesome_cwt_verifier/v1.0 | 32f54163-no-limit-disclosure-optional-fields | lpr_category_id,commuter_classification,registration_city | cwt | | i_myprofile_ud_es256k_jwt/v1.0 | PermanentResidentCard | permanentResidentCardTemplateID | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-optional-fields | lpr_category_id,commuter_classification,registration_city | cwt | + @oidc4vc_rest_pre_auth_flow_credential_refresh + Scenario Outline: OIDC credential issuance and verification Pre Auth flow + Given Profile "" issuer has been authorized with username "profile-user-issuer-1" and password "profile-user-issuer-1-pwd" + And User holds credential "" with templateID "" + And Profile "" verifier has been authorized with username "profile-user-verifier-1" and password "profile-user-verifier-1-pwd" + And proofType is "" + + When User interacts with Wallet to initiate credential issuance using pre authorization code flow + Then "1" credentials are issued + Then ensure credential refresh service is set + + Then wallet ensures that no credential refresh available + Then issuer send requests to initiate credential refresh + ## expected two times for test, as we should invalidate previous request + Then issuer send requests to initiate credential refresh + Then wallet refreshes credentials + Then User interacts with Verifier and initiate OIDC4VP interaction under "" profile with presentation definition ID "" and fields "" + And Verifier with profile "" retrieves interactions claims + Then we wait 2 seconds + And Verifier with profile "" requests deleted interactions claims + + Examples: + | issuerProfile | credentialType | credentialTemplate | verifierProfile | presentationDefinitionID | fields | proofType | + | bank_issuer/v1.0 | UniversityDegreeCredential | universityDegreeTemplateID | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | jwt | + @oidc4vc_rest_pre_auth_flow_compose Scenario Outline: OIDC credential issuance and verification Pre Auth flow Given Profile "" issuer has been authorized with username "profile-user-issuer-1" and password "profile-user-issuer-1-pwd" diff --git a/test/bdd/fixtures/krakend-config/settings/endpoint.json b/test/bdd/fixtures/krakend-config/settings/endpoint.json index 01cce7463..8d6f2ac10 100644 --- a/test/bdd/fixtures/krakend-config/settings/endpoint.json +++ b/test/bdd/fixtures/krakend-config/settings/endpoint.json @@ -24,6 +24,18 @@ "Content-Type" ] }, + { + "endpoint": "/issuer/profiles/{profileID}/{profileVersion}/interactions/refresh", + "method": "POST", + "protected": true, + "roles_to_validate": [ + "issuer" + ], + "input_headers": [ + "X-Tenant-ID", + "Content-Type" + ] + }, { "endpoint": "/issuer/profiles/{profileID}/{profileVersion}/interactions/initiate-oidc", "method": "POST", @@ -48,6 +60,20 @@ "Content-Type" ] }, + { + "endpoint": "/refresh/{profileID}/{profileVersion}", + "method": "GET", + "input_query_strings": [ + "credentialID" + ] + }, + { + "endpoint": "/refresh/{profileID}/{profileVersion}", + "method": "POST", + "input_query_strings": [ + "credentialID" + ] + }, { "endpoint": "/oidc/authorize", "method": "GET", diff --git a/test/bdd/fixtures/profile/profiles.json b/test/bdd/fixtures/profile/profiles.json index e662f44d0..74dec7fa8 100644 --- a/test/bdd/fixtures/profile/profiles.json +++ b/test/bdd/fixtures/profile/profiles.json @@ -10,6 +10,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "RS256", "signatureRepresentation": 0, "keyType": "RSARS256", @@ -49,7 +50,7 @@ "credentialTemplates": [], "credentialMetadata": { "credential_configurations_supported": { - "UniversityDegreeCredentialIdentifier": { + "UniversityDegreeCredentialIdentifier": { "format": "jwt_vc_json-ld", "scope": "UniversityDegreeCredential_001", "credential_definition": { @@ -143,6 +144,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "RS256", "signatureRepresentation": 0, "keyType": "RSARS256", @@ -232,7 +234,7 @@ ], "credentialMetadata": { "credential_configurations_supported": { - "UniversityDegreeCredentialIdentifier": { + "UniversityDegreeCredentialIdentifier": { "format": "jwt_vc_json-ld", "scope": "UniversityDegreeCredential_001", "credential_definition": { @@ -326,6 +328,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "EcdsaSecp256k1Signature2019", "signatureRepresentation": 1, "keyType": "ECDSASecp256k1DER", @@ -348,6 +351,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "JsonWebSignature2020", "signatureRepresentation": 0, "keyType": "ECDSAP256DER", @@ -495,6 +499,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "ES256", "signatureRepresentation": 1, "keyType": "ECDSAP256DER", @@ -517,6 +522,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "ES256", "signatureRepresentation": 1, "keyType": "ECDSAP256DER", @@ -543,6 +549,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "ES384", "signatureRepresentation": 1, "keyType": "ECDSAP384DER", @@ -565,6 +572,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "ES384", "signatureRepresentation": 1, "keyType": "ECDSAP384DER", @@ -591,6 +599,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "ES256K", "signatureRepresentation": 1, "keyType": "ECDSASecp256k1DER", @@ -782,6 +791,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "ES256K", "signatureRepresentation": 1, "keyType": "RSARS256", @@ -973,6 +983,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "ES256K", "signatureRepresentation": 1, "keyType": "ECDSASecp256k1DER", @@ -999,6 +1010,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "JsonWebSignature2020", "signatureRepresentation": 1, "keyType": "ECDSAP256DER", @@ -1021,6 +1033,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "JsonWebSignature2020", "signatureRepresentation": 1, "keyType": "ECDSASecp256k1DER", @@ -1043,6 +1056,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "JsonWebSignature2020", "signatureRepresentation": 1, "keyType": "ECDSAP256DER", @@ -1065,6 +1079,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "JsonWebSignature2020", "signatureRepresentation": 1, "keyType": "ECDSAP384DER", @@ -1087,6 +1102,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "JsonWebSignature2020", "signatureRepresentation": 1, "keyType": "ECDSAP256DER", @@ -1109,6 +1125,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "JsonWebSignature2020", "signatureRepresentation": 1, "keyType": "ECDSAP384DER", @@ -1131,6 +1148,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "JsonWebSignature2020", "signatureRepresentation": 1, "keyType": "ECDSAP256DER", @@ -1312,6 +1330,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": true, "signingAlgorithm": "JsonWebSignature2020", "signatureRepresentation": 0, "keyType": "ECDSASecp256k1DER", @@ -1524,6 +1543,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "JsonWebSignature2020", "signatureRepresentation": 0, "keyType": "ECDSASecp256k1DER", @@ -1720,6 +1740,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "JsonWebSignature2020", "signatureRepresentation": 0, "keyType": "ECDSASecp256k1DER", @@ -1839,6 +1860,7 @@ "url": "http://vc-rest-echo.trustbloc.local:8075", "active": true, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "JsonWebSignature2020", "signatureRepresentation": 1, "keyType": "ECDSAP256DER", @@ -1988,6 +2010,7 @@ } }, "vcConfig": { + "refreshServiceEnabled": false, "signingAlgorithm": "ES256K", "signatureRepresentation": 1, "keyType": "ECDSASecp256k1DER", diff --git a/test/bdd/go.mod b/test/bdd/go.mod index 85f72792f..d156f40ae 100644 --- a/test/bdd/go.mod +++ b/test/bdd/go.mod @@ -24,11 +24,11 @@ require ( github.com/samber/lo v1.38.1 github.com/tidwall/gjson v1.14.4 github.com/trustbloc/cmdutil-go v0.0.0-20221125151303-09d42adcc811 - github.com/trustbloc/did-go v1.2.1 + github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 github.com/trustbloc/kms-go v1.1.2 github.com/trustbloc/logutil-go v1.0.0-rc1 github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104 - github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f + github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc github.com/trustbloc/vcs v0.1.9-0.20230210204445-f2870a36f0ea github.com/trustbloc/vcs/component/wallet-cli v0.0.0-20240103173902-7fbe030659b2 github.com/trustbloc/vcs/test/stress v0.0.0-00010101000000-000000000000 diff --git a/test/bdd/go.sum b/test/bdd/go.sum index 2ebd4f51a..1dcd6b517 100644 --- a/test/bdd/go.sum +++ b/test/bdd/go.sum @@ -678,16 +678,16 @@ github.com/trustbloc/bbs-signature-go v1.0.2 h1:gepEsbLiZHv/vva9FKG5gF38mGtOIyGe github.com/trustbloc/bbs-signature-go v1.0.2/go.mod h1:xYotcXHAbcE0TO+SteW0J6XI3geQaXq4wdnXR2k+XCU= github.com/trustbloc/cmdutil-go v0.0.0-20221125151303-09d42adcc811 h1:0e1d1w9o662+e7ZnJvRYJH8yblcBXngme8qbsjTvhQc= github.com/trustbloc/cmdutil-go v0.0.0-20221125151303-09d42adcc811/go.mod h1:o/v7C1z6d/5UrjaC6GAUc1hk0XVuE3M4tpyvsMMUw5k= -github.com/trustbloc/did-go v1.2.1 h1:SEOmPX+x2JlE6+jjjJp82yCCusoJ4/67zQ2ST6ytfSc= -github.com/trustbloc/did-go v1.2.1/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 h1:LpX6reFIcahgTxuDLrV9yro1gENtXQLv2NsneX5hWkc= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= github.com/trustbloc/kms-go v1.1.2 h1:nAlhDoHkSyX1eQFRz/sJsdgmJuNadyX7FJEy/9ROwys= github.com/trustbloc/kms-go v1.1.2/go.mod h1:OKOtsLbE6W5s4mpjWkvk8XEqcmt9vTgVmDNkHELpWO0= github.com/trustbloc/logutil-go v1.0.0-rc1 h1:rRJbvgQfrlUfyej+mY0nuQJymGqjRW4oZEwKi544F4c= github.com/trustbloc/logutil-go v1.0.0-rc1/go.mod h1:JlxT0oZfNKgIlSNtgc001WEeDMxlnAvOM43gNm8DQVc= github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104 h1:0IW4muaGvhjJ4OkG6/PQG3DGf5POWxlA1wwEYsxWQ+4= github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104/go.mod h1:3yChjB5KOT7B9eZe0W1XaIx3MNUuC1Oe9nR/GCtI1W0= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f h1:v/Gf89EK2vCk2uj9bK+zmoTAQiJEXH9I2p3fYBiNLrg= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f/go.mod h1:7GkmcXtK5FNbfxypAZAVg8eM9PVnGJ/lmQo7gQgsjKk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc h1:6/AZPtL61aybL8FsAgZBtaq89zX4ahgRqa20aEKP1fk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc/go.mod h1:l70VUyzJ+jAxGSlS+V8jiP5GnVcxFfV4bPxJ0grlu14= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= diff --git a/test/bdd/pkg/v1/oidc4vc/oidc4vci.go b/test/bdd/pkg/v1/oidc4vc/oidc4vci.go index 0376f01cf..d41c273aa 100644 --- a/test/bdd/pkg/v1/oidc4vc/oidc4vci.go +++ b/test/bdd/pkg/v1/oidc4vc/oidc4vci.go @@ -78,15 +78,18 @@ func (s *Steps) authorizeIssuerProfileUser(profileVersionedID, username, passwor return nil } -func (s *Steps) initiateCredentialIssuanceInternal(endpointURL string, req any) (*initiateOIDC4VCIResponse, error) { - token := s.bddContext.Args[getOrgAuthTokenKey(s.issuerProfile.ID+"/"+s.issuerProfile.Version)] +func (s *Steps) getToken() string { + return s.bddContext.Args[getOrgAuthTokenKey(s.issuerProfile.ID+"/"+s.issuerProfile.Version)] +} +func (s *Steps) initiateCredentialIssuanceInternal(endpointURL string, req any) (*initiateOIDC4VCIResponse, error) { reqBody, err := json.Marshal(req) if err != nil { return nil, fmt.Errorf("marshal initiate oidc4vci req: %w", err) } - resp, err := bddutil.HTTPSDo(http.MethodPost, endpointURL, "application/json", token, bytes.NewReader(reqBody), + resp, err := bddutil.HTTPSDo(http.MethodPost, endpointURL, "application/json", s.getToken(), + bytes.NewReader(reqBody), s.bddContext.TLSConfig) if err != nil { return nil, fmt.Errorf("https do: %w", err) @@ -160,7 +163,7 @@ func (s *Steps) runOIDC4VCIPreAuth(initiateOIDC4CIResponseData initiateOIDC4VCIR return fmt.Errorf("init pre-auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err != nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err != nil { return fmt.Errorf("run pre-auth flow: %w", err) } @@ -412,7 +415,7 @@ func (s *Steps) runOIDC4CIPreAuthWithClientAttestation() error { return fmt.Errorf("init pre-auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err != nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err != nil { return fmt.Errorf("run pre-auth flow: %w", err) } @@ -483,7 +486,7 @@ func (s *Steps) runOIDC4CIAuthWithErrorInvalidClient(updatedClientID, errorConta return fmt.Errorf("init auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err == nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err == nil { return fmt.Errorf("error expected, got nil") } @@ -611,7 +614,7 @@ func (s *Steps) runOIDC4VCIAuthWithError(errorContains string, overrideOpts ...o return fmt.Errorf("init auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err == nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err == nil { return fmt.Errorf("error expected, got nil") } @@ -647,7 +650,7 @@ func (s *Steps) runOIDC4VCIAuth() error { return fmt.Errorf("init auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err != nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err != nil { return fmt.Errorf("run auth flow: %w", err) } @@ -688,7 +691,7 @@ func (s *Steps) runOIDC4VCIAuthBatchByCredentialConfigurationID(credentialConfig return fmt.Errorf("init auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err != nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err != nil { return fmt.Errorf("run auth flow: %w", err) } @@ -734,7 +737,7 @@ func (s *Steps) runOIDC4VCIAuthBatch() error { return fmt.Errorf("init auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err != nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err != nil { return fmt.Errorf("run auth flow: %w", err) } @@ -774,7 +777,7 @@ func (s *Steps) runOIDC4VCIAuthBatchWithScopes(scopes string) error { return fmt.Errorf("init auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err != nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err != nil { return fmt.Errorf("run auth flow: %w", err) } @@ -900,7 +903,7 @@ func (s *Steps) runOIDC4VCIAuthWithCredentialConfigurationID(credentialConfigura return fmt.Errorf("init auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err != nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err != nil { return fmt.Errorf("run auth flow: %w", err) } @@ -946,7 +949,7 @@ func (s *Steps) runOIDC4VCIAuthWithScopes(scopes string) error { return fmt.Errorf("init auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err != nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err != nil { return fmt.Errorf("run auth flow: %w", err) } @@ -973,7 +976,7 @@ func (s *Steps) runOIDC4VCIAuthWalletInitiatedFlow() error { return fmt.Errorf("init wallet-initiated auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err != nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err != nil { return fmt.Errorf("run wallet-initiated auth flow: %w", err) } @@ -1019,7 +1022,7 @@ func (s *Steps) runOIDC4VCIAuthWithInvalidClaims() error { return fmt.Errorf("init auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err == nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err == nil { return fmt.Errorf("error expected, got nil") } @@ -1069,7 +1072,7 @@ func (s *Steps) runOIDC4CIAuthWithClientRegistrationMethod(method string) error return fmt.Errorf("init auth flow: %w", err) } - if _, err = flow.Run(context.Background()); err != nil { + if s.issuedCredentials, err = flow.Run(context.Background()); err != nil { return fmt.Errorf("run auth flow: %w", err) } diff --git a/test/bdd/pkg/v1/oidc4vc/refresh.go b/test/bdd/pkg/v1/oidc4vc/refresh.go new file mode 100644 index 000000000..808201e77 --- /dev/null +++ b/test/bdd/pkg/v1/oidc4vc/refresh.go @@ -0,0 +1,142 @@ +/* +Copyright Avast Software. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package oidc4vc + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/trustbloc/vc-go/verifiable" + + "github.com/trustbloc/vcs/component/wallet-cli/pkg/refresh" + "github.com/trustbloc/vcs/pkg/restapi/v1/issuer" + "github.com/trustbloc/vcs/test/bdd/pkg/bddutil" +) + +func (s *Steps) ensureCredentialServiceSet() error { + if len(s.issuedCredentials) == 0 { + return fmt.Errorf("no credentials issued") + } + + for _, cred := range s.issuedCredentials { + srv := cred.Contents().RefreshService + if srv == nil { + return fmt.Errorf("refresh service is not set") + } + + if srv.Type != "VerifiableCredentialRefreshService2021" { + return fmt.Errorf("unexpected refresh service type: %s", srv.Type) + } + + if srv.ID == "" { + return fmt.Errorf("refresh service endpoint is not set") + } + } + + return nil +} + +func (s *Steps) ensureNoCredentialRefreshAvailable() error { + flow, err := refresh.NewFlow(s.oidc4vpProvider) + if err != nil { + return fmt.Errorf("init flow: %w", err) + } + + if err = flow.Run(context.TODO()); err != nil { + return fmt.Errorf("run flow: %w", err) + } + + updated := flow.GetUpdatedCredentials() + if len(updated) > 0 { + return fmt.Errorf("unexpected refreshed credentials: %d", len(updated)) + } + + return nil +} + +func (s *Steps) walletRefreshesCredential() error { + flow, err := refresh.NewFlow(s.oidc4vpProvider) + if err != nil { + return fmt.Errorf("init flow: %w", err) + } + + if err = flow.Run(context.TODO()); err != nil { + return fmt.Errorf("run flow: %w", err) + } + + updated := flow.GetUpdatedCredentials() + oldToNew := map[string]*verifiable.Credential{} + for _, upd := range updated { + oldToNew[upd.OldCredential.Contents().ID] = upd.NewCredential + } + + for _, issuedCred := range s.issuedCredentials { + _, ok := oldToNew[issuedCred.Contents().ID] + if !ok { + return fmt.Errorf("refreshed credential not found") + } + } + + return nil +} + +func (s *Steps) issuerSendRequestToInitiateCredentialRefresh() error { + claims, err := s.fetchClaimData(s.issuedCredentialType) + if err != nil { + return fmt.Errorf("fetchClaimData: %w", err) + } + + body, err := json.Marshal(issuer.SetCredentialRefreshStateRequest{ + Claims: claims, + CredentialId: s.issuedCredentials[0].Contents().ID, + }) + if err != nil { + return fmt.Errorf("marshal request payload: %w", err) + } + + resp, err := bddutil.HTTPSDo( + http.MethodPost, + fmt.Sprintf("%s/issuer/profiles/%s/%s/interactions/refresh", + vcsAPIGateway, + s.issuerProfile.ID, + s.issuerProfile.Version, + ), + "application/json", + s.getToken(), + bytes.NewReader(body), + s.tlsConfig, + ) //nolint: bodyclose + + if err != nil { + return err + } + defer bddutil.CloseResponseBody(resp.Body) + + respBytes, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return bddutil.ExpectedStatusCodeError(http.StatusOK, resp.StatusCode, respBytes) + } + + var result issuer.SetCredentialRefreshStateResult + if err = json.Unmarshal(respBytes, &result); err != nil { + return fmt.Errorf("decode response payload: %w", err) + } + + if result.TransactionId == "" { + return fmt.Errorf("missing transaction ID in response") + } + + return nil +} diff --git a/test/bdd/pkg/v1/oidc4vc/steps.go b/test/bdd/pkg/v1/oidc4vc/steps.go index 5d56d5357..fd127258e 100644 --- a/test/bdd/pkg/v1/oidc4vc/steps.go +++ b/test/bdd/pkg/v1/oidc4vc/steps.go @@ -76,6 +76,7 @@ type Steps struct { expectedCredentialsAmountForVP int expectedAttachment []string vpAttachments map[string]string + issuedCredentials []*verifiable.Credential } // NewSteps returns new Steps context. @@ -117,6 +118,10 @@ func (s *Steps) RegisterSteps(sc *godog.ScenarioContext) { sc.Step(`^User interacts with Wallet to initiate credential issuance using authorization code flow with invalid claims schema$`, s.runOIDC4VCIAuthWithInvalidClaims) sc.Step(`^User interacts with Wallet to initiate credential issuance using pre authorization code flow with client attestation enabled$`, s.runOIDC4CIPreAuthWithClientAttestation) sc.Step(`^proofType is "([^"]*)"$`, s.setProofType) + sc.Step("^ensure credential refresh service is set$", s.ensureCredentialServiceSet) + sc.Step("^wallet ensures that no credential refresh available$", s.ensureNoCredentialRefreshAvailable) + sc.Step("^issuer send requests to initiate credential refresh$", s.issuerSendRequestToInitiateCredentialRefresh) + sc.Step("^wallet refreshes credentials$", s.walletRefreshesCredential) sc.Step(`^initiateIssuanceVersion is "([^"]*)"$`, s.setInitiateIssuanceVersion) sc.Step(`^credentialCompose is active with "([^"]*)"$`, s.setCredentialCompose) @@ -170,6 +175,7 @@ func (s *Steps) ResetAndSetup() error { s.expectedCredentialsAmountForVP = 0 s.expectedAttachment = nil s.vpAttachments = nil + s.issuedCredentials = nil s.tlsConfig = s.bddContext.TLSConfig diff --git a/test/stress/go.mod b/test/stress/go.mod index ea3531ef9..747135ddd 100644 --- a/test/stress/go.mod +++ b/test/stress/go.mod @@ -17,11 +17,11 @@ require ( github.com/piprate/json-gold v0.5.1-0.20230111113000-6ddbe6e6f19f github.com/redis/go-redis/v9 v9.5.3 github.com/samber/lo v1.38.1 - github.com/trustbloc/did-go v1.2.1 + github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 github.com/trustbloc/kms-go v1.1.2 github.com/trustbloc/logutil-go v1.0.0-rc1 github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104 - github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f + github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc github.com/trustbloc/vcs v0.1.9-0.20230210204445-f2870a36f0ea github.com/trustbloc/vcs/component/wallet-cli v0.0.0-20240103173902-7fbe030659b2 github.com/trustbloc/vcs/test/bdd v0.0.0-00010101000000-000000000000 diff --git a/test/stress/go.sum b/test/stress/go.sum index 83345dc7e..f2946d6bf 100644 --- a/test/stress/go.sum +++ b/test/stress/go.sum @@ -563,16 +563,16 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/trustbloc/bbs-signature-go v1.0.2 h1:gepEsbLiZHv/vva9FKG5gF38mGtOIyGez7desZxiI1o= github.com/trustbloc/bbs-signature-go v1.0.2/go.mod h1:xYotcXHAbcE0TO+SteW0J6XI3geQaXq4wdnXR2k+XCU= -github.com/trustbloc/did-go v1.2.1 h1:SEOmPX+x2JlE6+jjjJp82yCCusoJ4/67zQ2ST6ytfSc= -github.com/trustbloc/did-go v1.2.1/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96 h1:LpX6reFIcahgTxuDLrV9yro1gENtXQLv2NsneX5hWkc= +github.com/trustbloc/did-go v1.2.2-0.20240812150654-c7d31e666f96/go.mod h1:packTRoBoo8DrwOE7QKsI98xXS3Vf6ovUXYD4FUAcB4= github.com/trustbloc/kms-go v1.1.2 h1:nAlhDoHkSyX1eQFRz/sJsdgmJuNadyX7FJEy/9ROwys= github.com/trustbloc/kms-go v1.1.2/go.mod h1:OKOtsLbE6W5s4mpjWkvk8XEqcmt9vTgVmDNkHELpWO0= github.com/trustbloc/logutil-go v1.0.0-rc1 h1:rRJbvgQfrlUfyej+mY0nuQJymGqjRW4oZEwKi544F4c= github.com/trustbloc/logutil-go v1.0.0-rc1/go.mod h1:JlxT0oZfNKgIlSNtgc001WEeDMxlnAvOM43gNm8DQVc= github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104 h1:0IW4muaGvhjJ4OkG6/PQG3DGf5POWxlA1wwEYsxWQ+4= github.com/trustbloc/sidetree-go v1.0.1-0.20240219121130-f4260aff7104/go.mod h1:3yChjB5KOT7B9eZe0W1XaIx3MNUuC1Oe9nR/GCtI1W0= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f h1:v/Gf89EK2vCk2uj9bK+zmoTAQiJEXH9I2p3fYBiNLrg= -github.com/trustbloc/vc-go v1.1.3-0.20240723144917-8cb417a4df0f/go.mod h1:7GkmcXtK5FNbfxypAZAVg8eM9PVnGJ/lmQo7gQgsjKk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc h1:6/AZPtL61aybL8FsAgZBtaq89zX4ahgRqa20aEKP1fk= +github.com/trustbloc/vc-go v1.1.3-0.20240813130343-c7c2ade0e1fc/go.mod h1:l70VUyzJ+jAxGSlS+V8jiP5GnVcxFfV4bPxJ0grlu14= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=