Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

Commit

Permalink
feat: PresentationDefinition API improvements: (#3547)
Browse files Browse the repository at this point in the history
- CreateVP methods set VP IDs
- Match supports merged submission map[string]interface{} and
  VP submission fields of PresentationSubmission type.

Signed-off-by: Filip Burlacu <filip.burlacu@securekey.com>
  • Loading branch information
Moopli committed Mar 7, 2023
1 parent 06f7338 commit 8771727
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 42 deletions.
41 changes: 38 additions & 3 deletions pkg/doc/presexch/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type MatchOptions struct {
CredentialOptions []verifiable.CredentialOpt
DisableSchemaValidation bool
MergedSubmission *PresentationSubmission
MergedSubmissionMap map[string]interface{}
}

// MatchOption is an option that sets an option for when matching.
Expand Down Expand Up @@ -96,6 +97,18 @@ func WithMergedSubmission(submission *PresentationSubmission) MatchOption {
}
}

// WithMergedSubmissionMap provides a presentation submission that's external to the Presentations being matched,
// which contains the descriptor mapping for each Presentation. This submission is expected to be in the
// map[string]interface{} format used by json.Unmarshal.
//
// If there are multiple Presentations, this merged submission should use the Presentation array as the JSON Path root
// when referencing the contained Presentations and the Credentials within.
func WithMergedSubmissionMap(submissionMap map[string]interface{}) MatchOption {
return func(m *MatchOptions) {
m.MergedSubmissionMap = submissionMap
}
}

// Match returns the credentials matched against the InputDescriptors ids.
func (pd *PresentationDefinition) Match(vpList []*verifiable.Presentation,
contextLoader ld.DocumentLoader, options ...MatchOption) (map[string]MatchValue, error) {
Expand Down Expand Up @@ -128,12 +141,21 @@ func getMatchedCreds( //nolint:gocyclo,funlen

descriptorIDs := descriptorIDs(pd.InputDescriptors)

useMergedSubmission := opts.MergedSubmission != nil
useMergedSubmission := opts.MergedSubmission != nil || len(opts.MergedSubmissionMap) != 0

var mappingsByVPIndex map[int][]*InputDescriptorMapping

if useMergedSubmission {
if opts.MergedSubmission != nil {
mappingsByVPIndex = descriptorsByPresentationIndex(opts.MergedSubmission.DescriptorMap)
} else if len(opts.MergedSubmissionMap) != 0 {
useMergedSubmission = true

descs, err := getDescriptorMapping(opts.MergedSubmissionMap)
if err != nil {
return nil, fmt.Errorf("failed to parse descriptor map: %w", err)
}

mappingsByVPIndex = descriptorsByPresentationIndex(descs)
}

rawVPs := make([]interface{}, len(vpList))
Expand Down Expand Up @@ -295,11 +317,24 @@ func checkJSONLDContextType(vp *verifiable.Presentation) error {
}

func parseDescriptorMap(vp *verifiable.Presentation) ([]*InputDescriptorMapping, error) {
submission, ok := vp.CustomFields[submissionProperty].(map[string]interface{})
untypedSubmission, ok := vp.CustomFields[submissionProperty]
if !ok {
return nil, fmt.Errorf("missing '%s' on verifiable presentation", submissionProperty)
}

switch submission := untypedSubmission.(type) {
case map[string]interface{}:
return getDescriptorMapping(submission)
case *PresentationSubmission:
return submission.DescriptorMap, nil
case PresentationSubmission:
return submission.DescriptorMap, nil
default:
return nil, fmt.Errorf("missing '%s' on verifiable presentation", submissionProperty)
}
}

func getDescriptorMapping(submission map[string]interface{}) ([]*InputDescriptorMapping, error) {
descriptorMap, ok := submission[descriptorMapProperty].([]interface{})
if !ok {
return nil, fmt.Errorf("missing '%s' on verifiable presentation", descriptorMapProperty)
Expand Down
87 changes: 58 additions & 29 deletions pkg/doc/presexch/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,22 +117,25 @@ func TestPresentationDefinition_Match(t *testing.T) {

docLoader := createTestDocumentLoader(t, uri, customType)

presList := []*verifiable.Presentation{
newVP(t,
&PresentationSubmission{
DescriptorMap: []*InputDescriptorMapping{
{
vpWithSubmissionType := newVP(t, nil, expected)

vpWithSubmissionType.CustomFields = map[string]interface{}{
"presentation_submission": &PresentationSubmission{
DescriptorMap: []*InputDescriptorMapping{
{
ID: defs.InputDescriptors[0].ID,
Path: "$", // TODO: $[0]
PathNested: &InputDescriptorMapping{
ID: defs.InputDescriptors[0].ID,
Path: "$", // TODO: $[0]
PathNested: &InputDescriptorMapping{
ID: defs.InputDescriptors[0].ID,
Path: "$.verifiableCredential[0]",
},
Path: "$.verifiableCredential[0]",
},
},
},
expected,
),
},
}

presList := []*verifiable.Presentation{
vpWithSubmissionType,
newVP(t,
&PresentationSubmission{
DescriptorMap: []*InputDescriptorMapping{
Expand Down Expand Up @@ -209,28 +212,30 @@ func TestPresentationDefinition_Match(t *testing.T) {
newVP(t, nil, expectedTwo),
}

opt := []MatchOption{
WithCredentialOptions(verifiable.WithJSONLDDocumentLoader(docLoader)),
WithMergedSubmission(&PresentationSubmission{
DescriptorMap: []*InputDescriptorMapping{
{
submission := &PresentationSubmission{
DescriptorMap: []*InputDescriptorMapping{
{
ID: defs.InputDescriptors[0].ID,
Path: "$[0]",
PathNested: &InputDescriptorMapping{
ID: defs.InputDescriptors[0].ID,
Path: "$[0]",
PathNested: &InputDescriptorMapping{
ID: defs.InputDescriptors[0].ID,
Path: "$.verifiableCredential[0]",
},
Path: "$.verifiableCredential[0]",
},
{
},
{
ID: defs.InputDescriptors[1].ID,
Path: "$[1]",
PathNested: &InputDescriptorMapping{
ID: defs.InputDescriptors[1].ID,
Path: "$[1]",
PathNested: &InputDescriptorMapping{
ID: defs.InputDescriptors[1].ID,
Path: "$.verifiableCredential[0]",
},
Path: "$.verifiableCredential[0]",
},
},
}),
},
}

opt := []MatchOption{
WithCredentialOptions(verifiable.WithJSONLDDocumentLoader(docLoader)),
WithMergedSubmission(submission),
}

matched, err := defs.Match(presList, docLoader, opt...)
Expand All @@ -242,6 +247,30 @@ func TestPresentationDefinition_Match(t *testing.T) {
result, ok = matched[defs.InputDescriptors[1].ID]
require.True(t, ok)
require.Equal(t, expectedTwo.ID, result.Credential.ID)

// json-unmarshalled merged submission

submissionJSON := map[string]interface{}{}

submissionBytes, err := json.Marshal(submission)
require.NoError(t, err)

require.NoError(t, json.Unmarshal(submissionBytes, &submissionJSON))

opt = []MatchOption{
WithCredentialOptions(verifiable.WithJSONLDDocumentLoader(docLoader)),
WithMergedSubmissionMap(submissionJSON),
}

matched, err = defs.Match(presList, docLoader, opt...)
require.NoError(t, err)
require.Len(t, matched, 2)
result, ok = matched[defs.InputDescriptors[0].ID]
require.True(t, ok)
require.Equal(t, expected.ID, result.Credential.ID)
result, ok = matched[defs.InputDescriptors[1].ID]
require.True(t, ok)
require.Equal(t, expectedTwo.ID, result.Credential.ID)
})

t.Run("match one presentation, with merged submission", func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions pkg/doc/presexch/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ func presentation(credentials ...*verifiable.Credential) (*verifiable.Presentati

vp.Context = append(vp.Context, PresentationSubmissionJSONLDContextIRI)
vp.Type = append(vp.Type, PresentationSubmissionJSONLDType)
vp.ID = uuid.NewString()

return vp, nil
}
Expand Down
14 changes: 14 additions & 0 deletions pkg/doc/presexch/example_v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func ExamplePresentationDefinition_CreateVP_v1() {
panic(err)
}

vp.ID = dummy
vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy

vpBytes, err := json.MarshalIndent(vp, "", "\t")
Expand All @@ -90,6 +91,7 @@ func ExamplePresentationDefinition_CreateVP_v1() {
// "https://www.w3.org/2018/credentials/v1",
// "https://identity.foundation/presentation-exchange/submission/v1"
// ],
// "id": "DUMMY",
// "presentation_submission": {
// "id": "DUMMY",
// "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd",
Expand Down Expand Up @@ -184,6 +186,7 @@ func ExamplePresentationDefinition_CreateVP_v1_With_LDP_FormatAndProof() {
panic(err)
}

vp.ID = dummy
vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy

vpBytes, err := json.MarshalIndent(vp, "", "\t")
Expand All @@ -198,6 +201,7 @@ func ExamplePresentationDefinition_CreateVP_v1_With_LDP_FormatAndProof() {
// "https://www.w3.org/2018/credentials/v1",
// "https://identity.foundation/presentation-exchange/submission/v1"
// ],
// "id": "DUMMY",
// "presentation_submission": {
// "id": "DUMMY",
// "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd",
Expand Down Expand Up @@ -292,6 +296,7 @@ func ExamplePresentationDefinition_CreateVP_v1_With_LDPVC_FormatAndProof() {
panic(err)
}

vp.ID = dummy
vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy

vpBytes, err := json.MarshalIndent(vp, "", "\t")
Expand All @@ -306,6 +311,7 @@ func ExamplePresentationDefinition_CreateVP_v1_With_LDPVC_FormatAndProof() {
// "https://www.w3.org/2018/credentials/v1",
// "https://identity.foundation/presentation-exchange/submission/v1"
// ],
// "id": "DUMMY",
// "presentation_submission": {
// "id": "DUMMY",
// "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd",
Expand Down Expand Up @@ -424,6 +430,7 @@ func ExamplePresentationDefinition_CreateVP_multipleMatches() {
panic(err)
}

vp.ID = dummy
vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy

vpBytes, err := json.MarshalIndent(vp, "", "\t")
Expand All @@ -438,6 +445,7 @@ func ExamplePresentationDefinition_CreateVP_multipleMatches() {
// "https://www.w3.org/2018/credentials/v1",
// "https://identity.foundation/presentation-exchange/submission/v1"
// ],
// "id": "DUMMY",
// "presentation_submission": {
// "id": "DUMMY",
// "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd",
Expand Down Expand Up @@ -604,6 +612,7 @@ func ExamplePresentationDefinition_CreateVP_multipleMatchesDisclosure() {
panic(err)
}

vp.ID = dummy
vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy

vpBytes, err := json.MarshalIndent(vp, "", "\t")
Expand All @@ -618,6 +627,7 @@ func ExamplePresentationDefinition_CreateVP_multipleMatchesDisclosure() {
// "https://www.w3.org/2018/credentials/v1",
// "https://identity.foundation/presentation-exchange/submission/v1"
// ],
// "id": "DUMMY",
// "presentation_submission": {
// "id": "DUMMY",
// "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd",
Expand Down Expand Up @@ -848,6 +858,7 @@ func ExamplePresentationDefinition_CreateVP_submissionRequirementsLimitDisclosur
panic(err)
}

vp.ID = dummy
vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy

vpBytes, err := json.MarshalIndent(vp, "", "\t")
Expand All @@ -862,6 +873,7 @@ func ExamplePresentationDefinition_CreateVP_submissionRequirementsLimitDisclosur
// "https://www.w3.org/2018/credentials/v1",
// "https://identity.foundation/presentation-exchange/submission/v1"
// ],
// "id": "DUMMY",
// "presentation_submission": {
// "id": "DUMMY",
// "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd",
Expand Down Expand Up @@ -1090,6 +1102,7 @@ func ExamplePresentationDefinition_CreateVP_submissionRequirements() {
panic(err)
}

vp.ID = dummy
vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy

vpBytes, err := json.MarshalIndent(vp, "", "\t")
Expand All @@ -1104,6 +1117,7 @@ func ExamplePresentationDefinition_CreateVP_submissionRequirements() {
// "https://www.w3.org/2018/credentials/v1",
// "https://identity.foundation/presentation-exchange/submission/v1"
// ],
// "id": "DUMMY",
// "presentation_submission": {
// "id": "DUMMY",
// "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd",
Expand Down
Loading

0 comments on commit 8771727

Please sign in to comment.