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

Commit

Permalink
feat: presentation definition can match array of presentations
Browse files Browse the repository at this point in the history
Signed-off-by: Filip Burlacu <filip.burlacu@securekey.com>
  • Loading branch information
Filip Burlacu committed Mar 2, 2023
1 parent bd8e271 commit c9838a0
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 196 deletions.
29 changes: 15 additions & 14 deletions pkg/doc/cm/credentialapplication.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@ func UnmarshalAndValidateAgainstCredentialManifest(credentialApplicationBytes []
// ValidateCredentialApplication validates credential application presentation by validating
// the embedded Credential Application object against the given Credential Manifest.
// There are 3 requirements for the Credential Application to be valid against the Credential Manifest:
// 1. Credential Application's manifest ID must match the Credential Manifest's ID.
// 2. If the Credential Manifest has a format property, the Credential Application must also have a
// format property which is a subset of the Credential Manifest's.
// 3. If the Credential Manifest contains a presentation_definition property, the Credential Application
// must have a matching presentation_submission property.
// 1. Credential Application's manifest ID must match the Credential Manifest's ID.
// 2. If the Credential Manifest has a format property, the Credential Application must also have a
// format property which is a subset of the Credential Manifest's.
// 3. If the Credential Manifest contains a presentation_definition property, the Credential Application
// must have a matching presentation_submission property.
//
// Proof of all individual credentials can also be validated by using options.
// Refer to https://identity.foundation/credential-manifest/#credential-application for more info.
func ValidateCredentialApplication(application *verifiable.Presentation, cm *CredentialManifest,
Expand Down Expand Up @@ -111,7 +112,7 @@ func ValidateCredentialApplication(application *verifiable.Presentation, cm *Cre
return nil
}

_, err = cm.PresentationDefinition.Match(application, contextLoader, options...)
_, err = cm.PresentationDefinition.Match([]*verifiable.Presentation{application}, contextLoader, options...)

return err
}
Expand Down Expand Up @@ -260,14 +261,14 @@ func WithExistingPresentationForPresentCredentialApplication(
// "https://identity.foundation/presentation-exchange/submission/v1" context is found, it will be replaced with
// the "https://identity.foundation/credential-manifest/application/v1" context. Note that any existing proofs are
// not updated. Note also the following assumptions/limitations of this method:
// 1. The format of all claims in the Presentation Submission are assumed to be ldp_vp and will be set as such.
// 2. The format for the Credential Application object will be set to match the format from the Credential Manifest
// exactly. If a caller wants to use a smaller subset of the Credential Manifest's format, then they will have to
// set it manually.
// 3. The location of the Verifiable Credentials is assumed to be an array at the root under a field called
// "verifiableCredential".
// 4. The Verifiable Credentials in the presentation is assumed to be in the same order as the Output Descriptors in
// the Credential Manifest.
// 1. The format of all claims in the Presentation Submission are assumed to be ldp_vp and will be set as such.
// 2. The format for the Credential Application object will be set to match the format from the Credential Manifest
// exactly. If a caller wants to use a smaller subset of the Credential Manifest's format, then they will have to
// set it manually.
// 3. The location of the Verifiable Credentials is assumed to be an array at the root under a field called
// "verifiableCredential".
// 4. The Verifiable Credentials in the presentation is assumed to be in the same order as the Output Descriptors in
// the Credential Manifest.
func PresentCredentialApplication(credentialManifest *CredentialManifest,
opts ...PresentCredentialApplicationOpt) (*verifiable.Presentation, error) {
if credentialManifest == nil {
Expand Down
106 changes: 60 additions & 46 deletions pkg/doc/presexch/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ type InputDescriptorMapping struct {
PathNested *InputDescriptorMapping `json:"path_nested,omitempty"`
}

// MatchValue holds a matched credential from PresentationDefinition.Match, along with the ID of the
// presentation that held the matched credential.
type MatchValue struct {
PresentationID string
Credential *verifiable.Credential
}

// MatchOptions is a holder of options that can set when matching a submission against definitions.
type MatchOptions struct {
CredentialOptions []verifiable.CredentialOpt
Expand All @@ -76,70 +83,77 @@ func WithDisableSchemaValidation() MatchOption {
}

// Match returns the credentials matched against the InputDescriptors ids.
func (pd *PresentationDefinition) Match(vp *verifiable.Presentation, // nolint:gocyclo,funlen
contextLoader ld.DocumentLoader, options ...MatchOption) (map[string]*verifiable.Credential, error) {
func (pd *PresentationDefinition) Match(vpList []*verifiable.Presentation, // nolint:gocyclo,funlen
contextLoader ld.DocumentLoader, options ...MatchOption) (map[string]MatchValue, error) {
opts := &MatchOptions{}

for i := range options {
options[i](opts)
}

err := checkJSONLDContextType(vp)
if err != nil {
return nil, err
}

vpBits, err := vp.MarshalJSON()
if err != nil {
return nil, fmt.Errorf("failed to marshal vp: %w", err)
}

typelessVP := interface{}(nil)

err = json.Unmarshal(vpBits, &typelessVP)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal vp: %w", err)
}
result := make(map[string]MatchValue)

descriptorIDs := descriptorIDs(pd.InputDescriptors)

descriptorMap, err := parseDescriptorMap(vp)
if err != nil {
return nil, fmt.Errorf("failed to parse descriptor map: %w", err)
}

result := make(map[string]*verifiable.Credential)

for i := range descriptorMap {
mapping := descriptorMap[i]
// The object MUST include an id property, and its value MUST be a string matching the id property of
// the Input Descriptor in the Presentation Definition the submission is related to.
if !stringsContain(descriptorIDs, mapping.ID) {
return nil, fmt.Errorf(
"an %s ID was found that did not match the `id` property of any input descriptor: %s",
descriptorMapProperty, mapping.ID)
for _, vp := range vpList {
err := checkJSONLDContextType(vp)
if err != nil {
return nil, err
}

vc, selectErr := selectVC(typelessVP, mapping, opts)
if selectErr != nil {
return nil, selectErr
vpBits, err := vp.MarshalJSON()
if err != nil {
return nil, fmt.Errorf("failed to marshal vp: %w", err)
}

inputDescriptor := pd.inputDescriptor(mapping.ID)
typelessVP := interface{}(nil)

passed := filterSchema(inputDescriptor.Schema, []*verifiable.Credential{vc}, contextLoader)
if len(passed) == 0 && !opts.DisableSchemaValidation {
return nil, fmt.Errorf(
"input descriptor id [%s] requires schemas %+v which do not match vc with @context [%+v] and types [%+v] selected by path [%s]", // nolint:lll
inputDescriptor.ID, inputDescriptor.Schema, vc.Context, vc.Types, mapping.Path)
err = json.Unmarshal(vpBits, &typelessVP)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal vp: %w", err)
}

// TODO add support for constraints: https://github.com/hyperledger/aries-framework-go/issues/2108
descriptorMap, err := parseDescriptorMap(vp)
if err != nil {
return nil, fmt.Errorf("failed to parse descriptor map: %w", err)
}

result[mapping.ID] = vc
for i := range descriptorMap {
mapping := descriptorMap[i]
// The object MUST include an id property, and its value MUST be a string matching the id property of
// the Input Descriptor in the Presentation Definition the submission is related to.
if !stringsContain(descriptorIDs, mapping.ID) {
return nil, fmt.Errorf(
"an %s ID was found that did not match the `id` property of any input descriptor: %s",
descriptorMapProperty, mapping.ID)
}

// TODO: more logic needed? scan all VPs?
vc, selectErr := selectVC(typelessVP, mapping, opts)
if selectErr != nil {
return nil, selectErr
}

inputDescriptor := pd.inputDescriptor(mapping.ID)

passed := filterSchema(inputDescriptor.Schema, []*verifiable.Credential{vc}, contextLoader)
if len(passed) == 0 && !opts.DisableSchemaValidation {
// TODO: VC needs to pass if *any* input descriptor passes
return nil, fmt.Errorf(
"input descriptor id [%s] requires schemas %+v which do not match vc with @context [%+v] and types [%+v] selected by path [%s]", // nolint:lll
inputDescriptor.ID, inputDescriptor.Schema, vc.Context, vc.Types, mapping.Path)
}

// TODO add support for constraints: https://github.com/hyperledger/aries-framework-go/issues/2108

result[mapping.ID] = MatchValue{
PresentationID: vp.ID,
Credential: vc,
}
}
}

err = pd.evalSubmissionRequirements(result)
err := pd.evalSubmissionRequirements(result)
if err != nil {
return nil, fmt.Errorf("failed submission requirements: %w", err)
}
Expand Down Expand Up @@ -185,7 +199,7 @@ func selectVC(typelessVerifiable interface{},
}

// Ensures the matched credentials meet the submission requirements.
func (pd *PresentationDefinition) evalSubmissionRequirements(matched map[string]*verifiable.Credential) error {
func (pd *PresentationDefinition) evalSubmissionRequirements(matched map[string]MatchValue) error {
// TODO support submission requirement rules: https://github.com/hyperledger/aries-framework-go/issues/2109
descriptorIDs := descriptorIDs(pd.InputDescriptors)

Expand Down
Loading

0 comments on commit c9838a0

Please sign in to comment.