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

feat: suppor path_nested in presex. #3416

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions pkg/doc/presexch/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ func (pd *PresentationDefinition) Match(vp *verifiable.Presentation, // nolint:g
return nil, fmt.Errorf("failed to parse descriptor map: %w", err)
}

builder := gval.Full(jsonpath.PlaceholderExtension())
result := make(map[string]*verifiable.Credential)

for i := range descriptorMap {
Expand All @@ -121,12 +120,9 @@ func (pd *PresentationDefinition) Match(vp *verifiable.Presentation, // nolint:g
descriptorMapProperty, mapping.ID)
}

// TODO need to revisit this logic
mapping = pd.getPathNestedIfExists(mapping)

vc, selectErr := selectByPath(builder, typelessVP, mapping.Path, opts)
vc, selectErr := selectVC(typelessVP, mapping, opts)
if selectErr != nil {
return nil, fmt.Errorf("failed to select vc from submission: %w", selectErr)
return nil, selectErr
}

inputDescriptor := pd.inputDescriptor(mapping.ID)
Expand All @@ -151,12 +147,40 @@ func (pd *PresentationDefinition) Match(vp *verifiable.Presentation, // nolint:g
return result, nil
}

func (pd *PresentationDefinition) getPathNestedIfExists(mapping *InputDescriptorMapping) *InputDescriptorMapping {
if mapping.PathNested != nil {
return pd.getPathNestedIfExists(mapping.PathNested)
func selectVC(typelessVerifiable interface{},
mapping *InputDescriptorMapping, opts *MatchOptions) (*verifiable.Credential, error) {
builder := gval.Full(jsonpath.PlaceholderExtension())

var vc *verifiable.Credential

var err error

for {
vc, err = selectByPath(builder, typelessVerifiable, mapping.Path, opts)
if err != nil {
return nil, fmt.Errorf("failed to select vc from submission: %w", err)
}

if mapping.PathNested == nil {
break
}

mapping = mapping.PathNested

var vcBytes []byte

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

err = json.Unmarshal(vcBytes, &typelessVerifiable)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal vc: %w", err)
}
}

return mapping
return vc, nil
}

// Ensures the matched credentials meet the submission requirements.
Expand Down
147 changes: 146 additions & 1 deletion pkg/doc/presexch/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestPresentationDefinition_Match(t *testing.T) {
t.Run("match one credential", func(t *testing.T) {
uri := randomURI()

customType := "CustomType"
customType := "CustomType" //nolint: goconst

expected := newVC([]string{uri})
expected.Types = append(expected.Types, customType)
Expand Down Expand Up @@ -65,6 +65,94 @@ func TestPresentationDefinition_Match(t *testing.T) {
require.Equal(t, expected.ID, result.ID)
})

t.Run("match one nested credential", func(t *testing.T) {
uri := randomURI()

customType := "CustomType"

expectedNested := newVC([]string{uri})
expectedNested.Types = append(expectedNested.Types, customType)
expectedNested.ID = "http://test.credential.com/123456"

expected := newVCWithCustomFld([]string{uri}, "nestedVC", expectedNested)
expected.Types = append(expected.Types, customType)

defs := &PresentationDefinition{
InputDescriptors: []*InputDescriptor{{
ID: uuid.New().String(),
Schema: []*Schema{{
URI: fmt.Sprintf("%s#%s", uri, customType),
}},
}},
}

docLoader := createTestDocumentLoader(t, uri, customType)

matched, err := defs.Match(newVP(t,
&PresentationSubmission{DescriptorMap: []*InputDescriptorMapping{{
ID: defs.InputDescriptors[0].ID,
Path: "$.verifiableCredential[0]",
PathNested: &InputDescriptorMapping{
ID: defs.InputDescriptors[0].ID,
Path: "$.nestedVC",
},
}}},
expected,
), docLoader, WithCredentialOptions(verifiable.WithJSONLDDocumentLoader(docLoader)))
require.NoError(t, err)
require.Len(t, matched, 1)
result, ok := matched[defs.InputDescriptors[0].ID]
require.True(t, ok)
require.Equal(t, expectedNested.ID, result.ID)
})

t.Run("match one nested jwt credential", func(t *testing.T) {
uri := randomURI()
contextLoader := createTestDocumentLoader(t, uri)
agent := newAgent(t)

customType := "CustomType"

expectedNested := newSignedJWTVC(t, agent, []string{uri})

expected := newVCWithCustomFld([]string{uri}, "nestedVC", expectedNested)
expected.Types = append(expected.Types, customType)
expected.ID = "http://test.credential.com/123456"

defs := &PresentationDefinition{
InputDescriptors: []*InputDescriptor{{
ID: uuid.New().String(),
Schema: []*Schema{{
URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType),
}},
}},
}

docLoader := createTestDocumentLoader(t, uri, customType)

matched, err := defs.Match(newVP(t,
&PresentationSubmission{DescriptorMap: []*InputDescriptorMapping{{
ID: defs.InputDescriptors[0].ID,
Path: "$.verifiableCredential[0]",
PathNested: &InputDescriptorMapping{
ID: defs.InputDescriptors[0].ID,
Path: "$.nestedVC",
},
}}},
expected,
), docLoader,
WithCredentialOptions(
verifiable.WithJSONLDDocumentLoader(contextLoader),
verifiable.WithPublicKeyFetcher(verifiable.NewVDRKeyResolver(agent.VDRegistry()).PublicKeyFetcher()),
),
)
require.NoError(t, err)
require.Len(t, matched, 1)
result, ok := matched[defs.InputDescriptors[0].ID]
require.True(t, ok)
require.Equal(t, expectedNested.ID, result.ID)
})

t.Run("match one signed credential", func(t *testing.T) {
uri := randomURI()
contextLoader := createTestDocumentLoader(t, uri)
Expand Down Expand Up @@ -394,6 +482,30 @@ func newVC(ctx []string) *verifiable.Credential {
return vc
}

func newVCWithCustomFld(ctx []string, fldName string, fld interface{}) *verifiable.Credential {
vc := &verifiable.Credential{
Context: []string{verifiable.ContextURI},
Types: []string{verifiable.VCType},
ID: "http://test.credential.com/123",
Issuer: verifiable.Issuer{ID: "http://test.issuer.com"},
Issued: &util.TimeWrapper{
Time: time.Now(),
},
Subject: map[string]interface{}{
"id": uuid.New().String(),
},
CustomFields: map[string]interface{}{
fldName: fld,
},
}

if ctx != nil {
vc.Context = append(vc.Context, ctx...)
}

return vc
}

func newSignedVC(t *testing.T,
agent *context.Provider, ctx []string, ctxLoader jsonld.DocumentLoader) *verifiable.Credential {
t.Helper()
Expand Down Expand Up @@ -428,6 +540,39 @@ func newSignedVC(t *testing.T,
return vc
}

func newSignedJWTVC(t *testing.T,
agent *context.Provider, ctx []string) *verifiable.Credential {
t.Helper()

vc := newVC(ctx)

keyID, kh, err := agent.KMS().Create(kms.ED25519Type)
require.NoError(t, err)

signer := suite.NewCryptoSigner(agent.Crypto(), kh)

pubKey, kt, err := agent.KMS().ExportPubKeyBytes(keyID)
require.NoError(t, err)
require.Equal(t, kms.ED25519Type, kt)

issuer, verMethod := fingerprint.CreateDIDKeyByCode(fingerprint.ED25519PubKeyMultiCodec, pubKey)

vc.Issuer = verifiable.Issuer{ID: issuer}

claims, err := vc.JWTClaims(false)
require.NoError(t, err)

jwsAlgo, err := verifiable.KeyTypeToJWSAlgo(kms.ED25519Type)
require.NoError(t, err)

jws, err := claims.MarshalJWS(jwsAlgo, signer, verMethod)
require.NoError(t, err)

vc.JWT = jws

return vc
}

func newVP(t *testing.T, submission *PresentationSubmission, vcs ...*verifiable.Credential) *verifiable.Presentation {
vp, err := verifiable.NewPresentation(verifiable.WithCredentials(vcs...))
require.NoError(t, err)
Expand Down