Skip to content

Commit

Permalink
Merge pull request #182 from TimoGlastra/feat/mdoc-parsing-with-vp
Browse files Browse the repository at this point in the history
fix: add back multi-vp handling
  • Loading branch information
sanderPostma authored Oct 31, 2024
2 parents 22c230c + 24e93ea commit 20669be
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 28 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ jobs:
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
with:
version: 9
- name: Use Node.js
uses: actions/setup-node@v4
with:
Expand Down
65 changes: 39 additions & 26 deletions lib/evaluation/evaluationClientWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,12 +571,12 @@ export class EvaluationClientWrapper {

// Iterate over each descriptor in the submission
for (const [descriptorIndex, descriptor] of submission.descriptor_map.entries()) {
let matchingVps: WrappedVerifiablePresentation[] = [];
let matchingVp: WrappedVerifiablePresentation;

if (presentationSubmissionLocation === PresentationSubmissionLocation.EXTERNAL) {
// Extract VPs matching the descriptor path
const vpResults = JsonPathUtils.extractInputField(wvps, [descriptor.path]) as Array<{
value: WrappedVerifiablePresentation[];
value: WrappedVerifiablePresentation;
}>;

if (!vpResults.length) {
Expand All @@ -587,45 +587,56 @@ export class EvaluationClientWrapper {
message: `Unable to extract path ${descriptor.path} for submission.descriptor_map[${descriptorIndex}] from presentation(s)`,
});
continue;
} else if (vpResults.length > 1) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
tag: 'SubmissionPathMultipleEntries',
message: `Extraction of path ${descriptor.path} for submission.descriptor_map[${descriptorIndex}] resulted in multiple values being returned.`,
});
continue;
}

// Flatten the array of VPs
const allVps = vpResults.flatMap((vpResult) => vpResult.value);

// Filter VPs that match the required format
matchingVps = allVps.filter((vp) => vp.format === descriptor.format);

if (!matchingVps.length) {
matchingVp = vpResults[0].value;
if (Array.isArray(matchingVp)) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
tag: 'SubmissionFormatNoMatch',
message: `No VP at path ${descriptor.path} matches the required format ${descriptor.format}`,
tag: 'SubmissionPathMultipleEntries',
message: `Extraction of path ${descriptor.path} for submission.descriptor_map[${descriptorIndex}] returned multiple entires. This is probably because the submission uses '$' to reference the presentation, while an array was used (thus all presentations are selected). Make sure the submission uses the correct path.`,
});
continue;
}
if (!matchingVp) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
tag: 'SubmissionPathNotFound',
message: `Extraction of path ${descriptor.path} for submission.descriptor_map[${descriptorIndex}] succeeded, but the value was undefined.`,
});
continue;
}

// Log a warning if multiple VPs match the descriptor
if (matchingVps.length > 1) {
result.warnings?.push({
status: Status.WARN,
tag: 'MultipleVpsMatched',
message: `Multiple VPs matched for descriptor_path[${descriptorIndex}]. Using the first matching VP.`,
if (matchingVp.format !== descriptor.format) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
tag: 'SubmissionFormatNoMatch',
message: `The VP at path ${descriptor.path} does not match the required format ${descriptor.format}`,
});
continue;
}
} else {
// When submission location is PRESENTATION, assume a single VP
matchingVps = Array.isArray(wvps) ? [wvps[0]] : [wvps];
matchingVp = Array.isArray(wvps) ? wvps[0] : wvps;
}

// Process the first matching VP
const vp = matchingVps[0];
let vc: WrappedVerifiableCredential;
let vcPath: string = `presentation ${descriptor.path}`;

if (presentationSubmissionLocation === PresentationSubmissionLocation.EXTERNAL) {
if (descriptor.path_nested) {
const extractionResult = this.extractWrappedVcFromWrappedVp(descriptor.path_nested, descriptorIndex.toString(), vp);
const extractionResult = this.extractWrappedVcFromWrappedVp(descriptor.path_nested, descriptorIndex.toString(), matchingVp);
if (extractionResult.error) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push(extractionResult.error);
Expand All @@ -635,7 +646,7 @@ export class EvaluationClientWrapper {
vc = extractionResult.wvc;
vcPath += ` with nested credential ${descriptor.path_nested.path}`;
} else if (descriptor.format === 'vc+sd-jwt' || descriptor.format === 'mso_mdoc') {
if (!vp.vcs || !vp.vcs.length) {
if (!matchingVp.vcs || !matchingVp.vcs.length) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
Expand All @@ -644,18 +655,18 @@ export class EvaluationClientWrapper {
});
continue;
}
vc = vp.vcs[0];
vc = matchingVp.vcs[0];
} else {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push({
status: Status.ERROR,
tag: 'UnsupportedFormat',
message: `VP format ${vp.format} is not supported`,
message: `VP format ${matchingVp.format} is not supported`,
});
continue;
}
} else {
const extractionResult = this.extractWrappedVcFromWrappedVp(descriptor, descriptorIndex.toString(), vp);
const extractionResult = this.extractWrappedVcFromWrappedVp(descriptor, descriptorIndex.toString(), matchingVp);
if (extractionResult.error) {
result.areRequiredCredentialsPresent = Status.ERROR;
result.errors?.push(extractionResult.error);
Expand All @@ -671,7 +682,9 @@ export class EvaluationClientWrapper {

// Determine holder DIDs
const holderDIDs =
CredentialMapper.isW3cPresentation(vp.presentation) && vp.presentation.holder ? [vp.presentation.holder] : opts?.holderDIDs || [];
CredentialMapper.isW3cPresentation(matchingVp.presentation) && matchingVp.presentation.holder
? [matchingVp.presentation.holder]
: opts?.holderDIDs || [];

if (pd.input_descriptors.findIndex((_id) => _id.id === descriptor.id) === -1) {
result.areRequiredCredentialsPresent = Status.ERROR;
Expand Down
53 changes: 53 additions & 0 deletions test/PEX.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1088,6 +1088,59 @@ describe('evaluate', () => {
expect(result.error).toEqual('This is not a valid PresentationDefinition');
});

it('evaluatePresentation with submission using $[0] while not passing an array will result in an error', function () {
const pdSchema: PresentationDefinitionV2 = {
id: '49768857',
input_descriptors: [
{
id: 'prc_type',
name: 'Name',
purpose: 'We can only support a familyName in a Permanent Resident Card',
constraints: {
fields: [
{
path: ['$.credentialSubject.familyName'],
filter: {
type: 'string',
const: 'Pasteur',
},
},
],
},
},
],
};
const pex: PEX = new PEX();
const jwtEncodedVp = getFile('./test/dif_pe_examples/vp/vp_permanentResidentCard.jwt');
const evalResult: PresentationEvaluationResults = pex.evaluatePresentation(pdSchema, [jwtEncodedVp], {
presentationSubmissionLocation: PresentationSubmissionLocation.EXTERNAL,
presentationSubmission: {
definition_id: pdSchema.id,
id: 'random',
descriptor_map: [
{
id: 'prc_type',
format: 'ldp_vp',
path: '$',
path_nested: {
id: 'prc_type',
format: 'ldp_vc',
path: '$.verifiableCredential[0]',
},
},
],
},
});
expect(evalResult.errors).toEqual([
{
message:
"Extraction of path $ for submission.descriptor_map[0] returned multiple entires. This is probably because the submission uses '$' to reference the presentation, while an array was used (thus all presentations are selected). Make sure the submission uses the correct path.",
status: 'error',
tag: 'SubmissionPathMultipleEntries',
},
]);
});

it('should pass with jwt vp with submission data', function () {
const pdSchema: PresentationDefinitionV2 = {
id: '49768857',
Expand Down

0 comments on commit 20669be

Please sign in to comment.