From 9e86e41d297beed038e05bfe4d4b57e7aa3b851a Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 29 Sep 2023 11:59:01 -0500 Subject: [PATCH] Protocol action "of" validation (#520) * move conditional property validation to javascript for protocol action rules * fix tests * replaced reuse of error code * npm audit fix get-func-name (#521) --------- Co-authored-by: Henry Tsai Co-authored-by: Diane Huxley --- README.md | 2 +- .../interface-methods/protocol-rule-set.json | 24 +------- src/core/dwn-error.ts | 2 + src/interfaces/protocols-configure.ts | 23 +++++++- tests/interfaces/protocols-configure.spec.ts | 59 +++++++++++++++++++ .../protocols/protocols-configure.spec.ts | 28 --------- 6 files changed, 83 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 70a577004..a7590b93d 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Here's to a thrilling Hacktoberfest voyage with us! 🎉 # Decentralized Web Node (DWN) SDK Code Coverage -![Statements](https://img.shields.io/badge/statements-97.77%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.03%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.26%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.77%25-brightgreen.svg?style=flat) +![Statements](https://img.shields.io/badge/statements-97.78%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.05%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.26%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.78%25-brightgreen.svg?style=flat) - [Introduction](#introduction) - [Installation](#installation) diff --git a/json-schemas/interface-methods/protocol-rule-set.json b/json-schemas/interface-methods/protocol-rule-set.json index 343439dc7..791abf704 100644 --- a/json-schemas/interface-methods/protocol-rule-set.json +++ b/json-schemas/interface-methods/protocol-rule-set.json @@ -32,29 +32,7 @@ "who": { "type": "string", "enum": [ - "anyone" - ] - }, - "can": { - "type": "string", - "enum": [ - "read", - "write" - ] - } - } - }, - { - "required": [ - "who", - "of", - "can" - ], - "additionalProperties": false, - "properties": { - "who": { - "type": "string", - "enum": [ + "anyone", "author", "recipient" ] diff --git a/src/core/dwn-error.ts b/src/core/dwn-error.ts index 73e25a66a..07e80043f 100644 --- a/src/core/dwn-error.ts +++ b/src/core/dwn-error.ts @@ -45,6 +45,8 @@ export enum DwnErrorCode { ProtocolAuthorizationNotARole = 'ProtocolAuthorizationNotARole', ProtocolsConfigureGlobalRoleAtProhibitedProtocolPath = 'ProtocolsConfigureGlobalRoleAtProhibitedProtocolPath', ProtocolsConfigureInvalidRole = 'ProtocolsConfigureInvalidRole', + ProtocolsConfigureInvalidActionMissingOf = 'ProtocolsConfigureInvalidActionMissingOf', + ProtocolsConfigureInvalidActionOfNotAllowed = 'ProtocolsConfigureInvalidActionOfNotAllowed', ProtocolsConfigureUnauthorized = 'ProtocolsConfigureUnauthorized', ProtocolsQueryUnauthorized = 'ProtocolsQueryUnauthorized', RecordsDecryptNoMatchingKeyEncryptedFound = 'RecordsDecryptNoMatchingKeyEncryptedFound', diff --git a/src/interfaces/protocols-configure.ts b/src/interfaces/protocols-configure.ts index 96d7657b0..2615ee7cc 100644 --- a/src/interfaces/protocols-configure.ts +++ b/src/interfaces/protocols-configure.ts @@ -8,8 +8,8 @@ import { DwnInterfaceName, DwnMethodName, Message } from '../core/message.js'; import { normalizeProtocolUrl, normalizeSchemaUrl, validateProtocolUrlNormalized, validateSchemaUrlNormalized } from '../utils/url.js'; export type ProtocolsConfigureOptions = { - messageTimestamp? : string; - definition : ProtocolDefinition; + messageTimestamp?: string; + definition: ProtocolDefinition; authorizationSigner: Signer; permissionsGrantId?: string; }; @@ -92,15 +92,32 @@ export class ProtocolsConfigure extends Message { ); } - // Validate that all `role` properties contain protocol paths $globalRole records + // Validate $actions in the ruleset const actions = ruleSet.$actions ?? []; for (const action of actions) { + // Validate that all `role` properties contain protocol paths $globalRole records if (action.role !== undefined && !globalRoles.includes(action.role)) { throw new DwnError( DwnErrorCode.ProtocolsConfigureInvalidRole, `Invalid role '${action.role}' found at protocol path '${protocolPath}'` ); } + + // Validate that if `who` is set to `anyone` then `of` is not set + if (action.who === 'anyone' && action.of) { + throw new DwnError( + DwnErrorCode.ProtocolsConfigureInvalidActionOfNotAllowed, + `'of' is not allowed at protocol path (${protocolPath})` + ); + } + + // Validate that if `who` is not set to `anyone` then `of` is set + if (action.who !== undefined && ['author', 'recipient'].includes(action.who) && !action.of) { + throw new DwnError( + DwnErrorCode.ProtocolsConfigureInvalidActionMissingOf, + `'of' is required at protocol path (${protocolPath})` + ); + } } // Validate nested rule sets diff --git a/tests/interfaces/protocols-configure.spec.ts b/tests/interfaces/protocols-configure.spec.ts index 17f2bc0b7..84b2df01c 100644 --- a/tests/interfaces/protocols-configure.spec.ts +++ b/tests/interfaces/protocols-configure.spec.ts @@ -165,6 +165,65 @@ describe('ProtocolsConfigure', () => { await expect(createProtocolsConfigurePromise) .to.be.rejectedWith(DwnErrorCode.ProtocolsConfigureInvalidRole); }); + + it('rejects protocol definitions with actions that contain `of` and `who` is `anyone`', async () => { + const definition = { + published : true, + protocol : 'http://example.com', + types : { + message: {}, + }, + structure: { + message: { + $actions: [{ + who : 'anyone', + of : 'message', // Not allowed + can : 'read' + }] + } + } + }; + + const alice = await TestDataGenerator.generatePersona(); + + const createProtocolsConfigurePromise = ProtocolsConfigure.create({ + authorizationSigner: Jws.createSigner(alice), + definition + }); + + await expect(createProtocolsConfigurePromise) + .to.be.rejectedWith(DwnErrorCode.ProtocolsConfigureInvalidActionOfNotAllowed); + }); + + it('rejects protocol definitions with actions that don\'t contain `of` and `who` is `author` or `recipient`', async () => { + const definition = { + published : true, + protocol : 'http://example.com', + types : { + message: {}, + }, + structure: { + message: { + $actions: [{ + who : 'author', + // of : 'message', // Intentionally missing + can : 'read' + }] + } + } + }; + + const alice = await TestDataGenerator.generatePersona(); + + const createProtocolsConfigurePromise = ProtocolsConfigure.create({ + authorizationSigner: Jws.createSigner(alice), + definition + }); + + await expect(createProtocolsConfigurePromise) + .to.be.rejectedWith(DwnErrorCode.ProtocolsConfigureInvalidActionMissingOf); + }); + }); }); }); diff --git a/tests/validation/json-schemas/protocols/protocols-configure.spec.ts b/tests/validation/json-schemas/protocols/protocols-configure.spec.ts index f822dd2c5..c13baf493 100644 --- a/tests/validation/json-schemas/protocols/protocols-configure.spec.ts +++ b/tests/validation/json-schemas/protocols/protocols-configure.spec.ts @@ -67,33 +67,5 @@ describe('ProtocolsConfigure schema definition', () => { }).throws('/$actions/0'); } }); - - it('#183 - should throw if required `of` is missing in rule-set', async () => { - const invalidRuleSet = { - $actions: [{ - who : 'author', - // of: 'thread', // intentionally missing - can : 'read' - }] - }; - - expect(() => { - validateJsonSchema('ProtocolRuleSet', invalidRuleSet); - }).throws('/$actions/0'); - }); - - it('#183 - should throw if `of` is present in `anyone` rule-set', async () => { - const invalidRuleSet = { - $actions: [{ - who : 'anyone', - of : 'thread', // intentionally present - can : 'read' - }] - }; - - expect(() => { - validateJsonSchema('ProtocolRuleSet', invalidRuleSet); - }).throws('/$actions/0'); - }); }); });