From 36f6c7ed709e0904aea915cf127e0afaaf5f8033 Mon Sep 17 00:00:00 2001 From: Stas Dm Date: Mon, 1 Jul 2024 12:19:32 +0200 Subject: [PATCH 1/7] feat: base work on attachments --- .gitignore | 2 + pkg/service/oidc4vp/api.go | 5 ++ pkg/service/oidc4vp/oidc4vp_service.go | 109 +++++++++++++++++++++++++ 3 files changed, 116 insertions(+) diff --git a/.gitignore b/.gitignore index aa41086ee..4759fa467 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,5 @@ cmd/vc-rest/.env test/stress/cmd/.env test/stress/.env + +component/wallet-cli/wallet-cli diff --git a/pkg/service/oidc4vp/api.go b/pkg/service/oidc4vp/api.go index 4154e2123..67b27ddcb 100644 --- a/pkg/service/oidc4vp/api.go +++ b/pkg/service/oidc4vp/api.go @@ -150,3 +150,8 @@ type ClientMetadata struct { ClientPurpose string `json:"client_purpose"` LogoURI string `json:"logo_uri"` } + +type Attachment struct { + Type string + Claim map[string]interface{} +} diff --git a/pkg/service/oidc4vp/oidc4vp_service.go b/pkg/service/oidc4vp/oidc4vp_service.go index ac2e98e62..c3324ea0b 100644 --- a/pkg/service/oidc4vp/oidc4vp_service.go +++ b/pkg/service/oidc4vp/oidc4vp_service.go @@ -10,9 +10,12 @@ package oidc4vp import ( "context" + "encoding/base64" "encoding/json" "errors" "fmt" + "io" + "net/http" "strings" "sync" "time" @@ -29,6 +32,7 @@ import ( "github.com/trustbloc/vc-go/jwt" "github.com/trustbloc/vc-go/presexch" "github.com/trustbloc/vc-go/proof/defaults" + "github.com/trustbloc/vc-go/util/maphelpers" "github.com/trustbloc/vc-go/verifiable" "github.com/trustbloc/vc-go/vermethod" "github.com/valyala/fastjson" @@ -609,6 +613,111 @@ func (s *Service) RetrieveClaims( return result } +const ( + AttachmentTypeRemote = "RemoteAttachment" + AttachmentTypeEmbedded = "EmbeddedAttachment" +) + +var knownAttachmentTypes = []string{AttachmentTypeRemote, AttachmentTypeEmbedded} + +func (s *Service) PrepareAttachments( + ctx context.Context, + subjects []*verifiable.Subject, +) ([]map[string]interface{}, error) { + var allAttachments []*Attachment + + for _, subject := range subjects { + allAttachments = append(allAttachments, s.findAttachments(subject.CustomFields, + make([]*Attachment, 0))...) + } + + var final []map[string]interface{} + + for _, attachment := range allAttachments { + clone := maphelpers.CopyMap(attachment.Claim) // shallow copy + final = append(final, clone) + + if attachment.Type != AttachmentTypeRemote { + continue + } + + targetUrl := fmt.Sprint(attachment.Claim["url"]) + if targetUrl == "" { + attachment.Claim["error"] = "url is required" + + continue + } + + resp, err := http.Get(targetUrl) + if err != nil { + attachment.Claim["error"] = fmt.Sprintf("failed to fetch url: %s", err) + + continue + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + attachment.Claim["error"] = fmt.Sprintf("failed to read response body: %s", err) + + continue + } + + attachment.Claim["data"] = base64.StdEncoding.EncodeToString(body) + } + + return final, nil +} + +func (s *Service) findAttachments( + targetMap map[string]interface{}, + attachments []*Attachment, +) []*Attachment { + for k, v := range targetMap { + if nested, ok := v.(map[string]interface{}); ok { + attachments = append(attachments, s.findAttachments(nested, attachments)...) + } + + if k != "type" && k != "@type" { + continue + } + + switch typed := v.(type) { + case string: + if lo.Contains(knownAttachmentTypes, typed) { + attachments = append(attachments, &Attachment{ + Type: typed, + Claim: targetMap, + }) + } + case []interface{}: + newSlice := make([]string, len(typed), 0) + for _, item := range typed { + newSlice = append(newSlice, fmt.Sprint(item)) + } + + for _, item := range newSlice { + if lo.Contains(knownAttachmentTypes, item) { + attachments = append(attachments, &Attachment{ + Type: item, + Claim: targetMap, + }) + } + } + case []string: + for _, item := range typed { + if lo.Contains(knownAttachmentTypes, item) { + attachments = append(attachments, &Attachment{ + Type: item, + Claim: targetMap, + }) + } + } + } + } + + return attachments +} + func (s *Service) DeleteClaims(_ context.Context, claimsID string) error { return s.transactionManager.DeleteReceivedClaims(claimsID) } From f8b57504d565cfbefa4b18bf963748d5433815cf Mon Sep 17 00:00:00 2001 From: Stas Dm Date: Mon, 1 Jul 2024 12:52:04 +0200 Subject: [PATCH 2/7] feat: base tests --- pkg/service/oidc4vp/attachments.go | 174 ++++++++++++++++++ pkg/service/oidc4vp/attachments_test.go | 48 +++++ pkg/service/oidc4vp/oidc4vp_service.go | 109 ----------- ...iversity_degree_embedded_attachment.jsonld | 46 +++++ 4 files changed, 268 insertions(+), 109 deletions(-) create mode 100644 pkg/service/oidc4vp/attachments.go create mode 100644 pkg/service/oidc4vp/attachments_test.go create mode 100644 pkg/service/oidc4vp/testdata/university_degree_embedded_attachment.jsonld diff --git a/pkg/service/oidc4vp/attachments.go b/pkg/service/oidc4vp/attachments.go new file mode 100644 index 000000000..e66abf66e --- /dev/null +++ b/pkg/service/oidc4vp/attachments.go @@ -0,0 +1,174 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +//go:generate mockgen -destination attachments_mocks_test.go -self_package mocks -package oidc4vp_test -source=attachments.go -mock_names httpClient=MockHttpClient + +package oidc4vp + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "io" + "net/http" + + "github.com/samber/lo" + "github.com/trustbloc/vc-go/util/maphelpers" + "github.com/trustbloc/vc-go/verifiable" +) + +const ( + AttachmentTypeRemote = "RemoteAttachment" + AttachmentTypeEmbedded = "EmbeddedAttachment" + AttachmentDataField = "uri" +) + +var knownAttachmentTypes = []string{AttachmentTypeRemote, AttachmentTypeEmbedded} + +type AttachmentService struct { + httpClient httpClient +} + +type httpClient interface { + Do(req *http.Request) (*http.Response, error) +} + +func NewAttachmentService( + httpClient httpClient, +) *AttachmentService { + return &AttachmentService{ + httpClient: httpClient, + } +} + +func (s *AttachmentService) PrepareAttachments( + ctx context.Context, + subjects []*verifiable.Subject, +) ([]map[string]interface{}, error) { + var allAttachments []*Attachment + + for _, subject := range subjects { + allAttachments = append(allAttachments, + s.findAttachments(subject.CustomFields, make([]*Attachment, 0))..., + ) + } + + var final []map[string]interface{} + + for _, attachment := range allAttachments { + cloned := maphelpers.CopyMap(attachment.Claim) // shallow copy + final = append(final, cloned) + + if attachment.Type == AttachmentTypeRemote { + go func() { + err := s.handleRemoteAttachment(ctx, cloned) + if err != nil { + attachment.Claim["error"] = fmt.Sprintf("failed to handle remote attachment: %s", err) + } + }() + } + } + + return final, nil +} + +func (s *AttachmentService) handleRemoteAttachment( + ctx context.Context, + attachment map[string]interface{}, +) error { + targetUrl := fmt.Sprint(attachment[AttachmentDataField]) + if targetUrl == "" { + return errors.New("url is required") + } + + httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, targetUrl, nil) + if err != nil { + return fmt.Errorf("failed to create http request: %w", err) + } + + resp, err := s.httpClient.Do(httpReq) + if err != nil { + return fmt.Errorf("failed to fetch url: %w", err) + } + + var body []byte + if resp.Body != nil { + body, err = io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unexpected status code: %d and body %v", resp.StatusCode, string(body)) + } + + attachment[AttachmentDataField] = base64.StdEncoding.EncodeToString(body) // todo prefix type + + return nil +} + +func (s *AttachmentService) findAttachments( + targetMap map[string]interface{}, + attachments []*Attachment, +) []*Attachment { + hasAttachment := false + for k, v := range targetMap { + if nested, ok := v.(map[string]interface{}); ok { + attachments = append(attachments, s.findAttachments(nested, attachments)...) + } + + if hasAttachment { + continue + } + + if k != "type" && k != "@type" { + continue + } + + switch typed := v.(type) { + case string: + if lo.Contains(knownAttachmentTypes, typed) { + attachments = append(attachments, &Attachment{ + Type: typed, + Claim: targetMap, + }) + + hasAttachment = true + } + case []interface{}: + newSlice := make([]string, 0, len(typed)) + for _, item := range typed { + newSlice = append(newSlice, fmt.Sprint(item)) + } + + for _, item := range newSlice { + if lo.Contains(knownAttachmentTypes, item) { + attachments = append(attachments, &Attachment{ + Type: item, + Claim: targetMap, + }) + + hasAttachment = true + } + } + case []string: + for _, item := range typed { + if lo.Contains(knownAttachmentTypes, item) { + attachments = append(attachments, &Attachment{ + Type: item, + Claim: targetMap, + }) + + hasAttachment = true + } + } + } + } + + return attachments +} diff --git a/pkg/service/oidc4vp/attachments_test.go b/pkg/service/oidc4vp/attachments_test.go new file mode 100644 index 000000000..da6e37a76 --- /dev/null +++ b/pkg/service/oidc4vp/attachments_test.go @@ -0,0 +1,48 @@ +package oidc4vp_test + +import ( + "context" + _ "embed" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/trustbloc/vc-go/verifiable" + + "github.com/trustbloc/vcs/pkg/service/oidc4vp" +) + +var ( + //go:embed testdata/university_degree_embedded_attachment.jsonld + sampleVCWithEmbeddedAttachment string +) + +func TestAttachment(t *testing.T) { + t.Run("no attachment in credential", func(t *testing.T) { + var data map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleVCJsonLD), &data)) + + srv := oidc4vp.NewAttachmentService(nil) + + resp, err := srv.PrepareAttachments(context.TODO(), []*verifiable.Subject{{CustomFields: data}}) + assert.NoError(t, err) + assert.Empty(t, resp) + }) + + t.Run("with embedded attachment", func(t *testing.T) { + var data map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleVCWithEmbeddedAttachment), &data)) + + srv := oidc4vp.NewAttachmentService(nil) + + resp, err := srv.PrepareAttachments(context.TODO(), []*verifiable.Subject{{CustomFields: data}}) + assert.NoError(t, err) + assert.Len(t, resp, 1) + + attachment := resp[0] + assert.EqualValues(t, []interface{}{"EmbeddedAttachment"}, attachment["type"]) + assert.EqualValues(t, "base64content", attachment["uri"]) + assert.Nil(t, attachment["error"]) + assert.EqualValues(t, "5d41402abc4b2a76b9719d911017c592", attachment["hash"]) + }) +} diff --git a/pkg/service/oidc4vp/oidc4vp_service.go b/pkg/service/oidc4vp/oidc4vp_service.go index c3324ea0b..ac2e98e62 100644 --- a/pkg/service/oidc4vp/oidc4vp_service.go +++ b/pkg/service/oidc4vp/oidc4vp_service.go @@ -10,12 +10,9 @@ package oidc4vp import ( "context" - "encoding/base64" "encoding/json" "errors" "fmt" - "io" - "net/http" "strings" "sync" "time" @@ -32,7 +29,6 @@ import ( "github.com/trustbloc/vc-go/jwt" "github.com/trustbloc/vc-go/presexch" "github.com/trustbloc/vc-go/proof/defaults" - "github.com/trustbloc/vc-go/util/maphelpers" "github.com/trustbloc/vc-go/verifiable" "github.com/trustbloc/vc-go/vermethod" "github.com/valyala/fastjson" @@ -613,111 +609,6 @@ func (s *Service) RetrieveClaims( return result } -const ( - AttachmentTypeRemote = "RemoteAttachment" - AttachmentTypeEmbedded = "EmbeddedAttachment" -) - -var knownAttachmentTypes = []string{AttachmentTypeRemote, AttachmentTypeEmbedded} - -func (s *Service) PrepareAttachments( - ctx context.Context, - subjects []*verifiable.Subject, -) ([]map[string]interface{}, error) { - var allAttachments []*Attachment - - for _, subject := range subjects { - allAttachments = append(allAttachments, s.findAttachments(subject.CustomFields, - make([]*Attachment, 0))...) - } - - var final []map[string]interface{} - - for _, attachment := range allAttachments { - clone := maphelpers.CopyMap(attachment.Claim) // shallow copy - final = append(final, clone) - - if attachment.Type != AttachmentTypeRemote { - continue - } - - targetUrl := fmt.Sprint(attachment.Claim["url"]) - if targetUrl == "" { - attachment.Claim["error"] = "url is required" - - continue - } - - resp, err := http.Get(targetUrl) - if err != nil { - attachment.Claim["error"] = fmt.Sprintf("failed to fetch url: %s", err) - - continue - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - attachment.Claim["error"] = fmt.Sprintf("failed to read response body: %s", err) - - continue - } - - attachment.Claim["data"] = base64.StdEncoding.EncodeToString(body) - } - - return final, nil -} - -func (s *Service) findAttachments( - targetMap map[string]interface{}, - attachments []*Attachment, -) []*Attachment { - for k, v := range targetMap { - if nested, ok := v.(map[string]interface{}); ok { - attachments = append(attachments, s.findAttachments(nested, attachments)...) - } - - if k != "type" && k != "@type" { - continue - } - - switch typed := v.(type) { - case string: - if lo.Contains(knownAttachmentTypes, typed) { - attachments = append(attachments, &Attachment{ - Type: typed, - Claim: targetMap, - }) - } - case []interface{}: - newSlice := make([]string, len(typed), 0) - for _, item := range typed { - newSlice = append(newSlice, fmt.Sprint(item)) - } - - for _, item := range newSlice { - if lo.Contains(knownAttachmentTypes, item) { - attachments = append(attachments, &Attachment{ - Type: item, - Claim: targetMap, - }) - } - } - case []string: - for _, item := range typed { - if lo.Contains(knownAttachmentTypes, item) { - attachments = append(attachments, &Attachment{ - Type: item, - Claim: targetMap, - }) - } - } - } - } - - return attachments -} - func (s *Service) DeleteClaims(_ context.Context, claimsID string) error { return s.transactionManager.DeleteReceivedClaims(claimsID) } diff --git a/pkg/service/oidc4vp/testdata/university_degree_embedded_attachment.jsonld b/pkg/service/oidc4vp/testdata/university_degree_embedded_attachment.jsonld new file mode 100644 index 000000000..2b7cf80fa --- /dev/null +++ b/pkg/service/oidc4vp/testdata/university_degree_embedded_attachment.jsonld @@ -0,0 +1,46 @@ +{ + "@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", + "expirationDate": "2030-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", + "attachment1" : { + "id": "doc1", + "type": [ + "EmbeddedAttachment" + ], + "mimeType": "image/jpeg", + "uri": "base64content", + "description": "Scanned copy of something", + "hash": "5d41402abc4b2a76b9719d911017c592", + "hash-alg": "SHA-256" + } + } +} From 9c151f44e12bb3f75ca7802fdf566c56920377e7 Mon Sep 17 00:00:00 2001 From: Stas Dm Date: Mon, 1 Jul 2024 13:57:33 +0200 Subject: [PATCH 3/7] feat: more work on attachments --- cmd/vc-rest/startcmd/start.go | 1 + pkg/observability/metrics/provider.go | 1 + pkg/service/oidc4vp/api.go | 7 +- pkg/service/oidc4vp/attachments.go | 47 ++--- pkg/service/oidc4vp/attachments_test.go | 169 +++++++++++++++++- pkg/service/oidc4vp/oidc4vp_service.go | 21 ++- pkg/service/oidc4vp/oidc4vp_service_test.go | 57 ++++++ ...university_degree_remote_attachment.jsonld | 46 +++++ .../university_degree_with_attachments.jsonld | 74 ++++++++ 9 files changed, 397 insertions(+), 26 deletions(-) create mode 100644 pkg/service/oidc4vp/testdata/university_degree_remote_attachment.jsonld create mode 100644 pkg/service/oidc4vp/testdata/university_degree_with_attachments.jsonld diff --git a/cmd/vc-rest/startcmd/start.go b/cmd/vc-rest/startcmd/start.go index 4db0113fc..9eba538c9 100644 --- a/cmd/vc-rest/startcmd/start.go +++ b/cmd/vc-rest/startcmd/start.go @@ -958,6 +958,7 @@ func buildEchoHandler( ResponseURI: conf.StartupParameters.apiGatewayURL + oidc4VPCheckEndpoint, TokenLifetime: 15 * time.Minute, Metrics: metrics, + AttachmentService: oidc4vp.NewAttachmentService(getHTTPClient(metricsProvider.Attachments)), }) if conf.IsTraceEnabled { diff --git a/pkg/observability/metrics/provider.go b/pkg/observability/metrics/provider.go index dc82c30a2..83d00116b 100644 --- a/pkg/observability/metrics/provider.go +++ b/pkg/observability/metrics/provider.go @@ -49,6 +49,7 @@ type ClientID string const ( ClientPreAuth ClientID = "preauthorize" ClientIssuerProfile ClientID = "issuer-profile" + Attachments ClientID = "attachments" ClientVerifierProfile ClientID = "verifier-profile" ClientCredentialStatus ClientID = "credential-status" //nolint:gosec ClientOIDC4CI ClientID = "oidc4ci" diff --git a/pkg/service/oidc4vp/api.go b/pkg/service/oidc4vp/api.go index 67b27ddcb..f9b0b9d94 100644 --- a/pkg/service/oidc4vp/api.go +++ b/pkg/service/oidc4vp/api.go @@ -52,9 +52,10 @@ type CredentialMetadata struct { ExpirationDate *util.TimeWrapper `json:"expirationDate,omitempty"` CustomClaims map[string]Claims `json:"customClaims,omitempty"` - Name interface{} `json:"name,omitempty"` - AwardedDate interface{} `json:"awardedDate,omitempty"` - Description interface{} `json:"description,omitempty"` + Name interface{} `json:"name,omitempty"` + AwardedDate interface{} `json:"awardedDate,omitempty"` + Description interface{} `json:"description,omitempty"` + Attachments []map[string]interface{} `json:"attachments"` } type ServiceInterface interface { diff --git a/pkg/service/oidc4vp/attachments.go b/pkg/service/oidc4vp/attachments.go index e66abf66e..0f5a42695 100644 --- a/pkg/service/oidc4vp/attachments.go +++ b/pkg/service/oidc4vp/attachments.go @@ -15,6 +15,7 @@ import ( "fmt" "io" "net/http" + "sync" "github.com/samber/lo" "github.com/trustbloc/vc-go/util/maphelpers" @@ -45,33 +46,42 @@ func NewAttachmentService( } } -func (s *AttachmentService) PrepareAttachments( +func (s *AttachmentService) GetAttachments( ctx context.Context, - subjects []*verifiable.Subject, + subjects []verifiable.Subject, ) ([]map[string]interface{}, error) { var allAttachments []*Attachment for _, subject := range subjects { allAttachments = append(allAttachments, - s.findAttachments(subject.CustomFields, make([]*Attachment, 0))..., + s.findAttachments(subject.CustomFields)..., ) } + if len(allAttachments) == 0 { + return nil, nil + } + var final []map[string]interface{} + var wg sync.WaitGroup for _, attachment := range allAttachments { - cloned := maphelpers.CopyMap(attachment.Claim) // shallow copy - final = append(final, cloned) + attachment.Claim = maphelpers.CopyMap(attachment.Claim) // clone + final = append(final, attachment.Claim) if attachment.Type == AttachmentTypeRemote { + wg.Add(1) go func() { - err := s.handleRemoteAttachment(ctx, cloned) + defer wg.Done() + + err := s.handleRemoteAttachment(ctx, attachment.Claim) if err != nil { attachment.Claim["error"] = fmt.Sprintf("failed to handle remote attachment: %s", err) } }() } } + wg.Wait() return final, nil } @@ -114,16 +124,19 @@ func (s *AttachmentService) handleRemoteAttachment( func (s *AttachmentService) findAttachments( targetMap map[string]interface{}, - attachments []*Attachment, ) []*Attachment { - hasAttachment := false - for k, v := range targetMap { - if nested, ok := v.(map[string]interface{}); ok { - attachments = append(attachments, s.findAttachments(nested, attachments)...) - } + var attachments []*Attachment - if hasAttachment { - continue + for k, v := range targetMap { + switch valTyped := v.(type) { + case []interface{}: + for _, item := range valTyped { + if nested, ok := item.(map[string]interface{}); ok { + attachments = append(attachments, s.findAttachments(nested)...) + } + } + case map[string]interface{}: + attachments = append(attachments, s.findAttachments(valTyped)...) } if k != "type" && k != "@type" { @@ -137,8 +150,6 @@ func (s *AttachmentService) findAttachments( Type: typed, Claim: targetMap, }) - - hasAttachment = true } case []interface{}: newSlice := make([]string, 0, len(typed)) @@ -152,8 +163,6 @@ func (s *AttachmentService) findAttachments( Type: item, Claim: targetMap, }) - - hasAttachment = true } } case []string: @@ -163,8 +172,6 @@ func (s *AttachmentService) findAttachments( Type: item, Claim: targetMap, }) - - hasAttachment = true } } } diff --git a/pkg/service/oidc4vp/attachments_test.go b/pkg/service/oidc4vp/attachments_test.go index da6e37a76..5bb2433f2 100644 --- a/pkg/service/oidc4vp/attachments_test.go +++ b/pkg/service/oidc4vp/attachments_test.go @@ -1,11 +1,19 @@ package oidc4vp_test import ( + "bytes" "context" _ "embed" "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "sort" + "sync" "testing" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/trustbloc/vc-go/verifiable" @@ -15,6 +23,12 @@ import ( var ( //go:embed testdata/university_degree_embedded_attachment.jsonld sampleVCWithEmbeddedAttachment string + + //go:embed testdata/university_degree_remote_attachment.jsonld + sampleVCWithRemoteAttachment string + + //go:embed testdata/university_degree_with_attachments.jsonld + sampleVCWitAttachments string ) func TestAttachment(t *testing.T) { @@ -24,7 +38,7 @@ func TestAttachment(t *testing.T) { srv := oidc4vp.NewAttachmentService(nil) - resp, err := srv.PrepareAttachments(context.TODO(), []*verifiable.Subject{{CustomFields: data}}) + resp, err := srv.GetAttachments(context.TODO(), []verifiable.Subject{{CustomFields: data}}) assert.NoError(t, err) assert.Empty(t, resp) }) @@ -35,14 +49,165 @@ func TestAttachment(t *testing.T) { srv := oidc4vp.NewAttachmentService(nil) - resp, err := srv.PrepareAttachments(context.TODO(), []*verifiable.Subject{{CustomFields: data}}) + resp, err := srv.GetAttachments(context.TODO(), []verifiable.Subject{{CustomFields: data}}) + assert.NoError(t, err) + assert.Len(t, resp, 1) + + attachment := resp[0] + assert.EqualValues(t, []interface{}{"EmbeddedAttachment"}, attachment["type"]) + assert.EqualValues(t, "base64content", attachment["uri"]) + assert.Nil(t, attachment["error"]) + assert.EqualValues(t, "5d41402abc4b2a76b9719d911017c592", attachment["hash"]) + }) + + t.Run("with remote attachment err", func(t *testing.T) { + var data map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleVCWithRemoteAttachment), &data)) + + mockHttp := NewMockHttpClient(gomock.NewController(t)) + srv := oidc4vp.NewAttachmentService(mockHttp) + + mockHttp.EXPECT().Do(gomock.Any()). + DoAndReturn(func(request *http.Request) (*http.Response, error) { + assert.EqualValues(t, "https://someurl.local", request.URL.String()) + assert.EqualValues(t, "GET", request.Method) + + return nil, errors.New("connection failed") + }) + + resp, err := srv.GetAttachments(context.TODO(), []verifiable.Subject{{CustomFields: data}}) + assert.NoError(t, err) + assert.Len(t, resp, 1) + + attachment := resp[0] + assert.EqualValues(t, []interface{}{"RemoteAttachment"}, attachment["type"]) + assert.EqualValues(t, "https://someurl.local", attachment["uri"]) + assert.EqualValues(t, "failed to handle remote attachment: failed to fetch url: connection failed", + attachment["error"]) + }) + + t.Run("with remote attachment invalid status", func(t *testing.T) { + var data map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleVCWithRemoteAttachment), &data)) + + mockHttp := NewMockHttpClient(gomock.NewController(t)) + srv := oidc4vp.NewAttachmentService(mockHttp) + + mockHttp.EXPECT().Do(gomock.Any()). + DoAndReturn(func(request *http.Request) (*http.Response, error) { + assert.EqualValues(t, "https://someurl.local", request.URL.String()) + assert.EqualValues(t, "GET", request.Method) + + return &http.Response{ + Body: io.NopCloser(bytes.NewBuffer([]byte( + "file not found", + ))), + StatusCode: http.StatusNotFound, + }, nil + }) + + resp, err := srv.GetAttachments(context.TODO(), []verifiable.Subject{{CustomFields: data}}) + assert.NoError(t, err) + assert.Len(t, resp, 1) + + attachment := resp[0] + assert.EqualValues(t, []interface{}{"RemoteAttachment"}, attachment["type"]) + assert.EqualValues(t, "https://someurl.local", attachment["uri"]) + assert.EqualValues(t, "failed to handle remote attachment: unexpected status code: 404 and body file not found", + attachment["error"]) + }) + + t.Run("with embedded attachment as string type", func(t *testing.T) { + var data map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleVCWithEmbeddedAttachment), &data)) + + data["credentialSubject"].(map[string]interface{})["attachment1"].(map[string]interface{})["type"] = "EmbeddedAttachment" //nolint + srv := oidc4vp.NewAttachmentService(nil) + + resp, err := srv.GetAttachments(context.TODO(), []verifiable.Subject{{CustomFields: data}}) + assert.NoError(t, err) + assert.Len(t, resp, 1) + + attachment := resp[0] + assert.EqualValues(t, "EmbeddedAttachment", attachment["type"]) + assert.EqualValues(t, "base64content", attachment["uri"]) + assert.Nil(t, attachment["error"]) + assert.EqualValues(t, "5d41402abc4b2a76b9719d911017c592", attachment["hash"]) + }) + + t.Run("with embedded attachment as string arr", func(t *testing.T) { + var data map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleVCWithEmbeddedAttachment), &data)) + + data["credentialSubject"].(map[string]interface{})["attachment1"].(map[string]interface{})["type"] = []string{"EmbeddedAttachment"} //nolint + srv := oidc4vp.NewAttachmentService(nil) + + resp, err := srv.GetAttachments(context.TODO(), []verifiable.Subject{{CustomFields: data}}) assert.NoError(t, err) assert.Len(t, resp, 1) attachment := resp[0] + assert.EqualValues(t, []string{"EmbeddedAttachment"}, attachment["type"]) + assert.EqualValues(t, "base64content", attachment["uri"]) + assert.Nil(t, attachment["error"]) + assert.EqualValues(t, "5d41402abc4b2a76b9719d911017c592", attachment["hash"]) + }) + + t.Run("multiple attachments", func(t *testing.T) { + var data map[string]interface{} + assert.NoError(t, json.Unmarshal([]byte(sampleVCWitAttachments), &data)) + + mockHttp := NewMockHttpClient(gomock.NewController(t)) + srv := oidc4vp.NewAttachmentService(mockHttp) + + var mut sync.Mutex + urlsCalled := []string{} + + mockHttp.EXPECT().Do(gomock.Any()). + DoAndReturn(func(request *http.Request) (*http.Response, error) { + mut.Lock() + urlsCalled = append(urlsCalled, request.URL.String()) + defer mut.Unlock() + assert.EqualValues(t, "GET", request.Method) + + return &http.Response{ + Body: io.NopCloser(bytes.NewBuffer([]byte( + fmt.Sprintf("base64content-%s", request.URL.String()), + ))), + StatusCode: http.StatusOK, + }, nil + }).Times(2) + + resp, err := srv.GetAttachments(context.TODO(), []verifiable.Subject{{CustomFields: data}}) + assert.NoError(t, err) + assert.Len(t, resp, 3) + + sort.Strings(urlsCalled) + assert.EqualValues(t, []string{"https://localhost/cat.png", "https://localhost/photo.png"}, urlsCalled) + + sort.Slice(resp, func(i, j int) bool { + return resp[i]["id"].(string) < resp[j]["id"].(string) + }) + + attachment := resp[1] + assert.EqualValues(t, "doc12", attachment["id"]) assert.EqualValues(t, []interface{}{"EmbeddedAttachment"}, attachment["type"]) assert.EqualValues(t, "base64content", attachment["uri"]) assert.Nil(t, attachment["error"]) assert.EqualValues(t, "5d41402abc4b2a76b9719d911017c592", attachment["hash"]) + + attachment = resp[0] + assert.EqualValues(t, "doc1", attachment["id"]) + assert.EqualValues(t, []interface{}{"RemoteAttachment"}, attachment["type"]) + assert.EqualValues(t, "YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9jYXQucG5n", attachment["uri"]) + assert.Nil(t, attachment["error"]) + assert.EqualValues(t, "abcd", attachment["hash"]) + + attachment = resp[2] + assert.EqualValues(t, "doc445", attachment["id"]) + assert.EqualValues(t, []interface{}{"RemoteAttachment"}, attachment["type"]) + assert.EqualValues(t, "YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9waG90by5wbmc=", attachment["uri"]) + assert.Nil(t, attachment["error"]) + assert.EqualValues(t, "xyz", attachment["hash"]) }) } diff --git a/pkg/service/oidc4vp/oidc4vp_service.go b/pkg/service/oidc4vp/oidc4vp_service.go index ac2e98e62..69f3711a9 100644 --- a/pkg/service/oidc4vp/oidc4vp_service.go +++ b/pkg/service/oidc4vp/oidc4vp_service.go @@ -4,7 +4,7 @@ Copyright SecureKey Technologies Inc. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -//go:generate mockgen -destination oidc4vp_service_mocks_test.go -self_package mocks -package oidc4vp_test -source=oidc4vp_service.go -mock_names transactionManager=MockTransactionManager,events=MockEvents,kmsRegistry=MockKMSRegistry,requestObjectStore=MockRequestObjectStore,profileService=MockProfileService,presentationVerifier=MockPresentationVerifier,trustRegistry=MockTrustRegistry +//go:generate mockgen -destination oidc4vp_service_mocks_test.go -self_package mocks -package oidc4vp_test -source=oidc4vp_service.go -mock_names transactionManager=MockTransactionManager,events=MockEvents,kmsRegistry=MockKMSRegistry,requestObjectStore=MockRequestObjectStore,profileService=MockProfileService,presentationVerifier=MockPresentationVerifier,trustRegistry=MockTrustRegistry,attachmentService=MockAttachmentService package oidc4vp @@ -97,6 +97,13 @@ type profileService interface { GetProfile(profileID profileapi.ID, profileVersion profileapi.Version) (*profileapi.Verifier, error) } +type attachmentService interface { + GetAttachments( + ctx context.Context, + subjects []verifiable.Subject, + ) ([]map[string]interface{}, error) +} + type presentationVerifier interface { VerifyPresentation( ctx context.Context, @@ -130,6 +137,7 @@ type Config struct { ResponseURI string TokenLifetime time.Duration Metrics metricsProvider + AttachmentService attachmentService } type Service struct { @@ -143,6 +151,7 @@ type Service struct { presentationVerifier presentationVerifier vdr vdrapi.Registry trustRegistry trustRegistry + attachmentService attachmentService responseURI string tokenLifetime time.Duration @@ -171,6 +180,7 @@ func NewService(cfg *Config) *Service { vdr: cfg.VDR, trustRegistry: cfg.TrustRegistry, metrics: metrics, + attachmentService: cfg.AttachmentService, } } @@ -590,6 +600,15 @@ func (s *Service) RetrieveClaims( credMeta.Issuer = verifiable.IssuerToJSON(*credContents.Issuer) } + if s.attachmentService != nil { + att, attErr := s.attachmentService.GetAttachments(ctx, credContents.Subject) + if attErr != nil { + logger.Errorc(ctx, fmt.Sprintf("Failed to get attachments: %+v", attErr)) + } + + credMeta.Attachments = att + } + result[credContents.ID] = credMeta } diff --git a/pkg/service/oidc4vp/oidc4vp_service_test.go b/pkg/service/oidc4vp/oidc4vp_service_test.go index 8845c13a1..c292481ac 100644 --- a/pkg/service/oidc4vp/oidc4vp_service_test.go +++ b/pkg/service/oidc4vp/oidc4vp_service_test.go @@ -1050,6 +1050,63 @@ func TestService_RetrieveClaims(t *testing.T) { require.Empty(t, claims["_scope"]) }) + t.Run("Success JsonLD with attachments", func(t *testing.T) { + mockEventSvc := NewMockeventService(gomock.NewController(t)) + mockEventSvc.EXPECT().Publish(gomock.Any(), spi.VerifierEventTopic, gomock.Any()).DoAndReturn( + expectedPublishEventFunc(t, spi.VerifierOIDCInteractionClaimsRetrieved, nil), + ) + + attachmentSvc := NewMockAttachmentService(gomock.NewController(t)) + + svc := oidc4vp.NewService(&oidc4vp.Config{ + EventSvc: mockEventSvc, + EventTopic: spi.VerifierEventTopic, + AttachmentService: attachmentSvc, + }) + ldvc, err := verifiable.ParseCredential([]byte(sampleVCJsonLD), + verifiable.WithJSONLDDocumentLoader(loader), + verifiable.WithDisabledProofCheck()) + + attachmentVals := []map[string]interface{}{ + { + "id": 123, + "uri": "base64-content", + }, + { + "id": 456, + "uri": "base64-content2", + }, + } + + attachmentSvc.EXPECT().GetAttachments(gomock.Any(), gomock.Any()). + DoAndReturn(func(ctx context.Context, subjects []verifiable.Subject) ([]map[string]interface{}, error) { + require.Len(t, subjects, 1) + require.EqualValues(t, ldvc.Contents().Subject[0], subjects[0]) + + return attachmentVals, errors.New("ignored") + }) + + require.NoError(t, err) + + claims := svc.RetrieveClaims(context.Background(), &oidc4vp.Transaction{ + ReceivedClaims: &oidc4vp.ReceivedClaims{Credentials: []*verifiable.Credential{ + ldvc, + }}}, &profileapi.Verifier{}) + + require.NotNil(t, claims) + subjects, ok := claims["http://example.gov/credentials/3732"].SubjectData.([]map[string]interface{}) + + require.True(t, ok) + require.Equal(t, "did:example:ebfeb1f712ebc6f1c276e12ec21", subjects[0]["id"]) + + require.EqualValues(t, attachmentVals, claims["http://example.gov/credentials/3732"].Attachments) + require.NotEmpty(t, claims["http://example.gov/credentials/3732"].Issuer) + require.NotEmpty(t, claims["http://example.gov/credentials/3732"].IssuanceDate) + require.NotEmpty(t, claims["http://example.gov/credentials/3732"].ExpirationDate) + + require.Empty(t, claims["_scope"]) + }) + t.Run("Empty claims", func(t *testing.T) { mockEventSvc := NewMockeventService(gomock.NewController(t)) mockEventSvc.EXPECT().Publish(gomock.Any(), spi.VerifierEventTopic, gomock.Any()).DoAndReturn( diff --git a/pkg/service/oidc4vp/testdata/university_degree_remote_attachment.jsonld b/pkg/service/oidc4vp/testdata/university_degree_remote_attachment.jsonld new file mode 100644 index 000000000..dad47342e --- /dev/null +++ b/pkg/service/oidc4vp/testdata/university_degree_remote_attachment.jsonld @@ -0,0 +1,46 @@ +{ + "@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", + "expirationDate": "2030-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", + "attachment1" : { + "id": "doc1", + "type": [ + "RemoteAttachment" + ], + "mimeType": "image/jpeg", + "uri": "https://someurl.local", + "description": "Scanned copy of something", + "hash": "5d41402abc4b2a76b9719d911017c592", + "hash-alg": "SHA-256" + } + } +} diff --git a/pkg/service/oidc4vp/testdata/university_degree_with_attachments.jsonld b/pkg/service/oidc4vp/testdata/university_degree_with_attachments.jsonld new file mode 100644 index 000000000..b67252ec8 --- /dev/null +++ b/pkg/service/oidc4vp/testdata/university_degree_with_attachments.jsonld @@ -0,0 +1,74 @@ +{ + "@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", + "expirationDate": "2030-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", + "attachment1": { + "id": "doc12", + "type": [ + "EmbeddedAttachment" + ], + "mimeType": "image/jpeg", + "uri": "base64content", + "description": "Scanned copy of something", + "hash": "5d41402abc4b2a76b9719d911017c592", + "hash-alg": "SHA-256" + }, + "obj2": { + "nested1": { + "arr": [ + { + "id": "doc1", + "type": [ + "RemoteAttachment" + ], + "mimeType": "image/png", + "uri": "https://localhost/cat.png", + "description": "Description 1", + "hash" : "abcd" + } + ] + } + }, + "some_object": { + "photo": { + "id": "doc445", + "type": [ + "RemoteAttachment" + ], + "mimeType": "image/png", + "uri": "https://localhost/photo.png", + "description": "Description 1", + "hash" : "xyz" + } + } + } +} \ No newline at end of file From b80c435eca5d315a5ecaacf2366a581a85769a8f Mon Sep 17 00:00:00 2001 From: Stas Dm Date: Mon, 1 Jul 2024 14:18:29 +0200 Subject: [PATCH 4/7] feat: attach logic --- pkg/service/oidc4vp/attachments.go | 5 ++++- pkg/service/oidc4vp/attachments_test.go | 7 +++++-- test/bdd/features/oidc4vc_api.feature | 23 +++++++++++++++++++++++ test/bdd/pkg/v1/oidc4vc/oidc4vci.go | 7 +++++++ test/bdd/pkg/v1/oidc4vc/steps.go | 4 ++++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/pkg/service/oidc4vp/attachments.go b/pkg/service/oidc4vp/attachments.go index 0f5a42695..2640c16ed 100644 --- a/pkg/service/oidc4vp/attachments.go +++ b/pkg/service/oidc4vp/attachments.go @@ -117,7 +117,10 @@ func (s *AttachmentService) handleRemoteAttachment( return fmt.Errorf("unexpected status code: %d and body %v", resp.StatusCode, string(body)) } - attachment[AttachmentDataField] = base64.StdEncoding.EncodeToString(body) // todo prefix type + attachment[AttachmentDataField] = fmt.Sprintf("data:%s;base64,%s", + resp.Header.Get("Content-Type"), + base64.StdEncoding.EncodeToString(body), + ) return nil } diff --git a/pkg/service/oidc4vp/attachments_test.go b/pkg/service/oidc4vp/attachments_test.go index 5bb2433f2..a0b7d7f39 100644 --- a/pkg/service/oidc4vp/attachments_test.go +++ b/pkg/service/oidc4vp/attachments_test.go @@ -174,6 +174,9 @@ func TestAttachment(t *testing.T) { Body: io.NopCloser(bytes.NewBuffer([]byte( fmt.Sprintf("base64content-%s", request.URL.String()), ))), + Header: map[string][]string{ + "Content-Type": {"image/svg"}, + }, StatusCode: http.StatusOK, }, nil }).Times(2) @@ -199,14 +202,14 @@ func TestAttachment(t *testing.T) { attachment = resp[0] assert.EqualValues(t, "doc1", attachment["id"]) assert.EqualValues(t, []interface{}{"RemoteAttachment"}, attachment["type"]) - assert.EqualValues(t, "YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9jYXQucG5n", attachment["uri"]) + assert.EqualValues(t, "data:image/svg;base64,YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9jYXQucG5n", attachment["uri"]) assert.Nil(t, attachment["error"]) assert.EqualValues(t, "abcd", attachment["hash"]) attachment = resp[2] assert.EqualValues(t, "doc445", attachment["id"]) assert.EqualValues(t, []interface{}{"RemoteAttachment"}, attachment["type"]) - assert.EqualValues(t, "YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9waG90by5wbmc=", attachment["uri"]) + assert.EqualValues(t, "data:image/svg;base64,YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9waG90by5wbmc=", attachment["uri"]) assert.Nil(t, attachment["error"]) assert.EqualValues(t, "xyz", attachment["hash"]) }) diff --git a/test/bdd/features/oidc4vc_api.feature b/test/bdd/features/oidc4vc_api.feature index 2e9fbd3b9..1008e04f5 100644 --- a/test/bdd/features/oidc4vc_api.feature +++ b/test/bdd/features/oidc4vc_api.feature @@ -200,6 +200,29 @@ Feature: OIDC4VC REST API | acme_issuer/v1.0 | UniversityDegreeCredential | | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | jwt | ewogICJAY29udGV4dCI6IFsKICAgICJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsCiAgICAiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiLAogICAgImh0dHBzOi8vdzNpZC5vcmcvdmMvc3RhdHVzLWxpc3QvMjAyMS92MSIKICBdLAogICJjcmVkZW50aWFsU3RhdHVzIjogewogICAgImlkIjogInVybjp1dWlkOjViYjIwNTU4LTI1Y2UtNDBiNS04MGZjLThhZjY5MWVlZGNjZSIsCiAgICAic3RhdHVzTGlzdENyZWRlbnRpYWwiOiAiaHR0cDovL3ZjLXJlc3QtZWNoby50cnVzdGJsb2MubG9jYWw6ODA3NS9pc3N1ZXIvZ3JvdXBzL2dyb3VwX2FjbWVfaXNzdWVyL2NyZWRlbnRpYWxzL3N0YXR1cy8wNmIxM2U5Mi0yN2E2LTRiNzYtOTk3Ny02ZWNlMjA3NzgzZGQiLAogICAgInN0YXR1c0xpc3RJbmRleCI6ICI2NTQ4IiwKICAgICJzdGF0dXNQdXJwb3NlIjogInJldm9jYXRpb24iLAogICAgInR5cGUiOiAiU3RhdHVzTGlzdDIwMjFFbnRyeSIKICB9LAogICJjcmVkZW50aWFsU3ViamVjdCI6IHsKICAgICJkZWdyZWUiOiB7CiAgICAgICJkZWdyZWUiOiAiTUlUIiwKICAgICAgInR5cGUiOiAiQmFjaGVsb3JEZWdyZWUiCiAgICB9LAogICAgImlkIjogImRpZDppb246RWlCeVFBeFhtT0FFTTdUbEVVRzl1NlJyMnJzNVFDTHh1T2ZWbE5ONXpUM1JtUTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKaFpHUXRjSFZpYkdsakxXdGxlWE1pTENKd2RXSnNhV05MWlhseklqcGJleUpwWkNJNklsOXVWVGx5YmtGcGNIZzNNRmhYU0RSS2RVcDNUMVZwV2paNFNVWk5ZVEZuUm1oS1VWVXdWSFoyTTFFaUxDSndkV0pzYVdOTFpYbEtkMnNpT25zaVkzSjJJam9pVUMwek9EUWlMQ0pyYVdRaU9pSmZibFU1Y201QmFYQjROekJZVjBnMFNuVktkMDlWYVZvMmVFbEdUV0V4WjBab1NsRlZNRlIyZGpOUklpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaU5sYzNkSEZhTVhabFRUZFpTRTU1ZEROME9YaE1PVGRNVDBoelp6a3libTV3WDJSc2FXTlFjVlJsY2pSdFNYaFplVFZ4TTA1MmVVa3djekkxV2tsRk9DSXNJbmtpT2lKRGExUlhaaTFZVlZwNkxWZDJOekl0TlhCdk9XZHBjblo2U25kNE1uRlRRMGt5Wm1GNWJIZFhjbXR0YW5wWE16RkVjV0ZIY2paRldrUXRkVmxEVTJkT0luMHNJbkIxY25CdmMyVnpJanBiSW1GMWRHaGxiblJwWTJGMGFXOXVJaXdpWVhOelpYSjBhVzl1VFdWMGFHOWtJbDBzSW5SNWNHVWlPaUpLYzI5dVYyVmlTMlY1TWpBeU1DSjlYWDFkTENKMWNHUmhkR1ZEYjIxdGFYUnRaVzUwSWpvaVJXbERNbWh0YzJSRk5IRk9kMjFUUTFCbU1qRjFNRXg2UWtodmMzQldVRkZzT1ZKdFRHczNORloxWnpWcmR5SjlMQ0p6ZFdabWFYaEVZWFJoSWpwN0ltUmxiSFJoU0dGemFDSTZJa1ZwUVMxWk9YVjBUVTVoYVVoVlIxcHROVEJtUVZKamNWTmFjVWRCTXpkNlFsWlVOVVk0UzBsdFh6TlRka0VpTENKeVpXTnZkbVZ5ZVVOdmJXMXBkRzFsYm5RaU9pSkZhVUpSYlVOaWRsOVZRelZSUmtOMk9VTndiMUpQYVZNNWJIWkNkbmR1WTJsWkxWOTRjbEU0U1RKdlIwSm5JbjBzSW5SNWNHVWlPaUpqY21WaGRHVWlmUSIsCiAgICAibmFtZSI6ICJKYXlkZW4gRG9lIiwKICAgICJzcG91c2UiOiAiZGlkOmV4YW1wbGU6YzI3NmUxMmVjMjFlYmZlYjFmNzEyZWJjNmYxIgogIH0sCiAgImV4cGlyYXRpb25EYXRlIjogIjIwMjUtMDMtMThUMjI6MDY6MzEuMzQyNTAxMDZaIiwKICAiaWQiOiAidXJuOnV1aWQ6N2NiYjliYTItMzgwMS00Nzg2LWI3YzEtYWQyZDQyOTI0M2E4IiwKICAiaXNzdWFuY2VEYXRlIjogIjIwMjQtMDMtMThUMjI6MDY6MzEuMzc1NDM0MTMxWiIsCiAgImlzc3VlciI6IHsKICAgICJpZCI6ICJkaWQ6aW9uOkVpQW9hSEpZRjJRNms5eml5aDRQRHJ1c0ladE5VS20xLWFGckF2clBEQ0VNU1E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSmhaR1F0Y0hWaWJHbGpMV3RsZVhNaUxDSndkV0pzYVdOTFpYbHpJanBiZXlKcFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpsSWpvaVFWRkJRaUlzSW10cFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW10MGVTSTZJbEpUUVNJc0ltNGlPaUp1V1RCaU5tRkdURTVWYW1RM2VFRnBiVnA0ZFVnd1prNVNUVWN6WldOSFdHOVpTM1JSV0RSMVMxRmpjMWxaVTI1R2FFRkRlRGhtYW5Cbk5qSkplVnBDZFZSUVlUWnVaM04yWDA1UlFraFVXVWxNUmpKUldWVlFhVFJRVkVzdGVVRTNUbVJQU2xad05VRkZWelZ5YzNsc1pUZ3dTeTFQVUdsVVVXMUtSMng1VWpoSlVrWklOazl0TTNNMllYTnRXVmw1WmtrdExXdFJaVGhJYTBkMGJYYzNUR2xpVldWWlduZDNSemxJU1d4NlNEUlFObWhJVDNOVVpsaGhSRmwyYkRVM1dXcDFibWd5Y1ZVeVlXbFhSRms1UVdsVVVrSnBiVXhDYUVsdVpuWTBOVTVZZVRsMWQzSkdPWE5aYkZodWJHZDViVlZUTVZGV2NUYzVWUzFsYmt0bVJYTXpSMnMyUlVSaVYzSkhiWHBIY0hSUFpHcHpkRWt3T1dWNGFWUklPRXRyZG5oT1JXaFFla3hrZW5rd1VGTmxSV1o0WDJsVFdXeE1aRmxKUmtWVU4yb3hNVTV2TFRGeU1rVjZRa05wVVRWdWRGSXhNMUVpZlN3aWNIVnljRzl6WlhNaU9sc2lZWFYwYUdWdWRHbGpZWFJwYjI0aUxDSmhjM05sY25ScGIyNU5aWFJvYjJRaVhTd2lkSGx3WlNJNklrcHpiMjVYWldKTFpYa3lNREl3SW4xZGZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVORVJsRlFabmhTUm5Oc2NrRkliVm80YVhOUWJFNDRRelpLVW1SemVuUTBabkkwYURsSU5HUm1NVlpuSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEJTRE14TkZkcVJUTkJhVFJoTFhNeVNtOUhjWFpqUzFWcFRFUmZNVEpWWjNWeFkwWTFiVWR6Um5aMFp5SXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFTMXBka1pxVFRaTGRteDJZVXRVT0dkQ2EzQkphelpMWmt4dk9VbDVNR28wWVdobWFpMXJUMGwyV2xFaWZTd2lkSGx3WlNJNkltTnlaV0YwWlNKOSIsCiAgICAibmFtZSI6ICJBY21lIElzc3VlciIKICB9LAogICJ0eXBlIjogWwogICAgIlZlcmlmaWFibGVDcmVkZW50aWFsIiwKICAgICJVbml2ZXJzaXR5RGVncmVlQ3JlZGVudGlhbCIKICBdCn0= | | acme_issuer_no_template/v1.0 | UniversityDegreeCredential | | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | jwt | ewogICJAY29udGV4dCI6IFsKICAgICJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsCiAgICAiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiLAogICAgImh0dHBzOi8vdzNpZC5vcmcvdmMvc3RhdHVzLWxpc3QvMjAyMS92MSIKICBdLAogICJjcmVkZW50aWFsU3RhdHVzIjogewogICAgImlkIjogInVybjp1dWlkOjViYjIwNTU4LTI1Y2UtNDBiNS04MGZjLThhZjY5MWVlZGNjZSIsCiAgICAic3RhdHVzTGlzdENyZWRlbnRpYWwiOiAiaHR0cDovL3ZjLXJlc3QtZWNoby50cnVzdGJsb2MubG9jYWw6ODA3NS9pc3N1ZXIvZ3JvdXBzL2dyb3VwX2FjbWVfaXNzdWVyL2NyZWRlbnRpYWxzL3N0YXR1cy8wNmIxM2U5Mi0yN2E2LTRiNzYtOTk3Ny02ZWNlMjA3NzgzZGQiLAogICAgInN0YXR1c0xpc3RJbmRleCI6ICI2NTQ4IiwKICAgICJzdGF0dXNQdXJwb3NlIjogInJldm9jYXRpb24iLAogICAgInR5cGUiOiAiU3RhdHVzTGlzdDIwMjFFbnRyeSIKICB9LAogICJjcmVkZW50aWFsU3ViamVjdCI6IHsKICAgICJkZWdyZWUiOiB7CiAgICAgICJkZWdyZWUiOiAiTUlUIiwKICAgICAgInR5cGUiOiAiQmFjaGVsb3JEZWdyZWUiCiAgICB9LAogICAgImlkIjogImRpZDppb246RWlCeVFBeFhtT0FFTTdUbEVVRzl1NlJyMnJzNVFDTHh1T2ZWbE5ONXpUM1JtUTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKaFpHUXRjSFZpYkdsakxXdGxlWE1pTENKd2RXSnNhV05MWlhseklqcGJleUpwWkNJNklsOXVWVGx5YmtGcGNIZzNNRmhYU0RSS2RVcDNUMVZwV2paNFNVWk5ZVEZuUm1oS1VWVXdWSFoyTTFFaUxDSndkV0pzYVdOTFpYbEtkMnNpT25zaVkzSjJJam9pVUMwek9EUWlMQ0pyYVdRaU9pSmZibFU1Y201QmFYQjROekJZVjBnMFNuVktkMDlWYVZvMmVFbEdUV0V4WjBab1NsRlZNRlIyZGpOUklpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaU5sYzNkSEZhTVhabFRUZFpTRTU1ZEROME9YaE1PVGRNVDBoelp6a3libTV3WDJSc2FXTlFjVlJsY2pSdFNYaFplVFZ4TTA1MmVVa3djekkxV2tsRk9DSXNJbmtpT2lKRGExUlhaaTFZVlZwNkxWZDJOekl0TlhCdk9XZHBjblo2U25kNE1uRlRRMGt5Wm1GNWJIZFhjbXR0YW5wWE16RkVjV0ZIY2paRldrUXRkVmxEVTJkT0luMHNJbkIxY25CdmMyVnpJanBiSW1GMWRHaGxiblJwWTJGMGFXOXVJaXdpWVhOelpYSjBhVzl1VFdWMGFHOWtJbDBzSW5SNWNHVWlPaUpLYzI5dVYyVmlTMlY1TWpBeU1DSjlYWDFkTENKMWNHUmhkR1ZEYjIxdGFYUnRaVzUwSWpvaVJXbERNbWh0YzJSRk5IRk9kMjFUUTFCbU1qRjFNRXg2UWtodmMzQldVRkZzT1ZKdFRHczNORloxWnpWcmR5SjlMQ0p6ZFdabWFYaEVZWFJoSWpwN0ltUmxiSFJoU0dGemFDSTZJa1ZwUVMxWk9YVjBUVTVoYVVoVlIxcHROVEJtUVZKamNWTmFjVWRCTXpkNlFsWlVOVVk0UzBsdFh6TlRka0VpTENKeVpXTnZkbVZ5ZVVOdmJXMXBkRzFsYm5RaU9pSkZhVUpSYlVOaWRsOVZRelZSUmtOMk9VTndiMUpQYVZNNWJIWkNkbmR1WTJsWkxWOTRjbEU0U1RKdlIwSm5JbjBzSW5SNWNHVWlPaUpqY21WaGRHVWlmUSIsCiAgICAibmFtZSI6ICJKYXlkZW4gRG9lIiwKICAgICJzcG91c2UiOiAiZGlkOmV4YW1wbGU6YzI3NmUxMmVjMjFlYmZlYjFmNzEyZWJjNmYxIgogIH0sCiAgImV4cGlyYXRpb25EYXRlIjogIjIwMjUtMDMtMThUMjI6MDY6MzEuMzQyNTAxMDZaIiwKICAiaWQiOiAidXJuOnV1aWQ6N2NiYjliYTItMzgwMS00Nzg2LWI3YzEtYWQyZDQyOTI0M2E4IiwKICAiaXNzdWFuY2VEYXRlIjogIjIwMjQtMDMtMThUMjI6MDY6MzEuMzc1NDM0MTMxWiIsCiAgImlzc3VlciI6IHsKICAgICJpZCI6ICJkaWQ6aW9uOkVpQW9hSEpZRjJRNms5eml5aDRQRHJ1c0ladE5VS20xLWFGckF2clBEQ0VNU1E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSmhaR1F0Y0hWaWJHbGpMV3RsZVhNaUxDSndkV0pzYVdOTFpYbHpJanBiZXlKcFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpsSWpvaVFWRkJRaUlzSW10cFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW10MGVTSTZJbEpUUVNJc0ltNGlPaUp1V1RCaU5tRkdURTVWYW1RM2VFRnBiVnA0ZFVnd1prNVNUVWN6WldOSFdHOVpTM1JSV0RSMVMxRmpjMWxaVTI1R2FFRkRlRGhtYW5Cbk5qSkplVnBDZFZSUVlUWnVaM04yWDA1UlFraFVXVWxNUmpKUldWVlFhVFJRVkVzdGVVRTNUbVJQU2xad05VRkZWelZ5YzNsc1pUZ3dTeTFQVUdsVVVXMUtSMng1VWpoSlVrWklOazl0TTNNMllYTnRXVmw1WmtrdExXdFJaVGhJYTBkMGJYYzNUR2xpVldWWlduZDNSemxJU1d4NlNEUlFObWhJVDNOVVpsaGhSRmwyYkRVM1dXcDFibWd5Y1ZVeVlXbFhSRms1UVdsVVVrSnBiVXhDYUVsdVpuWTBOVTVZZVRsMWQzSkdPWE5aYkZodWJHZDViVlZUTVZGV2NUYzVWUzFsYmt0bVJYTXpSMnMyUlVSaVYzSkhiWHBIY0hSUFpHcHpkRWt3T1dWNGFWUklPRXRyZG5oT1JXaFFla3hrZW5rd1VGTmxSV1o0WDJsVFdXeE1aRmxKUmtWVU4yb3hNVTV2TFRGeU1rVjZRa05wVVRWdWRGSXhNMUVpZlN3aWNIVnljRzl6WlhNaU9sc2lZWFYwYUdWdWRHbGpZWFJwYjI0aUxDSmhjM05sY25ScGIyNU5aWFJvYjJRaVhTd2lkSGx3WlNJNklrcHpiMjVYWldKTFpYa3lNREl3SW4xZGZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVORVJsRlFabmhTUm5Oc2NrRkliVm80YVhOUWJFNDRRelpLVW1SemVuUTBabkkwYURsSU5HUm1NVlpuSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEJTRE14TkZkcVJUTkJhVFJoTFhNeVNtOUhjWFpqUzFWcFRFUmZNVEpWWjNWeFkwWTFiVWR6Um5aMFp5SXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFTMXBka1pxVFRaTGRteDJZVXRVT0dkQ2EzQkphelpMWmt4dk9VbDVNR28wWVdobWFpMXJUMGwyV2xFaWZTd2lkSGx3WlNJNkltTnlaV0YwWlNKOSIsCiAgICAibmFtZSI6ICJBY21lIElzc3VlciIKICB9LAogICJ0eXBlIjogWwogICAgIlZlcmlmaWFibGVDcmVkZW50aWFsIiwKICAgICJVbml2ZXJzaXR5RGVncmVlQ3JlZGVudGlhbCIKICBdCn0= | + @oidc4vc_rest_pre_auth_flow_compose_with_attachment + 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 "" + And initiateIssuanceVersion is "2" + And credentialCompose is active with "" + + When User interacts with Wallet to initiate credential issuance using pre authorization code flow + Then "1" credentials are issued + Then User interacts with Verifier and initiate OIDC4VP interaction under "" profile with presentation definition ID "" and fields "" + And expected attachment for vp flow is "a" + And expected attachment for vp flow is "b" + 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 | credentialEncoded | + | acme_issuer/v1.0 | UniversityDegreeCredential | | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | jwt | ewogICJAY29udGV4dCI6IFsKICAgICJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsCiAgICAiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiLAogICAgImh0dHBzOi8vdzNpZC5vcmcvdmMvc3RhdHVzLWxpc3QvMjAyMS92MSIKICBdLAogICJjcmVkZW50aWFsU3RhdHVzIjogewogICAgImlkIjogInVybjp1dWlkOjViYjIwNTU4LTI1Y2UtNDBiNS04MGZjLThhZjY5MWVlZGNjZSIsCiAgICAic3RhdHVzTGlzdENyZWRlbnRpYWwiOiAiaHR0cDovL3ZjLXJlc3QtZWNoby50cnVzdGJsb2MubG9jYWw6ODA3NS9pc3N1ZXIvZ3JvdXBzL2dyb3VwX2FjbWVfaXNzdWVyL2NyZWRlbnRpYWxzL3N0YXR1cy8wNmIxM2U5Mi0yN2E2LTRiNzYtOTk3Ny02ZWNlMjA3NzgzZGQiLAogICAgInN0YXR1c0xpc3RJbmRleCI6ICI2NTQ4IiwKICAgICJzdGF0dXNQdXJwb3NlIjogInJldm9jYXRpb24iLAogICAgInR5cGUiOiAiU3RhdHVzTGlzdDIwMjFFbnRyeSIKICB9LAogICJjcmVkZW50aWFsU3ViamVjdCI6IHsKICAgICJkZWdyZWUiOiB7CiAgICAgICJkZWdyZWUiOiAiTUlUIiwKICAgICAgInR5cGUiOiAiQmFjaGVsb3JEZWdyZWUiCiAgICB9LAogICAgInBob3RvIjogewogICAgICAiaWQiOiAiZG9jNDQ1IiwKICAgICAgInR5cGUiOiBbCiAgICAgICAgIkVtYmVkZGVkQXR0YWNobWVudCIKICAgICAgXSwKICAgICAgIm1pbWVUeXBlIjogImltYWdlL3BuZyIsCiAgICAgICJ1cmkiOiAiZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dva0pnIiwKICAgICAgImRlc2NyaXB0aW9uIjogImIiCiAgICB9LAogICAgInJlbW90ZSI6IHsKICAgICAgImlkIjogInJlbW90ZV9kb2MiLAogICAgICAidHlwZSI6IFsKICAgICAgICAiUmVtb3RlQXR0YWNobWVudCIKICAgICAgXSwKICAgICAgIm1pbWVUeXBlIjogImltYWdlL3N2ZyIsCiAgICAgICJ1cmkiOiAiaHR0cHM6Ly93d3cudzMub3JnL2Fzc2V0cy9sb2dvcy93M2MvdzNjLW5vLWJhcnMuc3ZnIiwKICAgICAgImRlc2NyaXB0aW9uIjogImEiCiAgICB9LAogICAgImlkIjogImRpZDppb246RWlCeVFBeFhtT0FFTTdUbEVVRzl1NlJyMnJzNVFDTHh1T2ZWbE5ONXpUM1JtUTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKaFpHUXRjSFZpYkdsakxXdGxlWE1pTENKd2RXSnNhV05MWlhseklqcGJleUpwWkNJNklsOXVWVGx5YmtGcGNIZzNNRmhYU0RSS2RVcDNUMVZwV2paNFNVWk5ZVEZuUm1oS1VWVXdWSFoyTTFFaUxDSndkV0pzYVdOTFpYbEtkMnNpT25zaVkzSjJJam9pVUMwek9EUWlMQ0pyYVdRaU9pSmZibFU1Y201QmFYQjROekJZVjBnMFNuVktkMDlWYVZvMmVFbEdUV0V4WjBab1NsRlZNRlIyZGpOUklpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaU5sYzNkSEZhTVhabFRUZFpTRTU1ZEROME9YaE1PVGRNVDBoelp6a3libTV3WDJSc2FXTlFjVlJsY2pSdFNYaFplVFZ4TTA1MmVVa3djekkxV2tsRk9DSXNJbmtpT2lKRGExUlhaaTFZVlZwNkxWZDJOekl0TlhCdk9XZHBjblo2U25kNE1uRlRRMGt5Wm1GNWJIZFhjbXR0YW5wWE16RkVjV0ZIY2paRldrUXRkVmxEVTJkT0luMHNJbkIxY25CdmMyVnpJanBiSW1GMWRHaGxiblJwWTJGMGFXOXVJaXdpWVhOelpYSjBhVzl1VFdWMGFHOWtJbDBzSW5SNWNHVWlPaUpLYzI5dVYyVmlTMlY1TWpBeU1DSjlYWDFkTENKMWNHUmhkR1ZEYjIxdGFYUnRaVzUwSWpvaVJXbERNbWh0YzJSRk5IRk9kMjFUUTFCbU1qRjFNRXg2UWtodmMzQldVRkZzT1ZKdFRHczNORloxWnpWcmR5SjlMQ0p6ZFdabWFYaEVZWFJoSWpwN0ltUmxiSFJoU0dGemFDSTZJa1ZwUVMxWk9YVjBUVTVoYVVoVlIxcHROVEJtUVZKamNWTmFjVWRCTXpkNlFsWlVOVVk0UzBsdFh6TlRka0VpTENKeVpXTnZkbVZ5ZVVOdmJXMXBkRzFsYm5RaU9pSkZhVUpSYlVOaWRsOVZRelZSUmtOMk9VTndiMUpQYVZNNWJIWkNkbmR1WTJsWkxWOTRjbEU0U1RKdlIwSm5JbjBzSW5SNWNHVWlPaUpqY21WaGRHVWlmUSIsCiAgICAibmFtZSI6ICJKYXlkZW4gRG9lIiwKICAgICJzcG91c2UiOiAiZGlkOmV4YW1wbGU6YzI3NmUxMmVjMjFlYmZlYjFmNzEyZWJjNmYxIgogIH0sCiAgImV4cGlyYXRpb25EYXRlIjogIjIwMjUtMDMtMThUMjI6MDY6MzEuMzQyNTAxMDZaIiwKICAiaWQiOiAidXJuOnV1aWQ6N2NiYjliYTItMzgwMS00Nzg2LWI3YzEtYWQyZDQyOTI0M2E4IiwKICAiaXNzdWFuY2VEYXRlIjogIjIwMjQtMDMtMThUMjI6MDY6MzEuMzc1NDM0MTMxWiIsCiAgImlzc3VlciI6IHsKICAgICJpZCI6ICJkaWQ6aW9uOkVpQW9hSEpZRjJRNms5eml5aDRQRHJ1c0ladE5VS20xLWFGckF2clBEQ0VNU1E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSmhaR1F0Y0hWaWJHbGpMV3RsZVhNaUxDSndkV0pzYVdOTFpYbHpJanBiZXlKcFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpsSWpvaVFWRkJRaUlzSW10cFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW10MGVTSTZJbEpUUVNJc0ltNGlPaUp1V1RCaU5tRkdURTVWYW1RM2VFRnBiVnA0ZFVnd1prNVNUVWN6WldOSFdHOVpTM1JSV0RSMVMxRmpjMWxaVTI1R2FFRkRlRGhtYW5Cbk5qSkplVnBDZFZSUVlUWnVaM04yWDA1UlFraFVXVWxNUmpKUldWVlFhVFJRVkVzdGVVRTNUbVJQU2xad05VRkZWelZ5YzNsc1pUZ3dTeTFQVUdsVVVXMUtSMng1VWpoSlVrWklOazl0TTNNMllYTnRXVmw1WmtrdExXdFJaVGhJYTBkMGJYYzNUR2xpVldWWlduZDNSemxJU1d4NlNEUlFObWhJVDNOVVpsaGhSRmwyYkRVM1dXcDFibWd5Y1ZVeVlXbFhSRms1UVdsVVVrSnBiVXhDYUVsdVpuWTBOVTVZZVRsMWQzSkdPWE5aYkZodWJHZDViVlZUTVZGV2NUYzVWUzFsYmt0bVJYTXpSMnMyUlVSaVYzSkhiWHBIY0hSUFpHcHpkRWt3T1dWNGFWUklPRXRyZG5oT1JXaFFla3hrZW5rd1VGTmxSV1o0WDJsVFdXeE1aRmxKUmtWVU4yb3hNVTV2TFRGeU1rVjZRa05wVVRWdWRGSXhNMUVpZlN3aWNIVnljRzl6WlhNaU9sc2lZWFYwYUdWdWRHbGpZWFJwYjI0aUxDSmhjM05sY25ScGIyNU5aWFJvYjJRaVhTd2lkSGx3WlNJNklrcHpiMjVYWldKTFpYa3lNREl3SW4xZGZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVORVJsRlFabmhTUm5Oc2NrRkliVm80YVhOUWJFNDRRelpLVW1SemVuUTBabkkwYURsSU5HUm1NVlpuSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEJTRE14TkZkcVJUTkJhVFJoTFhNeVNtOUhjWFpqUzFWcFRFUmZNVEpWWjNWeFkwWTFiVWR6Um5aMFp5SXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFTMXBka1pxVFRaTGRteDJZVXRVT0dkQ2EzQkphelpMWmt4dk9VbDVNR28wWVdobWFpMXJUMGwyV2xFaWZTd2lkSGx3WlNJNkltTnlaV0YwWlNKOSIsCiAgICAibmFtZSI6ICJBY21lIElzc3VlciIKICB9LAogICJ0eXBlIjogWwogICAgIlZlcmlmaWFibGVDcmVkZW50aWFsIiwKICAgICJVbml2ZXJzaXR5RGVncmVlQ3JlZGVudGlhbCIKICBdCn0= | + + @oidc4vc_rest_pre_auth_flow_trustlist_success Scenario Outline: OIDC credential issuance and verification Pre Auth flow with trustlist (Success) Given Profile "" issuer has been authorized with username "profile-user-issuer-1" and password "profile-user-issuer-1-pwd" diff --git a/test/bdd/pkg/v1/oidc4vc/oidc4vci.go b/test/bdd/pkg/v1/oidc4vc/oidc4vci.go index f6fceef2d..4ea52ea8b 100644 --- a/test/bdd/pkg/v1/oidc4vc/oidc4vci.go +++ b/test/bdd/pkg/v1/oidc4vc/oidc4vci.go @@ -1138,6 +1138,12 @@ func getOrgAuthTokenKey(org string) string { return org + "-accessToken" } +func (s *Steps) addExpectedAttachmentForVP(data string) error { + s.expectedAttachment = append(s.expectedAttachment, data) + + return nil +} + func (s *Steps) setExpectedCredentialsAmountForVP(expectedCredentialsAmount string) error { amount, err := strconv.Atoi(expectedCredentialsAmount) if err != nil { @@ -1148,6 +1154,7 @@ func (s *Steps) setExpectedCredentialsAmountForVP(expectedCredentialsAmount stri return nil } + func (s *Steps) checkIssuedCredential(expectedCredentialsAmount string) error { credentialMap, err := s.wallet.GetAll() if err != nil { diff --git a/test/bdd/pkg/v1/oidc4vc/steps.go b/test/bdd/pkg/v1/oidc4vc/steps.go index b715c2d8c..e726a7839 100644 --- a/test/bdd/pkg/v1/oidc4vc/steps.go +++ b/test/bdd/pkg/v1/oidc4vc/steps.go @@ -74,6 +74,7 @@ type Steps struct { composeFeatureEnabled bool composeCredential *verifiable.Credential expectedCredentialsAmountForVP int + expectedAttachment []string } // NewSteps returns new Steps context. @@ -98,6 +99,7 @@ func (s *Steps) RegisterSteps(sc *godog.ScenarioContext) { sc.Step(`^User saves issued credentials`, s.saveCredentials) sc.Step(`^"([^"]*)" credentials are issued$`, s.checkIssuedCredential) sc.Step(`^expected credential count for vp flow is "([^"]*)"$`, s.setExpectedCredentialsAmountForVP) + sc.Step(`^expected attachment for vp flow is "([^"]*)"$`, s.addExpectedAttachmentForVP) sc.Step(`^issued credential history is updated`, s.checkIssuedCredentialHistoryStep) // OIDC4VCI @@ -122,6 +124,7 @@ func (s *Steps) RegisterSteps(sc *godog.ScenarioContext) { sc.Step(`^User interacts with Verifier and initiate OIDC4VP interaction under "([^"]*)" profile with presentation definition ID "([^"]*)" and fields "([^"]*)"$`, s.runOIDC4VPFlow) sc.Step(`^User interacts with Verifier and initiate OIDC4VP interaction under "([^"]*)" profile with presentation definition ID "([^"]*)" and fields "([^"]*)" and custom scopes "([^"]*)"$`, s.runOIDC4VPFlowWithCustomScopes) sc.Step(`^Verifier with profile "([^"]*)" retrieves interactions claims$`, s.retrieveInteractionsClaim) + sc.Step(`^Verifier with profile "([^"]*)" retrieves interactions claims and expects "([^"]*)" attachments$`, s.retrieveInteractionsClaim) sc.Step(`^Verifier with profile "([^"]*)" retrieves interactions claims with additional claims associated with custom scopes "([^"]*)"$`, s.retrieveInteractionsClaimWithCustomScopes) sc.Step(`^wallet configured to use hardcoded vp_token format "([^"]*)" for OIDC4VP interaction$`, s.setHardcodedVPTokenFormat) @@ -163,6 +166,7 @@ func (s *Steps) ResetAndSetup() error { s.composeFeatureEnabled = false s.composeCredential = nil s.expectedCredentialsAmountForVP = 0 + s.expectedAttachment = nil s.tlsConfig = s.bddContext.TLSConfig From 1df560eb860831a4f69b8bc6b8874478de2d7294 Mon Sep 17 00:00:00 2001 From: Stas Dm Date: Tue, 2 Jul 2024 00:56:05 +0200 Subject: [PATCH 5/7] feat: attachment tests --- .../metrics/prometheus/provider.go | 1 + test/bdd/features/oidc4vc_api.feature | 6 +- test/bdd/fixtures/profile/profiles.json | 214 ++++++++++++++++++ test/bdd/pkg/v1/oidc4vc/models.go | 1 + test/bdd/pkg/v1/oidc4vc/oidc4vp.go | 25 +- 5 files changed, 243 insertions(+), 4 deletions(-) diff --git a/pkg/observability/metrics/prometheus/provider.go b/pkg/observability/metrics/prometheus/provider.go index 3155efec3..84d364e1b 100644 --- a/pkg/observability/metrics/prometheus/provider.go +++ b/pkg/observability/metrics/prometheus/provider.go @@ -122,6 +122,7 @@ func NewMetrics( metrics.ClientDiscoverableClientIDScheme, metrics.ClientAttestationService, metrics.TrustRegistryService, + metrics.Attachments, } pm := &PromMetrics{ diff --git a/test/bdd/features/oidc4vc_api.feature b/test/bdd/features/oidc4vc_api.feature index 1008e04f5..99448f633 100644 --- a/test/bdd/features/oidc4vc_api.feature +++ b/test/bdd/features/oidc4vc_api.feature @@ -212,15 +212,15 @@ Feature: OIDC4VC REST API When User interacts with Wallet to initiate credential issuance using pre authorization code flow Then "1" credentials are issued Then User interacts with Verifier and initiate OIDC4VP interaction under "" profile with presentation definition ID "" and fields "" - And expected attachment for vp flow is "a" - And expected attachment for vp flow is "b" + And expected attachment for vp flow is "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgNzIgNDgiIGlkPSJzdmcxNjU5IiB3aWR0aD0iNzIiIGhlaWdodD0iNDgiPgogIDxtZXRhZGF0YSBpZD0ibWV0YWRhdGEzMTE2Ij4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yayByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgICA8ZGM6dGl0bGU+VzNDPC9kYzp0aXRsZT4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGRlZnMgaWQ9ImRlZnMzMTE0IiAvPgogIDxyZWN0IGZpbGwtcnVsZT0iZXZlbm9kZCIgd2lkdGg9IjcyIiBmaWxsPSIjZmZmZmZmIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGhlaWdodD0iNDgiIGlkPSJyZWN0MTYzOSIgeD0iMCIgeT0iMCIgc3R5bGU9ImltYWdlLXJlbmRlcmluZzpvcHRpbWl6ZVF1YWxpdHk7c2hhcGUtcmVuZGVyaW5nOmdlb21ldHJpY1ByZWNpc2lvbjt0ZXh0LXJlbmRlcmluZzpnZW9tZXRyaWNQcmVjaXNpb24iIC8+CiAgPHBhdGggZmlsbD0iIzAwNWE5YyIgZD0ibSAyLjAzMjQ1Myw4LjAwNTQ1MyA5LjUzMTI1LDMyLjE2OTkyMiBoIDAuMzk2NDg1IGwgNS45NTcwMzEsLTE5LjkzNTU0NyA1Ljk1ODk4NCwxOS45MzU1NDcgaCAwLjM5NjQ4NSBsIDcuMTQyNTc4LC0yNC4xMDU0NjkgYyAxLjM2NDEzNiwtNC42MDM2MDIgMC42ODczMDgsLTQuMDkzNzUgNC43NTc4MTIsLTQuMDkzNzUgSCAzOS41MzA1IGwgLTYuNjM2NzE5LDExLjUxOTUzMiBoIDIuNjY0MDYzIGMgMS43MjE2MDcsMCAzLjUzNDI4MSwwLjYyMTIzNCA0LjQ4ODI4MSwxLjg2NTIzNCAwLjEzMzY0OCwwLjE3MjYwMiAwLjI1Mzc0NywwLjM2MDQ3OCAwLjM2NTIzNCwwLjU1NjY0IDAuMDI1ODMsMC4wNDUxOSAwLjA0OTU1LDAuMDkyMjQgMC4wNzQyMiwwLjEzODY3MiAwLjEwNDkxOSwwLjE5ODU5NyAwLjIwMjkwNiwwLjQwNDE5MyAwLjI4NzExLDAuNjI1IDAuMzM0Njk5LDAuODc4MTk0IDAuNTAzOTA2LDEuOTMzMjY1IDAuNTAzOTA2LDMuMTY3OTY5IDAsMS43NDggLTAuMzg0MzQ0LDMuMjQzMjgxIC0xLjE1MjM0NCw0LjQ4ODI4MSAtMC4yMDg0ODMsMC4zMzc2OTkgLTAuNDMwMjMxLDAuNjI1MDY4IC0wLjY2NDA2MiwwLjg3MTA5NCAtMC4xNDk1NjQsMC4xNTczNjEgLTAuMzA1MTA1LDAuMjkzODgzIC0wLjQ2NDg0NCwwLjQxNDA2MyAtMC4wMTM0MywwLjAxMDA1IC0wLjAyNzUxLDAuMDE5NTEgLTAuMDQxMDIsMC4wMjkzIC0wLjE2Mjg3LDAuMTE4NzEgLTAuMzI4NDc5LDAuMjIwNTM2IC0wLjUwMTk1MywwLjMwMDc4MSAtMC4xNTgxMTIsMC4wNzMxNCAtMC4zMjMyMTQsMC4xMjUgLTAuNDkwMjM0LDAuMTY2MDE1IC0wLjA0MjY2LDAuMDEwMzkgLTAuMDg1NjgsMC4wMTkwNCAtMC4xMjg5MDcsMC4wMjczNCAtMC4xNzE4MDMsMC4wMzMyOCAtMC4zNDYyNDMsMC4wNTY2NCAtMC41MjczNDMsMC4wNTY2NCAtMC4wMDgxLDAgLTAuMDE1MzcsLTAuMDAxOSAtMC4wMjM0NCwtMC4wMDIgLTAuMDA4MSwtNC4xZS01IC0wLjAxNTM5LC0wLjAwMTkgLTAuMDIzNDQsLTAuMDAyIC0wLjAwNDEsLTMuOWUtNSAtMC4wMDc4LC0wLjAwMTUgLTAuMDExNzIsLTAuMDAyIC0wLjAwOTQsLTEuMThlLTQgLTAuMDE3OTksLTAuMDAxOCAtMC4wMjczNCwtMC4wMDIgLTAuMjM4MTg0LC0wLjAwNDMgLTAuNDcxMjkxLC0wLjAyMjQgLTAuNjk1MzEyLC0wLjA2MjUgLTAuMDAxMywtMi4yNmUtNCAtMC4wMDI3LDIuMjdlLTQgLTAuMDAzOSwwIC0wLjA2MTI5LC0wLjAxMTAzIC0wLjExNzUwNSwtMC4wMzUxMSAtMC4xNzc3MzQsLTAuMDQ4ODMgLTAuMDEyNTQsLTAuMDAyOCAtMC4wMjQ5MywtMC4wMDU5IC0wLjAzNzExLC0wLjAwOTggLTAuMDE1MjgsLTAuMDAzNyAtMC4wMjk3MSwtMC4wMDk4IC0wLjA0NDkyLC0wLjAxMzY3IC0wLjE1MjgzOCwtMC4wMzU0NiAtMC4zMDYzMTEsLTAuMDY5NjUgLTAuNDUxMTcyLC0wLjEyNSAtMC4yMjgyOTIsLTAuMDg3MjMgLTAuNDQ3MDk5LC0wLjE5NTYwMyAtMC42NTgyMDMsLTAuMzI2MTcyIEMgMzQuOTQwNDksMzUuNDc4Mzk5IDM0LjczNzkzNSwzNS4zMjYxMDEgMzQuNTQ0MTcyLDM1LjE1MTkzOCAzNC4xMjk5MywzNC43MDE4NTYgMzMuODE3OTkyLDM0LjE5MDIzMSAzMy40ODc1MzEsMzMuNjkxIGggLTQuMDU0Njg3IGMgMC4yNzQ4ODgsMC44MTY5ODUgMC42MTY0MDIsMS41NTU5OTYgMS4wMDk3NjUsMi4yMzQzNzUgbCAtMC4wMTE3MiwwLjAyMTQ4IGMgMC4wNDI1NCwwLjA3Njg0IDAuMDk5NDksMC4xNDg3NzggMC4xNDY0ODQsMC4yMjQ2MSAwLjI4MDAzMSwwLjQ1NDg3NiAwLjU4MTYwMiwwLjg4NjI2MiAwLjkxNzk2OSwxLjI3NTM5IDAuMjk5NTcxLDAuMzQ2NTYyIDAuNjIwNTkzLDAuNjY2NDM4IDAuOTY0ODQ0LDAuOTYwOTM4IDAuMTcxMjY2LDAuMTQ2NjIyIDAuMzQ2NjkyLDAuMjgzNzIgMC41MjUzOSwwLjQxMjEwOSAwLDAgMC4wMDIsMC4wMDIgMC4wMDIsMC4wMDIgMC4xNzc5ODksMC4xMjc3ODMgMC4zNTk1NywwLjI0NTc3NCAwLjU0NDkyMiwwLjM1NTQ2OSAwLjExNTA0NiwwLjA2ODA5IDAuMjMzNjgzLDAuMTI2Mzg0IDAuMzUxNTYzLDAuMTg3NSAwLjAxNTc3LDAuMDA4MiAwLjAzMTA2LDAuMDE3MzQgMC4wNDY4NywwLjAyNTM5IDAuMDU3MDEsMC4wMzEzNCAwLjExMzQ5NSwwLjA2NDA1IDAuMTcxODc1LDAuMDkxOCAwLjAwMTgsOC42OGUtNCAwLjAwNCwwLjAwMTEgMC4wMDU5LDAuMDAyIDAuMTkxODk2LDAuMDkwOTkgMC4zODY2NTEsMC4xNzMxMyAwLjU4NTkzNywwLjI0NjA5MyAwLjAwMjcsMTBlLTQgMC4wMDUxLDAuMDAyOSAwLjAwNzgsMC4wMDM5IDAsMCAwLjAwMiwwIDAuMDAyLDAgMC4xOTc3OSwwLjA3MjE3IDAuMzk4NTY0LDAuMTM0OTQ3IDAuNjAzNTE2LDAuMTg5NDUzIDAuMDA0LDAuMDAxMSAwLjAwNzcsMC4wMDI5IDAuMDExNzIsMC4wMDM5IDAsMCAwLjAwMiwwIDAuMDAyLDAgMC4yMDM5NywwLjA1MzgyIDAuNDEyMDAzLDAuMDk4NDIgMC42MjMwNDYsMC4xMzQ3NjYgMC4wMDUxLDguODJlLTQgMC4wMTA0OSwwLjAwMTEgMC4wMTU2MywwLjAwMiAwLjE4ODQ2NSwwLjAzMTg3IDAuMzgwMTA2LDAuMDU0MTYgMC41NzQyMTgsMC4wNzIyNyAwLjAwNDYsNC4zMmUtNCAwLjAwOSwwLjAwMTUgMC4wMTM2NywwLjAwMiAwLjAwNzEsMC4wMDE0IDAuMDE0MTcsMC4wMDMyIDAuMDIxNDgsMC4wMDM5IDAuMDEwOTMsMC4wMDE5IDAuMDIxOTcsMC4wMDI5IDAuMDMzMiwwLjAwMzkgMCwwIDAuMDAyLDAgMC4wMDIsMCAwLjAwNTMsNC40NmUtNCAwLjAxMDMxLDAuMDAxNSAwLjAxNTYzLDAuMDAyIDAsMCAwLjAwMiwwIDAuMDAyLDAgMC4xMTY5MzYsMC4wMDk2IDAuMjM4MDc0LDAuMDA5MSAwLjM1NzQyMiwwLjAxMzY3IDAuMDI0NzksOS41NWUtNCAwLjA0OTM0LDAuMDAzMiAwLjA3NDIyLDAuMDAzOSAwLjAxNTYsOC40NGUtNCAwLjAzMTEzLDAuMDAxNiAwLjA0Njg3LDAuMDAyIDAuMDYwMjYsMC4wMDM2IDAuMTIwMjcyLDAuMDA3NyAwLjE4MTY0MSwwLjAwNzggMC4wMDc5LDIuMmUtNSAwLjAxNTUsMCAwLjAyMzQ0LDAgMC4xNzgyNDQsMCAwLjM0ODAyLC0wLjAxNjk0IDAuNTIxNDg0LC0wLjAyNzM0IHYgLTAuMDA5OCBjIDAuMTg2NjA2LC0wLjAxMzQxIDAuMzc4MjYzLC0wLjAxNTUgMC41NjA1NDcsLTAuMDM5MDYgMC4wMjU1OSwtMC4wMDM1IDAuMDUwNjksLTAuMDA3OSAwLjA3NjE3LC0wLjAxMTcyIDAuMzQyMjU3LC0wLjA0NzY5IDAuNjczOCwtMC4xMTk4MTIgMC45OTYwOTQsLTAuMjEwOTM3IDEuNTAyNDksLTAuNDI0ODE3IDIuNzkzMTQ5LC0xLjMxMDE1NyAzLjg2MTMyOCwtMi42Nzk2ODggMS41NDksLTEuOTg2IDIuMzI0MjE4LC00LjQzNDY1NiAyLjMyNDIxOCwtNy4zNDc2NTYgMCwtMi4zNTYgLTAuNTMwODQzLC00LjQwNzI5NyAtMS41ODk4NDMsLTYuMTU0Mjk3IC0wLjgwNDI0NSwtMS4zMjY3MzggLTEuODk4ODgxLC0yLjMzOTQwNSAtMy4yODMyMDMsLTMuMDQxMDE1IC0wLjA0Mjg5LC0wLjAyMTc1IC0wLjA4OTQxLC0wLjAzOTM5IC0wLjEzMjgxMywtMC4wNjA1NSAtMC4xNzg4MjgsLTAuMDg3IC0wLjM1ODYwNiwtMC4xNzMxNyAtMC41NDY4NzUsLTAuMjUgLTAuMjMzNDg3LC0wLjA5NTc1IC0wLjQ3Mzg1NCwtMC4xODM3OTEgLTAuNzIyNjU2LC0wLjI2MzY3MiAyLjMyNDc2MywtNC4wMjU1MjIgNC42OTAyOTEsLTguMDI3NzU3IDYuOTc4NTE1LC0xMi4wNzQyMTkgSCAzMy44MDU4OTEgYyAtMy42MjQyOTksMCAtNC4zNTI4MDksMC42MTc2NDQ5IC01LjYxMTMyOSw0Ljg5NjQ4NSBMIDIzLjg3NjIwMywyNy41ODU1MzEgMTguMTE2NDM4LDguMDA1NDUzIGggLTQuMTY3OTY5IGwgMC45NTMxMjUsMy4xOTkyMTkgYyAwLjc5NDAwNCwyLjY2NDMwNiAwLjcyMzQ5NiwzLjk3MjIyIC0wLjAyMTQ4LDYuNDk0MTQgTCAxMS45NjAxODgsMjcuNTg1NTMxIDYuMjAyMzc0OSw4LjAwNTQ1MyBaIiBpZD0icGF0aDE1OTkiIC8+CiAgPHBhdGggaWQ9InBhdGg2IiBkPSJtIDY2LjkyLDguMDA1OTk5OSBjIC0wLjgxOSwwIC0xLjU1NCwwLjI5NSAtMi4xMTEsMC44NjEgLTAuNTkxLDAuNiAtMC45MiwxLjM3NjAwMDEgLTAuOTIsMi4xNzgwMDAxIDAsMC44MDIgMC4zMTMsMS41NDUgMC44ODcsMi4xMjggMC41ODMsMC41OTEgMS4zMzQsMC45MTIgMi4xNDUsMC45MTIgMC43OTMsMCAxLjU2MiwtMC4zMjEgMi4xNjEsLTAuOTAzIDAuNTc0LC0wLjU1NyAwLjg4NiwtMS4zMDEgMC44ODYsLTIuMTM3IDAsLTAuODExIC0wLjMyMSwtMS41NzAwMDAxIC0wLjg3OCwtMi4xMzYwMDAxIC0wLjU4MywtMC41OTEgLTEuMzQzLC0wLjkwMyAtMi4xNywtMC45MDMgeiBNIDY5LjU2MywxMS4wNzEgYyAwLDAuNzAxIC0wLjI3MSwxLjM1MSAtMC43NjksMS44MzIgLTAuNTIzLDAuNTA3IC0xLjE3MywwLjc3NyAtMS44OTEsMC43NzcgLTAuNjc1LDAgLTEuMzQyLC0wLjI3OCAtMS44NCwtMC43ODUgLTAuNDk4LC0wLjUwNiAtMC43NzcsLTEuMTU3IC0wLjc3NywtMS44NDkgMCwtMC42OTIgMC4yODcsLTEuMzY4MDAwMSAwLjgwMiwtMS44OTEwMDAxIDAuNDgxLC0wLjQ5IDEuMTMxLC0wLjc1MSAxLjg0LC0wLjc1MSAwLjcyNiwwIDEuMzc2LDAuMjcxIDEuODgzLDAuNzg1IDAuNDksMC40ODkgMC43NTIsMS4xNDgwMDAxIDAuNzUyLDEuODgyMDAwMSB6IE0gNjcuMDA1LDkuMjYzOTk5OSBoIC0xLjMgViAxMi43MDkgaCAwLjY1IFYgMTEuMjQgaCAwLjY0MiBsIDAuNzAxLDEuNDY5IGggMC43MjYgbCAtMC43NjksLTEuNTcxIGMgMC40OTgsLTAuMTAxIDAuNzg1LC0wLjQzOSAwLjc4NSwtMC45MjggMCwtMC42MjUwMDAxIC0wLjQ3MywtMC45NDYwMDAxIC0xLjQzNSwtMC45NDYwMDAxIHogbSAtMC4xMTksMC40MjIgYyAwLjYwOCwwIDAuODg2LDAuMTY5IDAuODg2LDAuNTkxMDAwMSAwLDAuNDA1IC0wLjI3OCwwLjU0OSAtMC44NywwLjU0OSBIIDY2LjM1MyBWIDkuNjg1OTk5OSBaIiAvPgogIDxwYXRoIGlkPSJwYXRoOCIgZD0ibSA2MS44MDcsNy44MjQ5OTk5IDAuMzM4LDIuMDUzNSBDIDYyLjQ2MDQ5NywxMS43OTUyODMgNjIuMTM5NiwxMi41ODkxNTMgNjEuMjg3NSwxNC4yMTk1IEwgNjAuMDkyLDE2LjUwNyBjIDAsMCAtMC45MTgsLTEuOTQxIC0yLjQ0MywtMy4wMTUgLTEuMjg1LC0wLjkwNSAtMi4xMjIsLTEuMTAyIC0zLjQzMSwtMC44MzIgLTEuNjgxLDAuMzQ3IC0zLjU4NywyLjM1NyAtNC40MTksNC44MzUgLTAuOTk1LDIuOTY1IC0xLjAwNSw0LjQgLTEuMDQsNS43MTggLTAuMDU2LDIuMTEzIDAuMjc3LDMuMzYyIDAuMjc3LDMuMzYyIDAsMCAtMS40NTEsLTIuNjg2IC0xLjQzOCwtNi42MiAwLjAwOSwtMi44MDggMC40NTEsLTUuMzU0IDEuNzUsLTcuODY3IDEuMTQzLC0yLjIwOTAwMDEgMi44NDIsLTMuNTM1MDAwMSA0LjM1LC0zLjY5MTAwMDEgMS41NTksLTAuMTYxIDIuNzkxLDAuNTkgMy43NDMsMS40MDQgQyA1OC40NCwxMC42NTUgNTkuNDUxLDEyLjUyMSA1OS40NTEsMTIuNTIxIFoiIC8+CiAgPHBhdGggaWQ9InBhdGgxMCIgZD0ibSA2Mi4xMDIsMzEuMDYzIGMgMCwwIC0xLjA1NywxLjg4OSAtMS43MTUsMi42MTcgLTAuNjU5LDAuNzI4IC0xLjgzNywyLjAxIC0zLjI5MiwyLjY1MSAtMS40NTYsMC42NDEgLTIuMjE4LDAuNzYyIC0zLjY1NiwwLjYyNCAtMS40MzcsLTAuMTM4IC0yLjc3MywtMC45NyAtMy4yNDEsLTEuMzE3IC0wLjQ2OCwtMC4zNDcgLTEuNjY0LC0xLjM2OSAtMi4zMzksLTIuMzIyIC0wLjY3NiwtMC45NTQgLTEuNzMzLC0yLjg1OSAtMS43MzMsLTIuODU5IDAsMCAwLjU4OSwxLjkxMSAwLjk1OCwyLjcyMSAwLjIxMiwwLjQ2NiAwLjg2NCwxLjg5NCAxLjc5LDMuMTM2IDAuODYyLDEuMTU5IDIuNTM5LDMuMTU0IDUuMDg2LDMuNjA0IDIuNTQ3LDAuNDUxIDQuMjk3LC0wLjY5MyA0LjczLC0wLjk3MSAwLjQzMywtMC4yNzcgMS40MzIzMDksLTAuOTMwNDIgMi4wMTAyNDMsLTEuNTQ4NDgyIDAuNjAzMDY2LC0wLjY0NDkzOCAwLjkyMzA4MywtMS4xMDczOTMgMS4zMjUxMTEsLTEuOTIwNzc1IDAuMjkyMDU3LC0wLjU5MDg4OSAwLjU0NDc2MywtMS45NzczMDQgMC4zODExNDYsLTIuODI5MjQzIHoiIC8+Cjwvc3ZnPgo=" + And expected attachment for vp flow is "data:image/png;base64,iVBORw0KGgokJg" 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 | credentialEncoded | - | acme_issuer/v1.0 | UniversityDegreeCredential | | v_myprofile_jwt/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | jwt | ewogICJAY29udGV4dCI6IFsKICAgICJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsCiAgICAiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiLAogICAgImh0dHBzOi8vdzNpZC5vcmcvdmMvc3RhdHVzLWxpc3QvMjAyMS92MSIKICBdLAogICJjcmVkZW50aWFsU3RhdHVzIjogewogICAgImlkIjogInVybjp1dWlkOjViYjIwNTU4LTI1Y2UtNDBiNS04MGZjLThhZjY5MWVlZGNjZSIsCiAgICAic3RhdHVzTGlzdENyZWRlbnRpYWwiOiAiaHR0cDovL3ZjLXJlc3QtZWNoby50cnVzdGJsb2MubG9jYWw6ODA3NS9pc3N1ZXIvZ3JvdXBzL2dyb3VwX2FjbWVfaXNzdWVyL2NyZWRlbnRpYWxzL3N0YXR1cy8wNmIxM2U5Mi0yN2E2LTRiNzYtOTk3Ny02ZWNlMjA3NzgzZGQiLAogICAgInN0YXR1c0xpc3RJbmRleCI6ICI2NTQ4IiwKICAgICJzdGF0dXNQdXJwb3NlIjogInJldm9jYXRpb24iLAogICAgInR5cGUiOiAiU3RhdHVzTGlzdDIwMjFFbnRyeSIKICB9LAogICJjcmVkZW50aWFsU3ViamVjdCI6IHsKICAgICJkZWdyZWUiOiB7CiAgICAgICJkZWdyZWUiOiAiTUlUIiwKICAgICAgInR5cGUiOiAiQmFjaGVsb3JEZWdyZWUiCiAgICB9LAogICAgInBob3RvIjogewogICAgICAiaWQiOiAiZG9jNDQ1IiwKICAgICAgInR5cGUiOiBbCiAgICAgICAgIkVtYmVkZGVkQXR0YWNobWVudCIKICAgICAgXSwKICAgICAgIm1pbWVUeXBlIjogImltYWdlL3BuZyIsCiAgICAgICJ1cmkiOiAiZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dva0pnIiwKICAgICAgImRlc2NyaXB0aW9uIjogImIiCiAgICB9LAogICAgInJlbW90ZSI6IHsKICAgICAgImlkIjogInJlbW90ZV9kb2MiLAogICAgICAidHlwZSI6IFsKICAgICAgICAiUmVtb3RlQXR0YWNobWVudCIKICAgICAgXSwKICAgICAgIm1pbWVUeXBlIjogImltYWdlL3N2ZyIsCiAgICAgICJ1cmkiOiAiaHR0cHM6Ly93d3cudzMub3JnL2Fzc2V0cy9sb2dvcy93M2MvdzNjLW5vLWJhcnMuc3ZnIiwKICAgICAgImRlc2NyaXB0aW9uIjogImEiCiAgICB9LAogICAgImlkIjogImRpZDppb246RWlCeVFBeFhtT0FFTTdUbEVVRzl1NlJyMnJzNVFDTHh1T2ZWbE5ONXpUM1JtUTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKaFpHUXRjSFZpYkdsakxXdGxlWE1pTENKd2RXSnNhV05MWlhseklqcGJleUpwWkNJNklsOXVWVGx5YmtGcGNIZzNNRmhYU0RSS2RVcDNUMVZwV2paNFNVWk5ZVEZuUm1oS1VWVXdWSFoyTTFFaUxDSndkV0pzYVdOTFpYbEtkMnNpT25zaVkzSjJJam9pVUMwek9EUWlMQ0pyYVdRaU9pSmZibFU1Y201QmFYQjROekJZVjBnMFNuVktkMDlWYVZvMmVFbEdUV0V4WjBab1NsRlZNRlIyZGpOUklpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaU5sYzNkSEZhTVhabFRUZFpTRTU1ZEROME9YaE1PVGRNVDBoelp6a3libTV3WDJSc2FXTlFjVlJsY2pSdFNYaFplVFZ4TTA1MmVVa3djekkxV2tsRk9DSXNJbmtpT2lKRGExUlhaaTFZVlZwNkxWZDJOekl0TlhCdk9XZHBjblo2U25kNE1uRlRRMGt5Wm1GNWJIZFhjbXR0YW5wWE16RkVjV0ZIY2paRldrUXRkVmxEVTJkT0luMHNJbkIxY25CdmMyVnpJanBiSW1GMWRHaGxiblJwWTJGMGFXOXVJaXdpWVhOelpYSjBhVzl1VFdWMGFHOWtJbDBzSW5SNWNHVWlPaUpLYzI5dVYyVmlTMlY1TWpBeU1DSjlYWDFkTENKMWNHUmhkR1ZEYjIxdGFYUnRaVzUwSWpvaVJXbERNbWh0YzJSRk5IRk9kMjFUUTFCbU1qRjFNRXg2UWtodmMzQldVRkZzT1ZKdFRHczNORloxWnpWcmR5SjlMQ0p6ZFdabWFYaEVZWFJoSWpwN0ltUmxiSFJoU0dGemFDSTZJa1ZwUVMxWk9YVjBUVTVoYVVoVlIxcHROVEJtUVZKamNWTmFjVWRCTXpkNlFsWlVOVVk0UzBsdFh6TlRka0VpTENKeVpXTnZkbVZ5ZVVOdmJXMXBkRzFsYm5RaU9pSkZhVUpSYlVOaWRsOVZRelZSUmtOMk9VTndiMUpQYVZNNWJIWkNkbmR1WTJsWkxWOTRjbEU0U1RKdlIwSm5JbjBzSW5SNWNHVWlPaUpqY21WaGRHVWlmUSIsCiAgICAibmFtZSI6ICJKYXlkZW4gRG9lIiwKICAgICJzcG91c2UiOiAiZGlkOmV4YW1wbGU6YzI3NmUxMmVjMjFlYmZlYjFmNzEyZWJjNmYxIgogIH0sCiAgImV4cGlyYXRpb25EYXRlIjogIjIwMjUtMDMtMThUMjI6MDY6MzEuMzQyNTAxMDZaIiwKICAiaWQiOiAidXJuOnV1aWQ6N2NiYjliYTItMzgwMS00Nzg2LWI3YzEtYWQyZDQyOTI0M2E4IiwKICAiaXNzdWFuY2VEYXRlIjogIjIwMjQtMDMtMThUMjI6MDY6MzEuMzc1NDM0MTMxWiIsCiAgImlzc3VlciI6IHsKICAgICJpZCI6ICJkaWQ6aW9uOkVpQW9hSEpZRjJRNms5eml5aDRQRHJ1c0ladE5VS20xLWFGckF2clBEQ0VNU1E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSmhaR1F0Y0hWaWJHbGpMV3RsZVhNaUxDSndkV0pzYVdOTFpYbHpJanBiZXlKcFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpsSWpvaVFWRkJRaUlzSW10cFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW10MGVTSTZJbEpUUVNJc0ltNGlPaUp1V1RCaU5tRkdURTVWYW1RM2VFRnBiVnA0ZFVnd1prNVNUVWN6WldOSFdHOVpTM1JSV0RSMVMxRmpjMWxaVTI1R2FFRkRlRGhtYW5Cbk5qSkplVnBDZFZSUVlUWnVaM04yWDA1UlFraFVXVWxNUmpKUldWVlFhVFJRVkVzdGVVRTNUbVJQU2xad05VRkZWelZ5YzNsc1pUZ3dTeTFQVUdsVVVXMUtSMng1VWpoSlVrWklOazl0TTNNMllYTnRXVmw1WmtrdExXdFJaVGhJYTBkMGJYYzNUR2xpVldWWlduZDNSemxJU1d4NlNEUlFObWhJVDNOVVpsaGhSRmwyYkRVM1dXcDFibWd5Y1ZVeVlXbFhSRms1UVdsVVVrSnBiVXhDYUVsdVpuWTBOVTVZZVRsMWQzSkdPWE5aYkZodWJHZDViVlZUTVZGV2NUYzVWUzFsYmt0bVJYTXpSMnMyUlVSaVYzSkhiWHBIY0hSUFpHcHpkRWt3T1dWNGFWUklPRXRyZG5oT1JXaFFla3hrZW5rd1VGTmxSV1o0WDJsVFdXeE1aRmxKUmtWVU4yb3hNVTV2TFRGeU1rVjZRa05wVVRWdWRGSXhNMUVpZlN3aWNIVnljRzl6WlhNaU9sc2lZWFYwYUdWdWRHbGpZWFJwYjI0aUxDSmhjM05sY25ScGIyNU5aWFJvYjJRaVhTd2lkSGx3WlNJNklrcHpiMjVYWldKTFpYa3lNREl3SW4xZGZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVORVJsRlFabmhTUm5Oc2NrRkliVm80YVhOUWJFNDRRelpLVW1SemVuUTBabkkwYURsSU5HUm1NVlpuSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEJTRE14TkZkcVJUTkJhVFJoTFhNeVNtOUhjWFpqUzFWcFRFUmZNVEpWWjNWeFkwWTFiVWR6Um5aMFp5SXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFTMXBka1pxVFRaTGRteDJZVXRVT0dkQ2EzQkphelpMWmt4dk9VbDVNR28wWVdobWFpMXJUMGwyV2xFaWZTd2lkSGx3WlNJNkltTnlaV0YwWlNKOSIsCiAgICAibmFtZSI6ICJBY21lIElzc3VlciIKICB9LAogICJ0eXBlIjogWwogICAgIlZlcmlmaWFibGVDcmVkZW50aWFsIiwKICAgICJVbml2ZXJzaXR5RGVncmVlQ3JlZGVudGlhbCIKICBdCn0= | + | bank_issuer/v1.0 | UniversityDegreeCredential | | v_myprofile_jwt_no_strict/v1.0 | 32f54163-no-limit-disclosure-single-field | degree_type_id | jwt | ewogICJAY29udGV4dCI6IFsKICAgICJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsCiAgICAiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvZXhhbXBsZXMvdjEiLAogICAgImh0dHBzOi8vdzNpZC5vcmcvdmMvc3RhdHVzLWxpc3QvMjAyMS92MSIKICBdLAogICJjcmVkZW50aWFsU3RhdHVzIjogewogICAgImlkIjogInVybjp1dWlkOjViYjIwNTU4LTI1Y2UtNDBiNS04MGZjLThhZjY5MWVlZGNjZSIsCiAgICAic3RhdHVzTGlzdENyZWRlbnRpYWwiOiAiaHR0cDovL3ZjLXJlc3QtZWNoby50cnVzdGJsb2MubG9jYWw6ODA3NS9pc3N1ZXIvZ3JvdXBzL2dyb3VwX2FjbWVfaXNzdWVyL2NyZWRlbnRpYWxzL3N0YXR1cy8wNmIxM2U5Mi0yN2E2LTRiNzYtOTk3Ny02ZWNlMjA3NzgzZGQiLAogICAgInN0YXR1c0xpc3RJbmRleCI6ICI2NTQ4IiwKICAgICJzdGF0dXNQdXJwb3NlIjogInJldm9jYXRpb24iLAogICAgInR5cGUiOiAiU3RhdHVzTGlzdDIwMjFFbnRyeSIKICB9LAogICJjcmVkZW50aWFsU3ViamVjdCI6IHsKICAgICJkZWdyZWUiOiB7CiAgICAgICJkZWdyZWUiOiAiTUlUIiwKICAgICAgInR5cGUiOiAiQmFjaGVsb3JEZWdyZWUiCiAgICB9LAogICAgInBob3RvIjogewogICAgICAiaWQiOiAiZG9jNDQ1IiwKICAgICAgInR5cGUiOiBbCiAgICAgICAgIkVtYmVkZGVkQXR0YWNobWVudCIKICAgICAgXSwKICAgICAgIm1pbWVUeXBlIjogImltYWdlL3BuZyIsCiAgICAgICJ1cmkiOiAiZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dva0pnIiwKICAgICAgImRlc2NyaXB0aW9uIjogImIiCiAgICB9LAogICAgInJlbW90ZSI6IHsKICAgICAgImlkIjogInJlbW90ZV9kb2MiLAogICAgICAidHlwZSI6IFsKICAgICAgICAiUmVtb3RlQXR0YWNobWVudCIKICAgICAgXSwKICAgICAgIm1pbWVUeXBlIjogImltYWdlL3N2ZyIsCiAgICAgICJ1cmkiOiAiaHR0cHM6Ly93d3cudzMub3JnL2Fzc2V0cy9sb2dvcy93M2MvdzNjLW5vLWJhcnMuc3ZnIiwKICAgICAgImRlc2NyaXB0aW9uIjogImEiCiAgICB9LAogICAgImlkIjogImRpZDppb246RWlCeVFBeFhtT0FFTTdUbEVVRzl1NlJyMnJzNVFDTHh1T2ZWbE5ONXpUM1JtUTpleUprWld4MFlTSTZleUp3WVhSamFHVnpJanBiZXlKaFkzUnBiMjRpT2lKaFpHUXRjSFZpYkdsakxXdGxlWE1pTENKd2RXSnNhV05MWlhseklqcGJleUpwWkNJNklsOXVWVGx5YmtGcGNIZzNNRmhYU0RSS2RVcDNUMVZwV2paNFNVWk5ZVEZuUm1oS1VWVXdWSFoyTTFFaUxDSndkV0pzYVdOTFpYbEtkMnNpT25zaVkzSjJJam9pVUMwek9EUWlMQ0pyYVdRaU9pSmZibFU1Y201QmFYQjROekJZVjBnMFNuVktkMDlWYVZvMmVFbEdUV0V4WjBab1NsRlZNRlIyZGpOUklpd2lhM1I1SWpvaVJVTWlMQ0o0SWpvaU5sYzNkSEZhTVhabFRUZFpTRTU1ZEROME9YaE1PVGRNVDBoelp6a3libTV3WDJSc2FXTlFjVlJsY2pSdFNYaFplVFZ4TTA1MmVVa3djekkxV2tsRk9DSXNJbmtpT2lKRGExUlhaaTFZVlZwNkxWZDJOekl0TlhCdk9XZHBjblo2U25kNE1uRlRRMGt5Wm1GNWJIZFhjbXR0YW5wWE16RkVjV0ZIY2paRldrUXRkVmxEVTJkT0luMHNJbkIxY25CdmMyVnpJanBiSW1GMWRHaGxiblJwWTJGMGFXOXVJaXdpWVhOelpYSjBhVzl1VFdWMGFHOWtJbDBzSW5SNWNHVWlPaUpLYzI5dVYyVmlTMlY1TWpBeU1DSjlYWDFkTENKMWNHUmhkR1ZEYjIxdGFYUnRaVzUwSWpvaVJXbERNbWh0YzJSRk5IRk9kMjFUUTFCbU1qRjFNRXg2UWtodmMzQldVRkZzT1ZKdFRHczNORloxWnpWcmR5SjlMQ0p6ZFdabWFYaEVZWFJoSWpwN0ltUmxiSFJoU0dGemFDSTZJa1ZwUVMxWk9YVjBUVTVoYVVoVlIxcHROVEJtUVZKamNWTmFjVWRCTXpkNlFsWlVOVVk0UzBsdFh6TlRka0VpTENKeVpXTnZkbVZ5ZVVOdmJXMXBkRzFsYm5RaU9pSkZhVUpSYlVOaWRsOVZRelZSUmtOMk9VTndiMUpQYVZNNWJIWkNkbmR1WTJsWkxWOTRjbEU0U1RKdlIwSm5JbjBzSW5SNWNHVWlPaUpqY21WaGRHVWlmUSIsCiAgICAibmFtZSI6ICJKYXlkZW4gRG9lIiwKICAgICJzcG91c2UiOiAiZGlkOmV4YW1wbGU6YzI3NmUxMmVjMjFlYmZlYjFmNzEyZWJjNmYxIgogIH0sCiAgImV4cGlyYXRpb25EYXRlIjogIjIwMjUtMDMtMThUMjI6MDY6MzEuMzQyNTAxMDZaIiwKICAiaWQiOiAidXJuOnV1aWQ6N2NiYjliYTItMzgwMS00Nzg2LWI3YzEtYWQyZDQyOTI0M2E4IiwKICAiaXNzdWFuY2VEYXRlIjogIjIwMjQtMDMtMThUMjI6MDY6MzEuMzc1NDM0MTMxWiIsCiAgImlzc3VlciI6IHsKICAgICJpZCI6ICJkaWQ6aW9uOkVpQW9hSEpZRjJRNms5eml5aDRQRHJ1c0ladE5VS20xLWFGckF2clBEQ0VNU1E6ZXlKa1pXeDBZU0k2ZXlKd1lYUmphR1Z6SWpwYmV5SmhZM1JwYjI0aU9pSmhaR1F0Y0hWaWJHbGpMV3RsZVhNaUxDSndkV0pzYVdOTFpYbHpJanBiZXlKcFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW5CMVlteHBZMHRsZVVwM2F5STZleUpsSWpvaVFWRkJRaUlzSW10cFpDSTZJbU0xTkdRME1qZzJMV0l5TmpNdE5ESTNNQzFoTURNeExUVXlPVEppWXpSak5qUmpaQ0lzSW10MGVTSTZJbEpUUVNJc0ltNGlPaUp1V1RCaU5tRkdURTVWYW1RM2VFRnBiVnA0ZFVnd1prNVNUVWN6WldOSFdHOVpTM1JSV0RSMVMxRmpjMWxaVTI1R2FFRkRlRGhtYW5Cbk5qSkplVnBDZFZSUVlUWnVaM04yWDA1UlFraFVXVWxNUmpKUldWVlFhVFJRVkVzdGVVRTNUbVJQU2xad05VRkZWelZ5YzNsc1pUZ3dTeTFQVUdsVVVXMUtSMng1VWpoSlVrWklOazl0TTNNMllYTnRXVmw1WmtrdExXdFJaVGhJYTBkMGJYYzNUR2xpVldWWlduZDNSemxJU1d4NlNEUlFObWhJVDNOVVpsaGhSRmwyYkRVM1dXcDFibWd5Y1ZVeVlXbFhSRms1UVdsVVVrSnBiVXhDYUVsdVpuWTBOVTVZZVRsMWQzSkdPWE5aYkZodWJHZDViVlZUTVZGV2NUYzVWUzFsYmt0bVJYTXpSMnMyUlVSaVYzSkhiWHBIY0hSUFpHcHpkRWt3T1dWNGFWUklPRXRyZG5oT1JXaFFla3hrZW5rd1VGTmxSV1o0WDJsVFdXeE1aRmxKUmtWVU4yb3hNVTV2TFRGeU1rVjZRa05wVVRWdWRGSXhNMUVpZlN3aWNIVnljRzl6WlhNaU9sc2lZWFYwYUdWdWRHbGpZWFJwYjI0aUxDSmhjM05sY25ScGIyNU5aWFJvYjJRaVhTd2lkSGx3WlNJNklrcHpiMjVYWldKTFpYa3lNREl3SW4xZGZWMHNJblZ3WkdGMFpVTnZiVzFwZEcxbGJuUWlPaUpGYVVORVJsRlFabmhTUm5Oc2NrRkliVm80YVhOUWJFNDRRelpLVW1SemVuUTBabkkwYURsSU5HUm1NVlpuSW4wc0luTjFabVpwZUVSaGRHRWlPbnNpWkdWc2RHRklZWE5vSWpvaVJXbEJTRE14TkZkcVJUTkJhVFJoTFhNeVNtOUhjWFpqUzFWcFRFUmZNVEpWWjNWeFkwWTFiVWR6Um5aMFp5SXNJbkpsWTI5MlpYSjVRMjl0YldsMGJXVnVkQ0k2SWtWcFFTMXBka1pxVFRaTGRteDJZVXRVT0dkQ2EzQkphelpMWmt4dk9VbDVNR28wWVdobWFpMXJUMGwyV2xFaWZTd2lkSGx3WlNJNkltTnlaV0YwWlNKOSIsCiAgICAibmFtZSI6ICJBY21lIElzc3VlciIKICB9LAogICJ0eXBlIjogWwogICAgIlZlcmlmaWFibGVDcmVkZW50aWFsIiwKICAgICJVbml2ZXJzaXR5RGVncmVlQ3JlZGVudGlhbCIKICBdCn0= | @oidc4vc_rest_pre_auth_flow_trustlist_success diff --git a/test/bdd/fixtures/profile/profiles.json b/test/bdd/fixtures/profile/profiles.json index b3d05919e..2d9e54285 100644 --- a/test/bdd/fixtures/profile/profiles.json +++ b/test/bdd/fixtures/profile/profiles.json @@ -2145,6 +2145,220 @@ }, "createDID": true }, + { + "verifier": { + "id": "v_myprofile_jwt_no_strict", + "version": "v1.0", + "name": "v_myprofile_jwt_no_strict", + "organizationID": "00000000-0000-0000-0000-000000000001", + "url": "https://test-verifier.com", + "active": true, + "webHook": "http://vcs.webhook.example.com:8180", + "checks": { + "credential": { + "format": [ + "jwt" + ], + "proof": true, + "status": true, + "strict": false + }, + "presentation": { + "format": [ + "jwt" + ], + "vcSubject": true, + "proof": true + } + }, + "oidcConfig": { + "roSigningAlgorithm": "EcdsaSecp256k1Signature2019", + "keyType": "ECDSASecp256k1DER", + "didMethod": "ion" + }, + "presentationDefinitions": [ + { + "id": "32f54163-no-limit-disclosure-single-field", + "input_descriptors": [ + { + "id": "degree", + "name": "degree", + "purpose": "We can only hire with bachelor degree.", + "schema": [ + { + "uri": "https://www.w3.org/2018/credentials#VerifiableCredential" + } + ], + "constraints": { + "fields": [ + { + "path": [ + "$.credentialSubject.degree.type", + "$.vc.credentialSubject.degree.type" + ], + "id": "degree_type_id", + "purpose": "We can only hire with bachelor degree.", + "filter": { + "type": "string", + "const": "BachelorDegree" + } + } + ] + } + } + ] + }, + { + "id": "32f54163-no-limit-disclosure-optional-fields", + "input_descriptors": [ + { + "id": "lprCategory", + "name": "lprCategory", + "purpose": "Permanent Resident Card specification", + "schema": [ + { + "uri": "https://www.w3.org/2018/credentials#VerifiableCredential" + } + ], + "constraints": { + "fields": [ + { + "path": [ + "$.credentialSubject.lprCategory", + "$.vc.credentialSubject.lprCategory" + ], + "id": "lpr_category_id", + "purpose": "Specific LPR category.", + "filter": { + "type": "string", + "const": "C09" + } + }, + { + "path": [ + "$.credentialSubject.commuterClassification", + "$.vc.credentialSubject.commuterClassification" + ], + "id": "commuter_classification", + "purpose": "Specific commuter classification.", + "filter": { + "type": "string", + "const": "C1" + } + }, + { + "path": [ + "$.credentialSubject.registrationCity", + "$.vc.credentialSubject.registrationCity" + ], + "id": "registration_city", + "purpose": "Specific registration city.", + "optional": true, + "filter": { + "type": "string", + "const": "Albuquerque" + } + } + ] + } + } + ] + }, + { + "id": "3c8b1d9a-limit-disclosure-optional-fields", + "input_descriptors": [ + { + "id": "uom", + "name": "uom", + "purpose": "Crude oil stream specification.", + "constraints": { + "limit_disclosure": "required", + "fields": [ + { + "path": [ + "$.credentialSubject.physicalSpecs.uom", + "$.vc.credentialSubject.physicalSpecs.uom" + ], + "id": "unit_of_measure_barrel", + "purpose": "We can only use barrel UoM.", + "filter": { + "type": "string", + "const": "barrel" + } + }, + { + "path": [ + "$.credentialSubject.physicalSpecs.apiGravity", + "$.vc.credentialSubject.physicalSpecs.apiGravity" + ], + "id": "api_gravity", + "purpose": "Min API Gravity.", + "filter": { + "type": "integer", + "minimum": 20 + } + }, + { + "path": [ + "$.credentialSubject.category", + "$.vc.credentialSubject.category" + ], + "id": "category", + "purpose": "Category.", + "optional": true, + "filter": { + "type": "string" + } + }, + { + "path": [ + "$.credentialSubject.supplierAddress", + "$.vc.credentialSubject.supplierAddress" + ], + "id": "supplier_address", + "purpose": "Supplier Address.", + "optional": true, + "filter": { + "type": "object" + } + } + ] + } + } + ] + }, + { + "id": "lp403pb9-schema-match", + "input_descriptors": [ + { + "id": "schema", + "name": "schema", + "purpose": "Match credentials using specific schema.", + "constraints": { + "fields": [ + { + "path": [ + "$[\"@context\"]" + ], + "id": "schema_id", + "purpose": "Match credentials using specific schema.", + "filter": { + "type": "array", + "contains": { + "type": "string", + "pattern": "https://trustbloc.github.io/context/vc/examples-crude-product-v1.jsonld" + } + } + } + ] + } + } + ] + } + ] + }, + "createDID": true + }, { "verifier": { "id": "v_myprofile_jwt", diff --git a/test/bdd/pkg/v1/oidc4vc/models.go b/test/bdd/pkg/v1/oidc4vc/models.go index 8c24299b4..de933c50f 100644 --- a/test/bdd/pkg/v1/oidc4vc/models.go +++ b/test/bdd/pkg/v1/oidc4vc/models.go @@ -109,6 +109,7 @@ type credentialMetadata struct { IssuanceDate *util.TimeWrapper `json:"issuanceDate,omitempty"` ExpirationDate *util.TimeWrapper `json:"expirationDate,omitempty"` CustomClaims map[string]map[string]interface{} `json:"customClaims,omitempty"` + Attachments []map[string]interface{} `json:"attachments,omitempty"` } type retrievedCredentialClaims map[string]credentialMetadata diff --git a/test/bdd/pkg/v1/oidc4vc/oidc4vp.go b/test/bdd/pkg/v1/oidc4vc/oidc4vp.go index 694afa142..799e54205 100644 --- a/test/bdd/pkg/v1/oidc4vc/oidc4vp.go +++ b/test/bdd/pkg/v1/oidc4vc/oidc4vp.go @@ -18,6 +18,7 @@ import ( "time" "github.com/piprate/json-gold/ld" + "github.com/samber/lo" vdrapi "github.com/trustbloc/did-go/vdr/api" storageapi "github.com/trustbloc/kms-go/spi/storage" "github.com/trustbloc/kms-go/wrapper/api" @@ -220,11 +221,33 @@ func (s *Steps) validateRetrievedCredentialClaims(claims retrievedCredentialClai issuedVCID[vcParsed.Contents().ID] = struct{}{} } - for retrievedVCID := range claims { + for retrievedVCID, val := range claims { _, exist := issuedVCID[retrievedVCID] + if !exist { return fmt.Errorf("unexpected credential ID %s", retrievedVCID) } + + var attachments []string + for _, attachment := range val.Attachments { + attachments = append(attachments, attachment["uri"].(string)) + } + + if len(s.expectedAttachment) > 0 { + if len(attachments) != len(s.expectedAttachment) { + return fmt.Errorf("unexpected attachment amount. Expected %d, got %d", + len(s.expectedAttachment), + len(attachments), + ) + } + + for _, expectedAttachment := range s.expectedAttachment { + if !lo.Contains(attachments, expectedAttachment) { + return fmt.Errorf("attachment %s not found", expectedAttachment) + } + } + } + } return nil From 463aa2bc2c2904965fab80a6a7ef796f2cee9064 Mon Sep 17 00:00:00 2001 From: Stas Dm Date: Tue, 2 Jul 2024 01:10:49 +0200 Subject: [PATCH 6/7] chore: cleanup --- pkg/service/oidc4vp/attachments.go | 21 ++++++++++++++------- pkg/service/oidc4vp/attachments_test.go | 18 +++++++++--------- test/bdd/pkg/v1/oidc4vc/steps.go | 1 - 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/pkg/service/oidc4vp/attachments.go b/pkg/service/oidc4vp/attachments.go index 2640c16ed..dddf5e0a4 100644 --- a/pkg/service/oidc4vp/attachments.go +++ b/pkg/service/oidc4vp/attachments.go @@ -28,7 +28,7 @@ const ( AttachmentDataField = "uri" ) -var knownAttachmentTypes = []string{AttachmentTypeRemote, AttachmentTypeEmbedded} +var knownAttachmentTypes = []string{AttachmentTypeRemote, AttachmentTypeEmbedded} // nolint:gochecknoglobals type AttachmentService struct { httpClient httpClient @@ -66,7 +66,9 @@ func (s *AttachmentService) GetAttachments( var wg sync.WaitGroup for _, attachment := range allAttachments { - attachment.Claim = maphelpers.CopyMap(attachment.Claim) // clone + cloned := maphelpers.CopyMap(attachment.Claim) + attachment.Claim = cloned + final = append(final, attachment.Claim) if attachment.Type == AttachmentTypeRemote { @@ -74,9 +76,9 @@ func (s *AttachmentService) GetAttachments( go func() { defer wg.Done() - err := s.handleRemoteAttachment(ctx, attachment.Claim) + err := s.handleRemoteAttachment(ctx, cloned) if err != nil { - attachment.Claim["error"] = fmt.Sprintf("failed to handle remote attachment: %s", err) + cloned["error"] = fmt.Sprintf("failed to handle remote attachment: %s", err) } }() } @@ -90,12 +92,12 @@ func (s *AttachmentService) handleRemoteAttachment( ctx context.Context, attachment map[string]interface{}, ) error { - targetUrl := fmt.Sprint(attachment[AttachmentDataField]) - if targetUrl == "" { + targetURL := fmt.Sprint(attachment[AttachmentDataField]) + if targetURL == "" { return errors.New("url is required") } - httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, targetUrl, nil) + httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil) if err != nil { return fmt.Errorf("failed to create http request: %w", err) } @@ -107,6 +109,10 @@ func (s *AttachmentService) handleRemoteAttachment( var body []byte if resp.Body != nil { + defer func() { + _ = resp.Body.Close() // nolint + }() + body, err = io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("failed to read response body: %w", err) @@ -125,6 +131,7 @@ func (s *AttachmentService) handleRemoteAttachment( return nil } +// nolint:gocognit func (s *AttachmentService) findAttachments( targetMap map[string]interface{}, ) []*Attachment { diff --git a/pkg/service/oidc4vp/attachments_test.go b/pkg/service/oidc4vp/attachments_test.go index a0b7d7f39..4c4bd935c 100644 --- a/pkg/service/oidc4vp/attachments_test.go +++ b/pkg/service/oidc4vp/attachments_test.go @@ -64,10 +64,10 @@ func TestAttachment(t *testing.T) { var data map[string]interface{} assert.NoError(t, json.Unmarshal([]byte(sampleVCWithRemoteAttachment), &data)) - mockHttp := NewMockHttpClient(gomock.NewController(t)) - srv := oidc4vp.NewAttachmentService(mockHttp) + mockHTTP := NewMockHttpClient(gomock.NewController(t)) + srv := oidc4vp.NewAttachmentService(mockHTTP) - mockHttp.EXPECT().Do(gomock.Any()). + mockHTTP.EXPECT().Do(gomock.Any()). DoAndReturn(func(request *http.Request) (*http.Response, error) { assert.EqualValues(t, "https://someurl.local", request.URL.String()) assert.EqualValues(t, "GET", request.Method) @@ -90,10 +90,10 @@ func TestAttachment(t *testing.T) { var data map[string]interface{} assert.NoError(t, json.Unmarshal([]byte(sampleVCWithRemoteAttachment), &data)) - mockHttp := NewMockHttpClient(gomock.NewController(t)) - srv := oidc4vp.NewAttachmentService(mockHttp) + mockHTTP := NewMockHttpClient(gomock.NewController(t)) + srv := oidc4vp.NewAttachmentService(mockHTTP) - mockHttp.EXPECT().Do(gomock.Any()). + mockHTTP.EXPECT().Do(gomock.Any()). DoAndReturn(func(request *http.Request) (*http.Response, error) { assert.EqualValues(t, "https://someurl.local", request.URL.String()) assert.EqualValues(t, "GET", request.Method) @@ -157,13 +157,13 @@ func TestAttachment(t *testing.T) { var data map[string]interface{} assert.NoError(t, json.Unmarshal([]byte(sampleVCWitAttachments), &data)) - mockHttp := NewMockHttpClient(gomock.NewController(t)) - srv := oidc4vp.NewAttachmentService(mockHttp) + mockHTTP := NewMockHttpClient(gomock.NewController(t)) + srv := oidc4vp.NewAttachmentService(mockHTTP) var mut sync.Mutex urlsCalled := []string{} - mockHttp.EXPECT().Do(gomock.Any()). + mockHTTP.EXPECT().Do(gomock.Any()). DoAndReturn(func(request *http.Request) (*http.Response, error) { mut.Lock() urlsCalled = append(urlsCalled, request.URL.String()) diff --git a/test/bdd/pkg/v1/oidc4vc/steps.go b/test/bdd/pkg/v1/oidc4vc/steps.go index e726a7839..f731128a6 100644 --- a/test/bdd/pkg/v1/oidc4vc/steps.go +++ b/test/bdd/pkg/v1/oidc4vc/steps.go @@ -124,7 +124,6 @@ func (s *Steps) RegisterSteps(sc *godog.ScenarioContext) { sc.Step(`^User interacts with Verifier and initiate OIDC4VP interaction under "([^"]*)" profile with presentation definition ID "([^"]*)" and fields "([^"]*)"$`, s.runOIDC4VPFlow) sc.Step(`^User interacts with Verifier and initiate OIDC4VP interaction under "([^"]*)" profile with presentation definition ID "([^"]*)" and fields "([^"]*)" and custom scopes "([^"]*)"$`, s.runOIDC4VPFlowWithCustomScopes) sc.Step(`^Verifier with profile "([^"]*)" retrieves interactions claims$`, s.retrieveInteractionsClaim) - sc.Step(`^Verifier with profile "([^"]*)" retrieves interactions claims and expects "([^"]*)" attachments$`, s.retrieveInteractionsClaim) sc.Step(`^Verifier with profile "([^"]*)" retrieves interactions claims with additional claims associated with custom scopes "([^"]*)"$`, s.retrieveInteractionsClaimWithCustomScopes) sc.Step(`^wallet configured to use hardcoded vp_token format "([^"]*)" for OIDC4VP interaction$`, s.setHardcodedVPTokenFormat) From 2047504bd6fec7ee5703ac40eefe4e6aa62d0b16 Mon Sep 17 00:00:00 2001 From: Stas Dm Date: Tue, 2 Jul 2024 13:33:17 +0200 Subject: [PATCH 7/7] fix: lint --- pkg/service/oidc4vp/attachments_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/service/oidc4vp/attachments_test.go b/pkg/service/oidc4vp/attachments_test.go index 4c4bd935c..ba22e635e 100644 --- a/pkg/service/oidc4vp/attachments_test.go +++ b/pkg/service/oidc4vp/attachments_test.go @@ -202,14 +202,18 @@ func TestAttachment(t *testing.T) { attachment = resp[0] assert.EqualValues(t, "doc1", attachment["id"]) assert.EqualValues(t, []interface{}{"RemoteAttachment"}, attachment["type"]) - assert.EqualValues(t, "data:image/svg;base64,YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9jYXQucG5n", attachment["uri"]) + assert.EqualValues(t, "data:image/svg;base64,YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9jYXQucG5n", + attachment["uri"]) + assert.Nil(t, attachment["error"]) assert.EqualValues(t, "abcd", attachment["hash"]) attachment = resp[2] assert.EqualValues(t, "doc445", attachment["id"]) assert.EqualValues(t, []interface{}{"RemoteAttachment"}, attachment["type"]) - assert.EqualValues(t, "data:image/svg;base64,YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9waG90by5wbmc=", attachment["uri"]) + assert.EqualValues(t, "data:image/svg;base64,YmFzZTY0Y29udGVudC1odHRwczovL2xvY2FsaG9zdC9waG90by5wbmc=", + attachment["uri"]) + assert.Nil(t, attachment["error"]) assert.EqualValues(t, "xyz", attachment["hash"]) })