Skip to content

Commit

Permalink
fix(core)!: improve proof request validation (#525)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Proof request requestedAttributes and requestedPredicates are now a map instead of record. This is needed to have proper validation using class-validator.

Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra authored Nov 8, 2021
1 parent 9c3910f commit 1b4d8d6
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 16 deletions.
76 changes: 76 additions & 0 deletions packages/core/src/modules/proofs/__tests__/ProofRequest.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { JsonTransformer } from '../../../utils/JsonTransformer'
import { MessageValidator } from '../../../utils/MessageValidator'
import { ProofRequest } from '../models'

describe('ProofRequest', () => {
it('should successfully validate if the proof request json contains a valid structure', async () => {
const proofRequest = JsonTransformer.fromJSON(
{
name: 'ProofRequest',
version: '1.0',
nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f',
requested_attributes: {
First: {
name: 'Timo',
restrictions: [
{
schema_id: 'string',
},
],
},
},
requested_predicates: {
Second: {
name: 'Timo',
p_type: '<=',
p_value: 10,
restrictions: [
{
schema_id: 'string',
},
],
},
},
},
ProofRequest
)

expect(MessageValidator.validate(proofRequest)).resolves.not.toThrow()
})

it('should throw an error if the proof request json contains an invalid structure', async () => {
const proofRequest = JsonTransformer.fromJSON(
{
name: 'ProofRequest',
version: '1.0',
nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f',
requested_attributes: {
First: {
names: [],
restrictions: [
{
schema_id: 'string',
},
],
},
},
requested_predicates: [
{
name: 'Timo',
p_type: '<=',
p_value: 10,
restrictions: [
{
schema_id: 'string',
},
],
},
],
},
ProofRequest
)

// Expect 2 top level validation errors
expect(MessageValidator.validate(proofRequest)).rejects.toHaveLength(2)
})
})
7 changes: 4 additions & 3 deletions packages/core/src/modules/proofs/models/ProofAttributeInfo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Expose, Type } from 'class-transformer'
import { IsString, IsOptional, IsArray, ValidateNested, IsInstance } from 'class-validator'
import { IsString, IsOptional, IsArray, ValidateNested, IsInstance, ValidateIf, ArrayNotEmpty } from 'class-validator'

import { RevocationInterval } from '../../credentials'

Expand All @@ -16,12 +16,13 @@ export class ProofAttributeInfo {
}

@IsString()
@IsOptional()
@ValidateIf((o: ProofAttributeInfo) => o.names === undefined)
public name?: string

@IsArray()
@IsString({ each: true })
@IsOptional()
@ValidateIf((o: ProofAttributeInfo) => o.name === undefined)
@ArrayNotEmpty()
public names?: string[]

@Expose({ name: 'non_revoked' })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Expose, Type } from 'class-transformer'
import { IsEnum, IsInstance, IsInt, IsOptional, IsString, ValidateNested } from 'class-validator'
import { IsArray, IsEnum, IsInstance, IsInt, IsOptional, IsString, ValidateNested } from 'class-validator'

import { RevocationInterval } from '../../credentials'

Expand Down Expand Up @@ -39,5 +39,6 @@ export class ProofPredicateInfo {
@Type(() => AttributeFilter)
@IsOptional()
@IsInstance(AttributeFilter, { each: true })
@IsArray()
public restrictions?: AttributeFilter[]
}
23 changes: 16 additions & 7 deletions packages/core/src/modules/proofs/models/ProofRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Expose, Type } from 'class-transformer'
import { IsString, ValidateNested, IsOptional, IsIn, IsInstance } from 'class-validator'

import { JsonTransformer } from '../../../utils/JsonTransformer'
import { RecordTransformer } from '../../../utils/transformers'
import { IsMap } from '../../../utils/transformers'
import { RevocationInterval } from '../../credentials'

import { ProofAttributeInfo } from './ProofAttributeInfo'
Expand All @@ -31,8 +31,12 @@ export class ProofRequest {
this.name = options.name
this.version = options.version
this.nonce = options.nonce
this.requestedAttributes = options.requestedAttributes ?? {}
this.requestedPredicates = options.requestedPredicates ?? {}
this.requestedAttributes = options.requestedAttributes
? new Map(Object.entries(options.requestedAttributes))
: new Map()
this.requestedPredicates = options.requestedPredicates
? new Map(Object.entries(options.requestedPredicates))
: new Map()
this.nonRevoked = options.nonRevoked
this.ver = options.ver
}
Expand All @@ -48,14 +52,19 @@ export class ProofRequest {
public nonce!: string

@Expose({ name: 'requested_attributes' })
@IsMap()
@ValidateNested({ each: true })
@RecordTransformer(ProofAttributeInfo)
public requestedAttributes!: Record<string, ProofAttributeInfo>
@Type(() => ProofAttributeInfo)
@IsInstance(ProofAttributeInfo, { each: true })
public requestedAttributes!: Map<string, ProofAttributeInfo>

@Expose({ name: 'requested_predicates' })
@IsMap()
@ValidateNested({ each: true })
@RecordTransformer(ProofPredicateInfo)
public requestedPredicates!: Record<string, ProofPredicateInfo>
@Type(() => ProofPredicateInfo)
@IsInstance(ProofPredicateInfo, { each: true })
public requestedPredicates!: Map<string, ProofPredicateInfo>

@Expose({ name: 'non_revoked' })
@ValidateNested()
@Type(() => RevocationInterval)
Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/modules/proofs/services/ProofService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ export class ProofService {
],
})

proofRequest.requestedAttributes[referent] = requestedAttribute
proofRequest.requestedAttributes.set(referent, requestedAttribute)
}

this.logger.debug('proposal predicates', presentationProposal.predicates)
Expand All @@ -642,7 +642,7 @@ export class ProofService {
],
})

proofRequest.requestedPredicates[uuid()] = requestedPredicate
proofRequest.requestedPredicates.set(uuid(), requestedPredicate)
}

return proofRequest
Expand All @@ -665,7 +665,7 @@ export class ProofService {
// Get the credentialIds if it contains a hashlink
for (const [referent, requestedAttribute] of Object.entries(requestedCredentials.requestedAttributes)) {
// Find the requested Attributes
const requestedAttributes = indyProofRequest.requestedAttributes[referent]
const requestedAttributes = indyProofRequest.requestedAttributes.get(referent) as ProofAttributeInfo

// List the requested attributes
requestedAttributesNames.push(...(requestedAttributes.names ?? [requestedAttributes.name]))
Expand Down Expand Up @@ -721,7 +721,7 @@ export class ProofService {
): Promise<RetrievedCredentials> {
const retrievedCredentials = new RetrievedCredentials({})

for (const [referent, requestedAttribute] of Object.entries(proofRequest.requestedAttributes)) {
for (const [referent, requestedAttribute] of proofRequest.requestedAttributes.entries()) {
let credentialMatch: Credential[] = []
const credentials = await this.getCredentialsForProofRequest(proofRequest, referent)

Expand Down Expand Up @@ -759,7 +759,7 @@ export class ProofService {
})
}

for (const [referent] of Object.entries(proofRequest.requestedPredicates)) {
for (const [referent] of proofRequest.requestedPredicates.entries()) {
const credentials = await this.getCredentialsForProofRequest(proofRequest, referent)

retrievedCredentials.requestedPredicates[referent] = credentials.map((credential) => {
Expand Down
19 changes: 19 additions & 0 deletions packages/core/src/utils/transformers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { ValidationOptions } from 'class-validator'

import { Transform, TransformationType } from 'class-transformer'
import { ValidateBy, buildMessage } from 'class-validator'
import { DateTime } from 'luxon'

import { JsonTransformer } from './JsonTransformer'
Expand Down Expand Up @@ -55,3 +58,19 @@ export function DateParser(value: string): Date {
}
return new Date()
}

/**
* Checks if a given value is a Map
*/
export function IsMap(validationOptions?: ValidationOptions): PropertyDecorator {
return ValidateBy(
{
name: 'isMap',
validator: {
validate: (value: unknown): boolean => value instanceof Map,
defaultMessage: buildMessage((eachPrefix) => eachPrefix + '$property must be a Map', validationOptions),
},
},
validationOptions
)
}

0 comments on commit 1b4d8d6

Please sign in to comment.