From 3b0794aecc75facdb5b119693d90c265922e3449 Mon Sep 17 00:00:00 2001 From: annelein Date: Mon, 28 Feb 2022 11:54:46 +0100 Subject: [PATCH] fix: issue where attributes and predicates match Signed-off-by: annelein --- demo/src/Faber.ts | 1 - .../modules/proofs/services/ProofService.ts | 13 +- packages/core/src/utils/assertNoDuplicates.ts | 11 ++ packages/core/src/utils/index.ts | 1 + packages/core/src/utils/indyProofRequest.ts | 24 +++ packages/core/tests/helpers.ts | 26 +++ packages/core/tests/proofs.test.ts | 168 +++++++++++++++++- 7 files changed, 233 insertions(+), 11 deletions(-) create mode 100644 packages/core/src/utils/assertNoDuplicates.ts create mode 100644 packages/core/src/utils/indyProofRequest.ts diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index 43f0699d4b..68c2822d84 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -43,7 +43,6 @@ export class Faber extends BaseAgent { public async acceptConnection(invitation_url: string) { const connectionRecord = await this.receiveConnectionRequest(invitation_url) - this.connectionRecordAliceId = await this.waitForConnection(connectionRecord) } diff --git a/packages/core/src/modules/proofs/services/ProofService.ts b/packages/core/src/modules/proofs/services/ProofService.ts index e06b5b4e71..5402777c7a 100644 --- a/packages/core/src/modules/proofs/services/ProofService.ts +++ b/packages/core/src/modules/proofs/services/ProofService.ts @@ -16,6 +16,7 @@ import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../error' +import { checkProofRequestForDuplicates } from '../../../utils' import { JsonEncoder } from '../../../utils/JsonEncoder' import { JsonTransformer } from '../../../utils/JsonTransformer' import { uuid } from '../../../utils/uuid' @@ -257,8 +258,8 @@ export class ProofService { comment?: string } ): Promise> { - // Assert attribute and predicate group names do not match - this.assertAttributePredicateGroupNamesDoNotMatch(proofRequest) + // Assert attribute and predicate (group) names do not match + checkProofRequestForDuplicates(proofRequest) // Assert proofRecord.assertState(ProofState.ProposalReceived) @@ -305,8 +306,8 @@ export class ProofService { ): Promise> { this.logger.debug(`Creating proof request`) - // Assert attribute and predicate group names do not match - this.assertAttributePredicateGroupNamesDoNotMatch(proofRequest) + // Assert attribute and predicate (group) names do not match + checkProofRequestForDuplicates(proofRequest) // Assert connectionRecord?.assertReady() @@ -369,8 +370,8 @@ export class ProofService { } await validateOrReject(proofRequest) - // Assert attribute and predicate group names do not match - this.assertAttributePredicateGroupNamesDoNotMatch(proofRequest) + // Assert attribute and predicate (group) names do not match + checkProofRequestForDuplicates(proofRequest) this.logger.debug('received proof request', proofRequest) diff --git a/packages/core/src/utils/assertNoDuplicates.ts b/packages/core/src/utils/assertNoDuplicates.ts new file mode 100644 index 0000000000..841ba261f4 --- /dev/null +++ b/packages/core/src/utils/assertNoDuplicates.ts @@ -0,0 +1,11 @@ +import { AriesFrameworkError } from '../error/AriesFrameworkError' + +export function assertNoDuplicatesInArray(arr: string[]) { + const arrayLength = arr.length + const uniqueArrayLength = new Set(arr).size + + if (arrayLength === uniqueArrayLength) return + + const duplicates = arr.filter((item, index) => arr.indexOf(item) != index) + throw new AriesFrameworkError(`The proof request contains duplicate items: ${duplicates.toString()}`) +} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 1a92d10e30..53c0f65f45 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -5,3 +5,4 @@ export * from './MultiBaseEncoder' export * from './buffer' export * from './MultiHashEncoder' export * from './JWE' +export * from './indyProofRequest' diff --git a/packages/core/src/utils/indyProofRequest.ts b/packages/core/src/utils/indyProofRequest.ts new file mode 100644 index 0000000000..1ee52cc529 --- /dev/null +++ b/packages/core/src/utils/indyProofRequest.ts @@ -0,0 +1,24 @@ +import type { ProofRequest } from '../modules/proofs/models/ProofRequest' + +import { assertNoDuplicatesInArray } from './assertNoDuplicates' + +export function attributesToArray(proofRequest: ProofRequest) { + // Attributes can contain either a `name` string value or an `names` string array. We reduce it to a single array + // containing all attribute names from the requested attributes. + return Array.from(proofRequest.requestedAttributes.values()).reduce( + (names, a) => [...names, ...(a.name ? [a.name] : a.names ? a.names : [])], + [] + ) +} + +export function predicatesToArray(proofRequest: ProofRequest) { + return Array.from(proofRequest.requestedPredicates.values()).map((a) => a.name) +} + +export function checkProofRequestForDuplicates(proofRequest: ProofRequest) { + const attributes = attributesToArray(proofRequest) + const predicates = predicatesToArray(proofRequest) + assertNoDuplicatesInArray(attributes) + assertNoDuplicatesInArray(predicates) + assertNoDuplicatesInArray(attributes.concat(predicates)) +} diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 8fc69cfecb..bc660248e3 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -664,3 +664,29 @@ export async function setupProofsTest(faberName: string, aliceName: string, auto aliceReplay, } } + +export async function setupSecondCredential( + faberAgent: Agent, + aliceAgent: Agent, + faberConnection: ConnectionRecord +): Promise { + const { definition } = await prepareForIssuance(faberAgent, ['name', 'age']) + + const credentialPreview = CredentialPreview.fromRecord({ + name: 'John', + age: '99', + }) + + await issueCredential({ + issuerAgent: faberAgent, + issuerConnectionId: faberConnection.id, + holderAgent: aliceAgent, + credentialTemplate: { + credentialDefinitionId: definition.id, + comment: 'some comment about credential', + preview: credentialPreview, + }, + }) + + return definition.id +} diff --git a/packages/core/tests/proofs.test.ts b/packages/core/tests/proofs.test.ts index 6205066459..227d2b6305 100644 --- a/packages/core/tests/proofs.test.ts +++ b/packages/core/tests/proofs.test.ts @@ -12,15 +12,19 @@ import { ProofState, ProposePresentationMessage, RequestPresentationMessage, + ProofRequest, + AriesFrameworkError, } from '../src' +import { checkProofRequestForDuplicates } from '../src/utils' -import { setupProofsTest, waitForProofRecord } from './helpers' +import { setupProofsTest, setupSecondCredential, waitForProofRecord } from './helpers' import testLogger from './logger' describe('Present Proof', () => { let faberAgent: Agent let aliceAgent: Agent let credDefId: CredDefId + let secCredDefId: string let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord let presentationPreview: PresentationPreview @@ -29,6 +33,8 @@ describe('Present Proof', () => { testLogger.test('Initializing the agents') ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = await setupProofsTest('Faber agent', 'Alice agent')) + testLogger.test('Issuing second credential') + secCredDefId = await setupSecondCredential(faberAgent, aliceAgent, faberConnection) }) afterAll(async () => { @@ -365,8 +371,162 @@ describe('Present Proof', () => { requestedAttributes: attributes, requestedPredicates: predicates, }) - ).rejects.toThrowError( - `The proof request contains an attribute group name that matches a predicate group name: age` - ) + ).rejects.toThrowError(`The proof request contains duplicate items: age`) + }) + + test('attribute names match, same cred def filter', async () => { + const attributes = { + name: new ProofAttributeInfo({ + name: 'age', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + age: new ProofAttributeInfo({ + name: 'age', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const proofRequestOptions = { + name: 'test-proof-request', + requestedAttributes: attributes, + } + + const nonce = 'testtesttest12345' + + const proofRequest = new ProofRequest({ + name: 'proof-request', + version: '1.0', + nonce, + requestedAttributes: proofRequestOptions.requestedAttributes, + }) + + expect(() => checkProofRequestForDuplicates(proofRequest)).toThrowError(AriesFrameworkError) + }) + + test('attribute names match with predicates name, same cred def filter', async () => { + const attributes = { + name: new ProofAttributeInfo({ + name: 'age', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const nonce = 'testtesttest12345' + + const proofRequest = new ProofRequest({ + name: 'proof-request', + version: '1.0', + nonce, + requestedAttributes: attributes, + requestedPredicates: predicates, + }) + + expect(() => checkProofRequestForDuplicates(proofRequest)).toThrowError(AriesFrameworkError) + }) + + test('attribute names match, different cred def filter', async () => { + const attributes = { + name: new ProofAttributeInfo({ + name: 'age', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: secCredDefId, + }), + ], + }), + } + + const proofRequestOptions = { + name: 'test-proof-request', + requestedAttributes: attributes, + predicates: predicates, + } + + const nonce = 'testtesttest12345' + + const proofRequest = new ProofRequest({ + name: 'proof-request', + version: '1.0', + nonce, + requestedAttributes: proofRequestOptions.requestedAttributes, + }) + + expect(() => checkProofRequestForDuplicates(proofRequest)).resolves + }) + + test('attribute name matches with predicate name, different cred def filter', async () => { + const attributes = { + name: new ProofAttributeInfo({ + name: 'age', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const predicates = { + age: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: secCredDefId, + }), + ], + }), + } + + const nonce = 'testtesttest12345' + + const proofRequest = new ProofRequest({ + name: 'proof-request', + version: '1.0', + nonce, + requestedAttributes: attributes, + requestedPredicates: predicates, + }) + + expect(() => checkProofRequestForDuplicates(proofRequest)).resolves }) })