Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add back multi-vp handling #182

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
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 @@

// 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 @@
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;

Check warning on line 591 in lib/evaluation/evaluationClientWrapper.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/evaluationClientWrapper.ts#L591

Added line #L591 was not covered by tests
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;

Check warning on line 597 in lib/evaluation/evaluationClientWrapper.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/evaluationClientWrapper.ts#L597

Added line #L597 was not covered by tests
}

// 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;

Check warning on line 611 in lib/evaluation/evaluationClientWrapper.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/evaluationClientWrapper.ts#L611

Added line #L611 was not covered by tests
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;

Check warning on line 621 in lib/evaluation/evaluationClientWrapper.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/evaluationClientWrapper.ts#L621

Added line #L621 was not covered by tests
result.errors?.push({
status: Status.ERROR,
tag: 'SubmissionFormatNoMatch',
message: `The VP at path ${descriptor.path} does not match the required format ${descriptor.format}`,
});
continue;

Check warning on line 627 in lib/evaluation/evaluationClientWrapper.ts

View check run for this annotation

Codecov / codecov/patch

lib/evaluation/evaluationClientWrapper.ts#L627

Added line #L627 was not covered by tests
}
} 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 @@
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 @@
});
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 @@

// 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
Loading