From 25877427b04d91802a6a389d06ed4e3ed70b37ab Mon Sep 17 00:00:00 2001 From: William Brooks Date: Mon, 20 Dec 2021 12:39:07 +0200 Subject: [PATCH 1/4] IDCOM-1592 Updated to check for properties in references schemas --- __test__/creds/VerifiableCredential.test.js | 18 ++++++++ .../credential-test:IdDocument-v1.schema.json | 12 +++++ src/schemas/jsonSchema/index.js | 44 +++++++++++++++++-- 3 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 __test__/schema/fixtures/credential-test:IdDocument-v1.schema.json diff --git a/__test__/creds/VerifiableCredential.test.js b/__test__/creds/VerifiableCredential.test.js index 98035cdf..3256a22c 100644 --- a/__test__/creds/VerifiableCredential.test.js +++ b/__test__/creds/VerifiableCredential.test.js @@ -45,6 +45,24 @@ const signAttestationSubject = (subject, xprv, xpub) => { }; }; +class TestSchemaLoader extends CVCSchemaLoader { + // eslint-disable-next-line class-methods-use-this + valid(identifier) { + return /^(claim|credential|type)-(test):.*$/.test(identifier); + } + + async loadSchema(identifier) { + try { + // eslint-disable-next-line global-require,import/no-dynamic-require + return require(`../schema/fixtures/${identifier}.schema.json`); + // eslint-disable-next-line no-empty + } catch (e) { + } + + return super.loadSchema(identifier); + } +} + describe('Unit tests for Verifiable Credentials', () => { beforeAll(() => { schemaLoader.addLoader(new CVCSchemaLoader()); diff --git a/__test__/schema/fixtures/credential-test:IdDocument-v1.schema.json b/__test__/schema/fixtures/credential-test:IdDocument-v1.schema.json new file mode 100644 index 00000000..26e5e133 --- /dev/null +++ b/__test__/schema/fixtures/credential-test:IdDocument-v1.schema.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://identity.com/schemas/credential-test:IdDocument-v1", + "title": "credential-test:IdDocument-v1", + "type": "object", + "allOf": [ + { + "$ref": "http://identity.com/schemas/credential-cvc:IdDocument-v2" + } + ], + "additionalProperties": false +} diff --git a/src/schemas/jsonSchema/index.js b/src/schemas/jsonSchema/index.js index 9f6fe70d..dae1e069 100644 --- a/src/schemas/jsonSchema/index.js +++ b/src/schemas/jsonSchema/index.js @@ -200,7 +200,9 @@ class SchemaLoader { definition.depends.push(propertySchema.title); } - if (schema.properties.claim.required && schema.properties.claim.required.includes(property)) { + const schemaProperties = await this.flattenCredentialSchemaProperties(schema); + + if (schemaProperties.claim.required && schemaProperties.claim.required.includes(property)) { definition.required.push(propertySchema.title); } } @@ -216,6 +218,40 @@ class SchemaLoader { } } + /** + * Flattens the properties of a schema if there are any referenced schemas + * @param schema + * @returns {Promise<*>} + */ + async flattenCredentialSchemaProperties(schema) { + let properties = schema.properties ? schema.properties : {}; + + if (schema.allOf) { + const promises = schema.allOf.map(async (allOf) => { + if (allOf.$ref) { + const refSchema = await this.loadSchemaFromUri(allOf.$ref); + const refProperties = await this.flattenCredentialSchemaProperties(refSchema); + + properties = { + ...properties, + ...refProperties, + }; + } + + if (allOf.properties) { + properties = { + ...properties, + ...allOf.properties, + }; + } + }); + + await Promise.all(promises); + } + + return properties; + } + /** * Adds a credential definition to be backwards compatible with the old schema structure. */ @@ -230,12 +266,14 @@ class SchemaLoader { definition.transient = true; } - if (schema.properties.claim.required) { + const properties = await this.flattenCredentialSchemaProperties(schema); + + if (properties.claim.required) { definition.required = []; } const references = []; - _.forEach(schema.properties.claim.properties, (vo) => { + _.forEach(properties.claim.properties, (vo) => { _.forEach(vo.properties, (vi, ki) => { references.push({ ref: vo.properties[ki].$ref, property: ki }); }); From e45125469727016c9bf386856d4992d33693b998 Mon Sep 17 00:00:00 2001 From: William Brooks Date: Mon, 20 Dec 2021 12:40:55 +0200 Subject: [PATCH 2/4] IDCOM-1592 Version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d1c080e7..82c0e43f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@identity.com/credential-commons", - "version": "2.0.1", + "version": "2.0.2", "author": "Identity.com Community", "license": "MIT", "description": "Verifiable Credential and Attestation Library", From e756da666ff1c2a3761adf4504bd9883c88c5dd5 Mon Sep 17 00:00:00 2001 From: William Brooks Date: Mon, 20 Dec 2021 12:44:56 +0200 Subject: [PATCH 3/4] IDCOM-1592 Added unit test for references schemas --- __test__/creds/VerifiableCredential.test.js | 54 +++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/__test__/creds/VerifiableCredential.test.js b/__test__/creds/VerifiableCredential.test.js index 3256a22c..45c1c7c2 100644 --- a/__test__/creds/VerifiableCredential.test.js +++ b/__test__/creds/VerifiableCredential.test.js @@ -1900,3 +1900,57 @@ describe('Signned Verifiable Credentials', () => { expect(signerVerifier.isSignatureValid(dataOnlyCredential)).toBeTruthy(); }); }); +describe('Referenced Schemas for Verifiable Credentials', () => { + beforeAll(() => { + schemaLoader.addLoader(new TestSchemaLoader()); + schemaLoader.addLoader(new CVCSchemaLoader()); + }); + + test('Loads a schema the contains a reference', async () => { + const type = await Claim.create('claim-cvc:Document.type-v1', 'passport', '1'); + const number = await Claim.create('claim-cvc:Document.number-v1', 'FP12345', '1'); + const nameValue = { + givenNames: 'e8qhs4Iak1', + familyNames: 'e8qak1', + otherNames: 'qhs4I', + }; + const name = await Claim.create('claim-cvc:Document.name-v1', nameValue, '1'); + const gender = await Claim.create('claim-cvc:Document.gender-v1', 'M', '1'); + const issueCountry = await Claim.create('claim-cvc:Document.issueCountry-v1', 'Brazil', '1'); + const placeOfBirth = await Claim.create('claim-cvc:Document.placeOfBirth-v1', 'Belo Horizonte', '1'); + const dateOfBirthValue = identityDateOfBirth; + const dateOfBirth = await Claim.create('claim-cvc:Document.dateOfBirth-v1', dateOfBirthValue, '1'); + const dateOfExpiryValue = { + day: 12, + month: 2, + year: 2025, + }; + const dateOfExpiry = await Claim.create('claim-cvc:Document.dateOfExpiry-v1', dateOfExpiryValue, '1'); + const nationality = await Claim.create('claim-cvc:Document.nationality-v1', 'Brazilian', '1'); + + const evidencesValue = { + idDocumentFront: { + algorithm: 'sha256', + data: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + idDocumentBack: { + algorithm: 'sha256', + data: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + selfie: { + algorithm: 'sha256', + data: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', + }, + }; + const evidences = await Claim.create('claim-cvc:Document.evidences-v1', evidencesValue, '1'); + + const credential = await VC.create( + 'credential-test:IdDocument-v1', '', null, [type, number, name, gender, + issueCountry, placeOfBirth, dateOfBirth, dateOfExpiry, nationality, evidences], '1', + ); + + expect(credential).toBeDefined(); + const filtered = credential.filter(['claim-cvc:Document.dateOfBirth-v1']); + expect(filtered).toBeDefined(); + }); +}); From a323f0ea4dc48ba637026b5c2204c97e7377a361 Mon Sep 17 00:00:00 2001 From: William Brooks Date: Mon, 20 Dec 2021 12:53:43 +0200 Subject: [PATCH 4/4] IDCOM-1592 Added unit test to ensure validation on referenced schemas --- __test__/creds/VerifiableCredential.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/__test__/creds/VerifiableCredential.test.js b/__test__/creds/VerifiableCredential.test.js index 45c1c7c2..b2374d4e 100644 --- a/__test__/creds/VerifiableCredential.test.js +++ b/__test__/creds/VerifiableCredential.test.js @@ -1953,4 +1953,16 @@ describe('Referenced Schemas for Verifiable Credentials', () => { const filtered = credential.filter(['claim-cvc:Document.dateOfBirth-v1']); expect(filtered).toBeDefined(); }); + + test('Validates a schema the contains a reference', async () => { + const type = await Claim.create('claim-cvc:Document.type-v1', 'passport', '1'); + const number = await Claim.create('claim-cvc:Document.number-v1', 'FP12345', '1'); + + const createCredential = VC.create( + 'credential-test:IdDocument-v1', '', null, [type, number], '1', + ); + + expect(createCredential).rejects.toThrow('Missing required claim(s): claim-cvc:Document.name-v1, ' + + 'claim-cvc:Document.issueCountry-v1, claim-cvc:Document.dateOfBirth-v1, claim-cvc:Document.evidences-v1'); + }); });