From 04345b0405d968def6a317d293132c7899951e37 Mon Sep 17 00:00:00 2001 From: Diane Huxley Date: Tue, 28 Mar 2023 11:59:33 -0700 Subject: [PATCH] Stricten typescript in protocol-authorization.ts (#277) --- README.md | 2 +- package.json | 2 +- src/core/protocol-authorization.ts | 28 +++++----- .../records/handlers/records-write.spec.ts | 52 +++++++++++++++++++ 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index f610ed1ad..3b97ed95d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # Decentralized Web Node (DWN) SDK Code Coverage -![Statements](https://img.shields.io/badge/statements-93.64%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-92.8%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-90.4%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-93.64%25-brightgreen.svg?style=flat) +![Statements](https://img.shields.io/badge/statements-95.1%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-93.05%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.09%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-95.1%25-brightgreen.svg?style=flat) ## Introduction diff --git a/package.json b/package.json index 0b8ba67f3..05e2708bd 100644 --- a/package.json +++ b/package.json @@ -134,4 +134,4 @@ "url": "https://github.com/TBD54566975/dwn-sdk-js/issues" }, "homepage": "https://github.com/TBD54566975/dwn-sdk-js#readme" -} \ No newline at end of file +} diff --git a/src/core/protocol-authorization.ts b/src/core/protocol-authorization.ts index a224baca1..d078ea315 100644 --- a/src/core/protocol-authorization.ts +++ b/src/core/protocol-authorization.ts @@ -4,8 +4,9 @@ import type { RecordsWriteMessage } from '../interfaces/records/types.js'; import type { ProtocolDefinition, ProtocolRuleSet, ProtocolsConfigureMessage } from '../interfaces/protocols/types.js'; import { DwnInterfaceName, DwnMethodName, Message } from './message.js'; +import { Filter } from './types.js'; -const methodToAllowedActionMap = { +const methodToAllowedActionMap: Record = { [DwnMethodName.Write]: 'write', }; @@ -31,13 +32,14 @@ export class ProtocolAuthorization { const recordSchemaToLabelMap: Map = new Map(); for (const schemaLabel in protocolDefinition.labels) { const schema = protocolDefinition.labels[schemaLabel].schema; - recordSchemaToLabelMap[schema] = schemaLabel; + recordSchemaToLabelMap.set(schema, schemaLabel); } // get the rule set for the inbound message const inboundMessageRuleSet = ProtocolAuthorization.getRuleSet( recordsWrite.message, - protocolDefinition, ancestorMessageChain, + protocolDefinition, + ancestorMessageChain, recordSchemaToLabelMap ); @@ -57,10 +59,10 @@ export class ProtocolAuthorization { */ private static async fetchProtocolDefinition(tenant: string, recordsWrite: RecordsWrite, messageStore: MessageStore): Promise { // get the protocol URI - const protocolUri = recordsWrite.message.descriptor.protocol; + const protocolUri = recordsWrite.message.descriptor.protocol!; // fetch the corresponding protocol definition - const query = { + const query: Filter = { interface : DwnInterfaceName.Protocols, method : DwnMethodName.Configure, protocol : protocolUri @@ -83,14 +85,14 @@ export class ProtocolAuthorization { : Promise { const ancestorMessageChain: RecordsWriteMessage[] = []; - const protocol = recordsWrite.message.descriptor.protocol; - const contextId = recordsWrite.message.contextId; + const protocol = recordsWrite.message.descriptor.protocol!; + const contextId = recordsWrite.message.contextId!; // keep walking up the chain from the inbound message's parent, until there is no more parent let currentParentId = recordsWrite.message.descriptor.parentId; while (currentParentId !== undefined) { // fetch parent - const query = { + const query: Filter = { interface : DwnInterfaceName.Records, method : DwnMethodName.Write, protocol, @@ -126,17 +128,17 @@ export class ProtocolAuthorization { // walk down the ancestor message chain from the root ancestor record and match against the corresponding rule set at each level // to make sure the chain structure is allowed - let allowedRecordsAtCurrentLevel = protocolDefinition.records; + let allowedRecordsAtCurrentLevel: { [key: string]: ProtocolRuleSet} | undefined = protocolDefinition.records; let currentMessageIndex = 0; while (true) { const currentRecordSchema = messageChain[currentMessageIndex].descriptor.schema; - const currentRecordType = recordSchemaToLabelMap[currentRecordSchema]; + const currentRecordType = recordSchemaToLabelMap.get(currentRecordSchema!); if (currentRecordType === undefined) { throw new Error(`record with schema '${currentRecordSchema}' not allowed in protocol`); } - if (!(currentRecordType in allowedRecordsAtCurrentLevel)) { + if (allowedRecordsAtCurrentLevel === undefined || !(currentRecordType in allowedRecordsAtCurrentLevel)) { throw new Error(`record with schema: '${currentRecordSchema}' not allowed in structure level ${currentMessageIndex}`); } @@ -202,7 +204,7 @@ export class ProtocolAuthorization { } } - let allowedActions: string[]; + let allowedActions: string[] = []; if (allowRule.anyone !== undefined) { allowedActions = allowRule.anyone.to; } else if (allowRule.recipient !== undefined) { @@ -262,7 +264,7 @@ export class ProtocolAuthorization { const expectedAncestorType = expectedAncestors[i]; const ancestorMessage = ancestorMessageChain[i]; - const actualAncestorType = recordSchemaToLabelMap[ancestorMessage.descriptor.schema]; + const actualAncestorType = recordSchemaToLabelMap.get(ancestorMessage.descriptor.schema!); if (actualAncestorType !== expectedAncestorType) { throw new Error(`mismatching record schema: expecting ${expectedAncestorType} but actual ${actualAncestorType}`); } diff --git a/tests/interfaces/records/handlers/records-write.spec.ts b/tests/interfaces/records/handlers/records-write.spec.ts index 0e5528177..f9a6d8352 100644 --- a/tests/interfaces/records/handlers/records-write.spec.ts +++ b/tests/interfaces/records/handlers/records-write.spec.ts @@ -943,6 +943,58 @@ describe('RecordsWriteHandler.handle()', () => { expect(reply.status.detail).to.contain('not allowed in structure level'); }); + + it('should fail authorization if record schema is not allowed at the hierarchical level attempted for the RecordsWrite', async () => { + const alice = await DidKeyResolver.generate(); + + const protocol = 'chatProtocol'; + const protocolDefinition = { + labels: { + email: { + schema: 'emailSchema' + }, + sms: { + schema: 'smsSchema' + } + }, + records: { + email: {}, + sms: {} + } + }; + const protocolConfig = await TestDataGenerator.generateProtocolsConfigure({ + requester: alice, + protocol, + protocolDefinition + }); + + const protocolConfigureReply = await dwn.processMessage(alice.did, protocolConfig.message, protocolConfig.dataStream); + expect(protocolConfigureReply.status.code).to.equal(202); + + const emailRecordsWrite = await TestDataGenerator.generateRecordsWrite({ + requester : alice, + recipientDid : alice.did, + protocol, + schema : protocolDefinition.labels.email.schema, + data: Encoder.stringToBytes('any data'), + }); + await dwn.processMessage(alice.did, emailRecordsWrite.message, emailRecordsWrite.dataStream); + + const smsSchemaResponse = await TestDataGenerator.generateRecordsWrite({ + requester : alice, + recipientDid : alice.did, + protocol, + schema : protocolDefinition.labels.sms.schema, // SMS are allowed, but not as a child record of emails + data: Encoder.stringToBytes('any other data'), + parentId: emailRecordsWrite.message.recordId, + contextId: await emailRecordsWrite.recordsWrite.getEntryId() + }); + const reply = await dwn.processMessage(alice.did, smsSchemaResponse.message, smsSchemaResponse.dataStream); + + expect(reply.status.code).to.equal(401); + expect(reply.status.detail).to.contain('record with schema: \'smsSchema\' not allowed in structure level 1'); + }); + it('should only allow DWN owner to write if record does not have an allow rule defined', async () => { const alice = await DidKeyResolver.generate();