diff --git a/pkg/doc/presexch/definition.go b/pkg/doc/presexch/definition.go index 6bc160ee0..4b743244e 100644 --- a/pkg/doc/presexch/definition.go +++ b/pkg/doc/presexch/definition.go @@ -366,38 +366,90 @@ func makeRequirement(requirements []*SubmissionRequirement, descriptors []*Input // CreateVP creates verifiable presentation. func (pd *PresentationDefinition) CreateVP(credentials []*verifiable.Credential, documentLoader ld.DocumentLoader, opts ...verifiable.CredentialOpt) (*verifiable.Presentation, error) { - if err := pd.ValidateSchema(); err != nil { + applicableCredentials, submission, err := presentationData(pd, credentials, documentLoader, false, opts...) + if err != nil { return nil, err } - req, err := makeRequirement(pd.SubmissionRequirements, pd.InputDescriptors) + vp, err := presentation(applicableCredentials...) if err != nil { return nil, err } - format, result, err := pd.applyRequirement(req, credentials, documentLoader, opts...) + vp.CustomFields = verifiable.CustomFields{ + submissionProperty: submission, + } + + return vp, nil +} + +// CreateVPArray creates a list of verifiable presentations, with one presentation for each provided credential. +// A PresentationSubmission is returned alongside, which uses the presentation list as the root for json paths. +func (pd *PresentationDefinition) CreateVPArray( + credentials []*verifiable.Credential, + documentLoader ld.DocumentLoader, + opts ...verifiable.CredentialOpt, +) ([]*verifiable.Presentation, *PresentationSubmission, error) { + applicableCredentials, submission, err := presentationData(pd, credentials, documentLoader, true, opts...) if err != nil { - return nil, err + return nil, nil, err + } + + var presentations []*verifiable.Presentation + + for _, credential := range applicableCredentials { + vp, e := presentation(credential) + if e != nil { + return nil, nil, e + } + + presentations = append(presentations, vp) } - applicableCredentials, descriptors := merge(format, result) + return presentations, submission, nil +} + +func presentationData( + pd *PresentationDefinition, + credentials []*verifiable.Credential, + documentLoader ld.DocumentLoader, + separatePresentations bool, + opts ...verifiable.CredentialOpt, +) ([]*verifiable.Credential, *PresentationSubmission, error) { + if err := pd.ValidateSchema(); err != nil { + return nil, nil, err + } - vp, err := verifiable.NewPresentation(verifiable.WithCredentials(applicableCredentials...)) + req, err := makeRequirement(pd.SubmissionRequirements, pd.InputDescriptors) if err != nil { - return nil, err + return nil, nil, err } - vp.Context = append(vp.Context, PresentationSubmissionJSONLDContextIRI) - vp.Type = append(vp.Type, PresentationSubmissionJSONLDType) + format, result, err := pd.applyRequirement(req, credentials, documentLoader, opts...) + if err != nil { + return nil, nil, err + } - vp.CustomFields = verifiable.CustomFields{ - submissionProperty: &PresentationSubmission{ - ID: uuid.New().String(), - DefinitionID: pd.ID, - DescriptorMap: descriptors, - }, + applicableCredentials, descriptors := merge(format, result, separatePresentations) + + submission := &PresentationSubmission{ + ID: uuid.New().String(), + DefinitionID: pd.ID, + DescriptorMap: descriptors, } + return applicableCredentials, submission, nil +} + +func presentation(credentials ...*verifiable.Credential) (*verifiable.Presentation, error) { + vp, e := verifiable.NewPresentation(verifiable.WithCredentials(credentials...)) + if e != nil { + return nil, e + } + + vp.Context = append(vp.Context, PresentationSubmissionJSONLDContextIRI) + vp.Type = append(vp.Type, PresentationSubmissionJSONLDType) + return vp, nil } @@ -1193,7 +1245,11 @@ func getPath(keys []interface{}, set map[string]int) [2]string { return [...]string{strings.Join(newPath, "."), strings.Join(originalPath, ".")} } -func merge(presentationFormat string, setOfCredentials map[string][]*verifiable.Credential) ([]*verifiable.Credential, []*InputDescriptorMapping) { //nolint:lll +func merge( + presentationFormat string, + setOfCredentials map[string][]*verifiable.Credential, + separatePresentations bool, +) ([]*verifiable.Credential, []*InputDescriptorMapping) { //nolint:lll setOfCreds := make(map[string]int) setOfDescriptors := make(map[string]struct{}) @@ -1225,16 +1281,24 @@ func merge(presentationFormat string, setOfCredentials map[string][]*verifiable. } if _, ok := setOfDescriptors[fmt.Sprintf("%s-%s", credential.ID, credential.ID)]; !ok { - descriptors = append(descriptors, &InputDescriptorMapping{ + desc := &InputDescriptorMapping{ ID: descriptorID, Format: presentationFormat, - Path: "$", PathNested: &InputDescriptorMapping{ ID: descriptorID, Format: vcFormat, - Path: fmt.Sprintf("$.verifiableCredential[%d]", setOfCreds[credential.ID]), }, - }) + } + + if separatePresentations { + desc.Path = fmt.Sprintf("$[%d]", setOfCreds[credential.ID]) + desc.PathNested.Path = "$.verifiableCredential[0]" + } else { + desc.Path = "$" + desc.PathNested.Path = fmt.Sprintf("$.verifiableCredential[%d]", setOfCreds[credential.ID]) + } + + descriptors = append(descriptors, desc) } } } diff --git a/pkg/doc/presexch/definition_test.go b/pkg/doc/presexch/definition_test.go index 8a675b01a..cec6559b8 100644 --- a/pkg/doc/presexch/definition_test.go +++ b/pkg/doc/presexch/definition_test.go @@ -2017,6 +2017,50 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { }) } +func TestPresentationDefinition_CreateVPArray(t *testing.T) { + lddl := createTestJSONLDDocumentLoader(t) + + t.Run("Matches two descriptors", func(t *testing.T) { + pd := &PresentationDefinition{ + ID: uuid.New().String(), + InputDescriptors: []*InputDescriptor{{ + ID: uuid.New().String(), + Schema: []*Schema{{ + URI: "https://example.org/examples#UniversityDegreeCredential", + }}, + }, { + ID: uuid.New().String(), + Schema: []*Schema{{ + URI: "https://example.org/examples#DocumentVerification", + }}, + }}, + } + + vpList, ps, err := pd.CreateVPArray([]*verifiable.Credential{ + { + Context: []string{verifiable.ContextURI, "https://www.w3.org/2018/credentials/examples/v1"}, + Types: []string{verifiable.VCType, "UniversityDegreeCredential"}, + ID: uuid.New().String(), + }, + { + Context: []string{verifiable.ContextURI, "https://trustbloc.github.io/context/vc/examples-v1.jsonld"}, + Types: []string{verifiable.VCType, "DocumentVerification"}, + ID: uuid.New().String(), + }, + }, lddl) + + require.NoError(t, err) + require.NotNil(t, vpList) + require.Len(t, vpList, 2) + + checkExternalSubmission(t, vpList, ps, pd) + + for _, vp := range vpList { + checkVP(t, vp) + } + }) +} + func createEdDSAJWS(t *testing.T, cred *verifiable.Credential, signer verifiable.Signer, keyID string, minimize bool) string { t.Helper() @@ -2159,6 +2203,37 @@ func checkSubmission(t *testing.T, vp *verifiable.Presentation, pd *Presentation } } +func checkExternalSubmission( + t *testing.T, + vpList []*verifiable.Presentation, + ps *PresentationSubmission, + pd *PresentationDefinition, +) { + t.Helper() + + require.NotEmpty(t, ps.ID) + require.Equal(t, ps.DefinitionID, pd.ID) + + src, err := json.Marshal(vpList) + require.NoError(t, err) + + rawVPList := []interface{}{} + require.NoError(t, json.Unmarshal(src, &rawVPList)) + + builder := gval.Full(jsonpath.PlaceholderExtension()) + + for _, descriptor := range ps.DescriptorMap { + require.NotEmpty(t, descriptor.ID) + require.NotEmpty(t, descriptor.Path) + require.NotEmpty(t, descriptor.Format) + + path, err := builder.NewEvaluable(descriptor.Path) + require.NoError(t, err) + _, err = path(context.TODO(), rawVPList) + require.NoError(t, err) + } +} + func checkVP(t *testing.T, vp *verifiable.Presentation) { t.Helper() diff --git a/pkg/doc/verifiable/credential.go b/pkg/doc/verifiable/credential.go index 1fbcbda0b..ea7f58ac2 100644 --- a/pkg/doc/verifiable/credential.go +++ b/pkg/doc/verifiable/credential.go @@ -586,6 +586,7 @@ type credentialOpts struct { strictValidation bool ldpSuites []verifier.SignatureSuite defaultSchema string + disableValidation bool jsonldCredentialOpts } @@ -600,6 +601,13 @@ func WithDisabledProofCheck() CredentialOpt { } } +// WithCredDisableValidation options for disabling of JSON-LD and json-schema validation. +func WithCredDisableValidation() CredentialOpt { + return func(opts *credentialOpts) { + opts.disableValidation = true + } +} + // WithSchema option to set custom schema. func WithSchema(schema string) CredentialOpt { return func(opts *credentialOpts) { @@ -839,7 +847,7 @@ func ParseCredential(vcData []byte, opts ...CredentialOpt) (*Credential, error) return nil, err } - if externalJWT == "" { + if externalJWT == "" && !vcOpts.disableValidation { // TODO: consider new validation options for, eg, jsonschema only, for JWT VC err = validateCredential(vc, vcDataDecoded, vcOpts) if err != nil { diff --git a/pkg/doc/verifiable/credential_test.go b/pkg/doc/verifiable/credential_test.go index 71d7f67cc..b7e88700a 100644 --- a/pkg/doc/verifiable/credential_test.go +++ b/pkg/doc/verifiable/credential_test.go @@ -815,6 +815,15 @@ func TestWithDisabledProofCheck(t *testing.T) { require.True(t, opts.disabledProofCheck) } +func TestWithCredDisableValidation(t *testing.T) { + credentialOpt := WithCredDisableValidation() + require.NotNil(t, credentialOpt) + + opts := &credentialOpts{} + credentialOpt(opts) + require.True(t, opts.disableValidation) +} + func TestWithCredentialSchemaLoader(t *testing.T) { httpClient := &http.Client{} jsonSchemaLoader := gojsonschema.NewStringLoader(JSONSchemaLoader())