From c6733d60121547fac90c565d625272c3b4e217ff Mon Sep 17 00:00:00 2001 From: Diane Huxley Date: Fri, 14 Apr 2023 16:40:18 -0700 Subject: [PATCH] Support protocol authorized reads (#307) * Support protocol authorized reads * Refactor ancestorMessageChain * Rewrite tests with email protocol * Remove stubDidResolver * Update socialMediaProtocol to allow anyone to read captions * Update tests/interfaces/records/handlers/records-read.spec.ts Co-authored-by: Henry Tsai * Lint --------- Co-authored-by: Henry Tsai --- README.md | 2 +- json-schemas/protocol-rule-set.json | 127 +++++++--------- src/core/protocol-authorization.ts | 143 ++++++++++++------ .../records/handlers/records-read.ts | 2 +- .../records/messages/records-read.ts | 7 +- .../records/handlers/records-read.spec.ts | 143 +++++++++++++++++- .../records/handlers/records-write.spec.ts | 50 ------ tests/vectors/protocol-definitions/email.json | 18 ++- .../protocol-definitions/social-media.json | 13 +- 9 files changed, 331 insertions(+), 174 deletions(-) diff --git a/README.md b/README.md index 31485938b..45135460c 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.56%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-93.09%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-91.24%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-93.56%25-brightgreen.svg?style=flat) +![Statements](https://img.shields.io/badge/statements-93.62%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-93.2%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-91.24%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-93.62%25-brightgreen.svg?style=flat) ## Introduction diff --git a/json-schemas/protocol-rule-set.json b/json-schemas/protocol-rule-set.json index a78db025b..ce67a40cb 100644 --- a/json-schemas/protocol-rule-set.json +++ b/json-schemas/protocol-rule-set.json @@ -6,91 +6,78 @@ "properties": { "allow": { "type": "object", - "oneOf": [ - { + "minProperties": 1, + "additionalProperties": false, + "properties": { + "anyone": { "type": "object", "additionalProperties": false, "properties": { - "anyone": { - "type": "object", - "additionalProperties": false, - "properties": { - "to": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "enum": [ - "write" - ] - } - } - }, - "required": [ - "to" - ] + "to": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "read", + "write" + ] + } } - } + }, + "required": [ + "to" + ] }, - { + "author": { "type": "object", "additionalProperties": false, "properties": { - "author": { - "type": "object", - "additionalProperties": false, - "properties": { - "of": { - "type": "string" - }, - "to": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "enum": [ - "write" - ] - } - } - }, - "required": [ - "of", - "to" - ] + "of": { + "type": "string" + }, + "to": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "read", + "write" + ] + } } - } + }, + "required": [ + "of", + "to" + ] }, - { + "recipient": { "type": "object", "additionalProperties": false, "properties": { - "recipient": { - "type": "object", - "additionalProperties": false, - "properties": { - "of": { - "type": "string" - }, - "to": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "enum": [ - "write" - ] - } - } - }, - "required": [ - "of", - "to" - ] + "of": { + "type": "string" + }, + "to": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "read", + "write" + ] + } } - } + }, + "required": [ + "of", + "to" + ] } - ] + } }, "records": { "type": "object", diff --git a/src/core/protocol-authorization.ts b/src/core/protocol-authorization.ts index 685f79fb2..a31818c33 100644 --- a/src/core/protocol-authorization.ts +++ b/src/core/protocol-authorization.ts @@ -1,13 +1,15 @@ import type { MessageStore } from '../store/message-store.js'; -import type { RecordsWrite } from '../interfaces/records/messages/records-write.js'; -import type { RecordsWriteMessage } from '../interfaces/records/types.js'; -import type { BaseMessage, Filter } from './types.js'; +import type { RecordsRead } from '../interfaces/records/messages/records-read.js'; +import type { Filter, TimestampedMessage } from './types.js'; import type { ProtocolDefinition, ProtocolRuleSet, ProtocolsConfigureMessage } from '../interfaces/protocols/types.js'; +import type { RecordsReadMessage, RecordsWriteMessage } from '../interfaces/records/types.js'; +import { RecordsWrite } from '../interfaces/records/messages/records-write.js'; import { DwnInterfaceName, DwnMethodName, Message } from './message.js'; const methodToAllowedActionMap: Record = { - [DwnMethodName.Write]: 'write', + [DwnMethodName.Write] : 'write', + [DwnMethodName.Read] : 'read', }; export class ProtocolAuthorization { @@ -18,15 +20,21 @@ export class ProtocolAuthorization { */ public static async authorize( tenant: string, - recordsWrite: RecordsWrite, - requesterDid: string, + incomingMessage: RecordsRead | RecordsWrite, + requesterDid: string | undefined, messageStore: MessageStore ): Promise { - // fetch the protocol definition - const protocolDefinition = await ProtocolAuthorization.fetchProtocolDefinition(tenant, recordsWrite, messageStore); - // fetch ancestor message chain - const ancestorMessageChain: RecordsWriteMessage[] = await ProtocolAuthorization.constructAncestorMessageChain(tenant, recordsWrite, messageStore); + const ancestorMessageChain: RecordsWriteMessage[] = + await ProtocolAuthorization.constructAncestorMessageChain(tenant, incomingMessage, messageStore); + + // fetch the protocol definition + const protocolDefinition = await ProtocolAuthorization.fetchProtocolDefinition( + tenant, + incomingMessage, + ancestorMessageChain, + messageStore + ); // record schema -> schema label map const recordSchemaToLabelMap: Map = new Map(); @@ -37,7 +45,7 @@ export class ProtocolAuthorization { // get the rule set for the inbound message const inboundMessageRuleSet = ProtocolAuthorization.getRuleSet( - recordsWrite.message, + incomingMessage.message, protocolDefinition, ancestorMessageChain, recordSchemaToLabelMap @@ -47,22 +55,32 @@ export class ProtocolAuthorization { ProtocolAuthorization.verifyAllowedActions( tenant, requesterDid, - recordsWrite, + incomingMessage.message.descriptor.method, inboundMessageRuleSet, ancestorMessageChain, recordSchemaToLabelMap ); - // verify allowed condition of the write - await ProtocolAuthorization.verifyActionCondition(tenant, recordsWrite, messageStore); + // verify allowed condition of incoming message + await ProtocolAuthorization.verifyActionCondition(tenant, incomingMessage, messageStore); } /** * Fetches the protocol definition based on the protocol specified in the given message. */ - private static async fetchProtocolDefinition(tenant: string, recordsWrite: RecordsWrite, messageStore: MessageStore): Promise { + private static async fetchProtocolDefinition( + tenant: string, + incomingMessage: RecordsRead | RecordsWrite, + ancestorMessageChain: RecordsWriteMessage[], + messageStore: MessageStore + ): Promise { // get the protocol URI - const protocolUri = recordsWrite.message.descriptor.protocol!; + let protocolUri: string; + if (incomingMessage.message.descriptor.method === DwnMethodName.Write) { + protocolUri = (incomingMessage as RecordsWrite).message.descriptor.protocol!; + } else { + protocolUri = ancestorMessageChain[ancestorMessageChain.length-1].descriptor.protocol!; + } // fetch the corresponding protocol definition const query: Filter = { @@ -84,10 +102,31 @@ export class ProtocolAuthorization { * Constructs a chain of ancestor messages * @returns the ancestor chain of messages where the first element is the root of the chain; returns empty array if no parent is specified. */ - private static async constructAncestorMessageChain(tenant: string, recordsWrite: RecordsWrite, messageStore: MessageStore) + private static async constructAncestorMessageChain( + tenant: string, + incomingMessage: RecordsRead | RecordsWrite, + messageStore: MessageStore + ) : Promise { const ancestorMessageChain: RecordsWriteMessage[] = []; + // Get first RecordsWrite in ancestor chain, or use incoming write message + let recordsWrite: RecordsWrite; + if (incomingMessage.message.descriptor.method === DwnMethodName.Write) { + recordsWrite = incomingMessage as RecordsWrite; + } else { + const recordsRead = incomingMessage as RecordsRead; + const query = { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Write, + recordId : recordsRead.message.descriptor.recordId, + }; + const existingMessages = await messageStore.query(tenant, query) as TimestampedMessage[]; + const recordsWriteMessage = await RecordsWrite.getNewestMessage(existingMessages) as RecordsWriteMessage; + recordsWrite = await RecordsWrite.parse(recordsWriteMessage); + ancestorMessageChain.push(recordsWrite.message); + } + const protocol = recordsWrite.message.descriptor.protocol!; const contextId = recordsWrite.message.contextId!; @@ -118,16 +157,19 @@ export class ProtocolAuthorization { } /** - * Gets the rule set corresponding to the inbound message. + * Gets the rule set corresponding to the given message chain. */ private static getRuleSet( - inboundMessage: RecordsWriteMessage, + inboundMessage: RecordsReadMessage | RecordsWriteMessage, protocolDefinition: ProtocolDefinition, ancestorMessageChain: RecordsWriteMessage[], recordSchemaToLabelMap: Map ): ProtocolRuleSet { - // make a copy of the ancestor messages and include the inbound message in the chain - const messageChain = [...ancestorMessageChain, inboundMessage]; + // make a copy of the ancestor messages and include the inbound write in the chain + const messageChain = [...ancestorMessageChain]; + if (inboundMessage.descriptor.method === DwnMethodName.Write) { + messageChain.push(inboundMessage as RecordsWriteMessage); + } // 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 @@ -163,14 +205,13 @@ export class ProtocolAuthorization { */ private static verifyAllowedActions( tenant: string, - requesterDid: string, - incomingMessage: Message, + requesterDid: string | undefined, + incomingMessageMethod: DwnMethodName, inboundMessageRuleSet: ProtocolRuleSet, ancestorMessageChain: RecordsWriteMessage[], recordSchemaToLabelMap: Map ): void { const allowRule = inboundMessageRuleSet.allow; - const incomingMessageMethod = incomingMessage.message.descriptor.method; if (allowRule === undefined) { // if no allow rule is defined, owner of DWN can do everything @@ -192,10 +233,12 @@ export class ProtocolAuthorization { allowRule.author.of, recordSchemaToLabelMap ); - const expectedRequesterDid = Message.getAuthor(messageForAuthorCheck); + if (messageForAuthorCheck !== undefined) { + const expectedRequesterDid = Message.getAuthor(messageForAuthorCheck); - if (requesterDid === expectedRequesterDid) { - allowRule.author.to.forEach(action => allowedActions.add(action)); + if (requesterDid === expectedRequesterDid) { + allowRule.author.to.forEach(action => allowedActions.add(action)); + } } } @@ -205,10 +248,12 @@ export class ProtocolAuthorization { allowRule.recipient.of, recordSchemaToLabelMap ); - const expectedRequesterDid = messageForRecipientCheck.descriptor.recipient; + if (messageForRecipientCheck !== undefined) { + const expectedRequesterDid = messageForRecipientCheck.descriptor.recipient; - if (requesterDid === expectedRequesterDid) { - allowRule.recipient.to.forEach(action => allowedActions.add(action)); + if (requesterDid === expectedRequesterDid) { + allowRule.recipient.to.forEach(action => allowedActions.add(action)); + } } } @@ -223,26 +268,32 @@ export class ProtocolAuthorization { * Currently the only check is: if the write is not the initial write, the author must be the same as the initial write * @throws {Error} if fails verification */ - private static async verifyActionCondition(tenant: string, recordsWrite: RecordsWrite, messageStore: MessageStore): Promise { - const isInitialWrite = await recordsWrite.isInitialWrite(); - if (!isInitialWrite) { - // fetch the initialWrite - const query = { - entryId: recordsWrite.message.recordId - }; - const result = await messageStore.query(tenant, query) as RecordsWriteMessage[]; - - // check the author of the initial write matches the author of the incoming message - const initialWrite = result[0]; - const authorOfInitialWrite = Message.getAuthor(initialWrite); - if (recordsWrite.author !== authorOfInitialWrite) { - throw new Error(`author of incoming message '${recordsWrite.author}' must match to author of initial write '${authorOfInitialWrite}'`); + private static async verifyActionCondition(tenant: string, incomingMessage: RecordsRead | RecordsWrite, messageStore: MessageStore): Promise { + if (incomingMessage.message.descriptor.method === DwnMethodName.Read) { + // Currently no conditions for reads + } else if (incomingMessage.message.descriptor.method === DwnMethodName.Write) { + const recordsWrite = incomingMessage as RecordsWrite; + const isInitialWrite = await recordsWrite.isInitialWrite(); + if (!isInitialWrite) { + // fetch the initialWrite + const query = { + entryId: recordsWrite.message.recordId + }; + const result = await messageStore.query(tenant, query) as RecordsWriteMessage[]; + + // check the author of the initial write matches the author of the incoming message + const initialWrite = result[0]; + const authorOfInitialWrite = Message.getAuthor(initialWrite); + if (recordsWrite.author !== authorOfInitialWrite) { + throw new Error(`author of incoming message '${recordsWrite.author}' must match to author of initial write '${authorOfInitialWrite}'`); + } } } } /** * Gets the message from the message chain based on the path specified. + * Returns undefined if matching message does not existing in ancestor chain * @param messagePath `/` delimited path starting from the root ancestor. * Each path segment denotes the expected record type declared in protocol definition. * e.g. `A/B/C` means that the root ancestor must be of type A, its child must be of type B, followed by a child of type C. @@ -252,12 +303,12 @@ export class ProtocolAuthorization { ancestorMessageChain: RecordsWriteMessage[], messagePath: string, recordSchemaToLabelMap: Map - ): RecordsWriteMessage { + ): RecordsWriteMessage | undefined { const expectedAncestors = messagePath.split('/'); // consider moving this check to ProtocolsConfigure message ingestion if (expectedAncestors.length > ancestorMessageChain.length) { - throw new Error('specified path to expected recipient is longer than actual length of ancestor message chain'); + return undefined; } let i = 0; diff --git a/src/interfaces/records/handlers/records-read.ts b/src/interfaces/records/handlers/records-read.ts index f3110056d..cb809fa21 100644 --- a/src/interfaces/records/handlers/records-read.ts +++ b/src/interfaces/records/handlers/records-read.ts @@ -58,7 +58,7 @@ export class RecordsReadHandler implements MethodHandler { // NOTE we check for `true` as a defensive equality comparison, so we don't mistakenly assume a record that is not published as published. } else { try { - await recordsRead.authorize(tenant); + await recordsRead.authorize(tenant, await RecordsWrite.parse(newestRecordsWrite), this.messageStore); } catch (error) { return MessageReply.fromError(error, 401); } diff --git a/src/interfaces/records/messages/records-read.ts b/src/interfaces/records/messages/records-read.ts index b81efce55..96b6f5187 100644 --- a/src/interfaces/records/messages/records-read.ts +++ b/src/interfaces/records/messages/records-read.ts @@ -1,9 +1,12 @@ import type { BaseMessage } from '../../../core/types.js'; +import type { MessageStore } from '../../../store/message-store.js'; +import type { RecordsWrite } from './records-write.js'; import type { SignatureInput } from '../../../jose/jws/general/types.js'; import type { RecordsReadDescriptor, RecordsReadMessage } from '../types.js'; import { getCurrentTimeInHighPrecision } from '../../../utils/time.js'; import { Message } from '../../../core/message.js'; +import { ProtocolAuthorization } from '../../../core/protocol-authorization.js'; import { validateAuthorizationIntegrity } from '../../../core/auth.js'; import { DwnInterfaceName, DwnMethodName } from '../../../core/message.js'; @@ -49,10 +52,12 @@ export class RecordsRead extends Message { return new RecordsRead(message); } - public async authorize(tenant: string): Promise { + public async authorize(tenant: string, newestRecordsWrite: RecordsWrite, messageStore: MessageStore): Promise { // if author/requester is the same as the target tenant, we can directly grant access if (this.author === tenant) { return; + } else if (newestRecordsWrite.message.descriptor.protocol !== undefined) { + await ProtocolAuthorization.authorize(tenant, this, this.author, messageStore); } else { throw new Error('message failed authorization'); } diff --git a/tests/interfaces/records/handlers/records-read.spec.ts b/tests/interfaces/records/handlers/records-read.spec.ts index fb086b44d..92e989fc3 100644 --- a/tests/interfaces/records/handlers/records-read.spec.ts +++ b/tests/interfaces/records/handlers/records-read.spec.ts @@ -1,9 +1,10 @@ import type { DerivedPrivateJwk } from '../../../../src/utils/hd-key.js'; +import emailProtocolDefinition from '../../../vectors/protocol-definitions/email.json' assert { type: 'json' }; import type { EncryptionInput } from '../../../../src/interfaces/records/messages/records-write.js'; import type { ProtocolDefinition } from '../../../../src/index.js'; +import socialMediaProtocolDefinition from '../../../vectors/protocol-definitions/social-media.json' assert { type: 'json' }; import chaiAsPromised from 'chai-as-promised'; -import emailProtocolDefinition from '../../../vectors/protocol-definitions/email.json' assert { type: 'json' }; import sinon from 'sinon'; import chai, { expect } from 'chai'; @@ -153,6 +154,146 @@ describe('RecordsReadHandler.handle()', () => { expect(Comparer.byteArraysEqual(dataFetched, dataBytes!)).to.be.true; }); + describe('protocol based reads', () => { + it('should allow read with allow-anyone rule', async () => { + // scenario: Alice writes an image to her DWN, then Bob reads the image because he is "anyone". + + const alice = await DidKeyResolver.generate(); + const bob = await DidKeyResolver.generate(); + + const protocol = 'https://tbd.website/decentralized-web-node/protocols/social-media'; + const protocolDefinition: ProtocolDefinition = socialMediaProtocolDefinition; + + // Install social-media protocol on Alice's DWN + const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ + requester: alice, + protocol, + protocolDefinition + }); + const protocolWriteReply = await dwn.processMessage(alice.did, protocolsConfig.message, protocolsConfig.dataStream); + expect(protocolWriteReply.status.code).to.equal(202); + + // Alice writes image to her DWN + const encodedImage = new TextEncoder().encode('cafe-aesthetic.jpg'); + const imageRecordsWrite = await TestDataGenerator.generateRecordsWrite({ + requester : alice, + protocol, + schema : protocolDefinition.labels.image.schema, + data : encodedImage, + recipientDid : alice.did + }); + const imageReply = await dwn.processMessage(alice.did, imageRecordsWrite.message, imageRecordsWrite.dataStream); + expect(imageReply.status.code).to.equal(202); + + // Bob (anyone) reads the image that Alice wrote + const imageRecordsRead = await RecordsRead.create({ + recordId : imageRecordsWrite.message.recordId, + authorizationSignatureInput : Jws.createSignatureInput(bob) + }); + const imageReadReply = await dwn.processMessage(alice.did, imageRecordsRead.message); + expect(imageReadReply.status.code).to.equal(200); + }); + + it('should allow read with recipient rule', async () => { + // scenario: Alice sends an email to Bob, then Bob reads the email. + // ImposterBob tries and fails to read the email. + + const alice = await DidKeyResolver.generate(); + const bob = await DidKeyResolver.generate(); + const imposterBob = await DidKeyResolver.generate(); + + const protocol = 'https://tbd.website/decentralized-web-node/protocols/email'; + const protocolDefinition: ProtocolDefinition = emailProtocolDefinition; + + // Install email protocol on Alice's DWN + const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ + requester: alice, + protocol, + protocolDefinition + }); + const protocolWriteReply = await dwn.processMessage(alice.did, protocolsConfig.message, protocolsConfig.dataStream); + expect(protocolWriteReply.status.code).to.equal(202); + + // Alice writes an email with Bob as recipient + const encodedEmail = new TextEncoder().encode('Dear Bob, hello!'); + const emailRecordsWrite = await TestDataGenerator.generateRecordsWrite({ + requester : alice, + protocol, + schema : protocolDefinition.labels.email.schema, + data : encodedEmail, + recipientDid : bob.did + }); + const imageReply = await dwn.processMessage(alice.did, emailRecordsWrite.message, emailRecordsWrite.dataStream); + expect(imageReply.status.code).to.equal(202); + + // Bob reads Alice's email + const bobRecordsRead = await RecordsRead.create({ + recordId : emailRecordsWrite.message.recordId, + authorizationSignatureInput : Jws.createSignatureInput(bob) + }); + const bobReadReply = await dwn.processMessage(alice.did, bobRecordsRead.message); + expect(bobReadReply.status.code).to.equal(200); + + // ImposterBob is not able to read Alice's email + const imposterRecordsRead = await RecordsRead.create({ + recordId : emailRecordsWrite.message.recordId, + authorizationSignatureInput : Jws.createSignatureInput(imposterBob) + }); + const imposterReadReply = await dwn.processMessage(alice.did, imposterRecordsRead.message); + expect(imposterReadReply.status.code).to.equal(401); + expect(imposterReadReply.status.detail).to.include('inbound message action \'read\' not in list of allowed actions'); + }); + + it('should allow read with author rule', async () => { + // scenario: Bob sends an email to Alice, then Bob reads the email. + // ImposterBob tries and fails to read the email. + const alice = await DidKeyResolver.generate(); + const bob = await DidKeyResolver.generate(); + const imposterBob = await DidKeyResolver.generate(); + + const protocol = 'https://tbd.website/decentralized-web-node/protocols/email'; + const protocolDefinition: ProtocolDefinition = emailProtocolDefinition; + + // Install email protocol on Alice's DWN + const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ + requester: alice, + protocol, + protocolDefinition + }); + const protocolWriteReply = await dwn.processMessage(alice.did, protocolsConfig.message, protocolsConfig.dataStream); + expect(protocolWriteReply.status.code).to.equal(202); + + // Alice writes an email with Bob as recipient + const encodedEmail = new TextEncoder().encode('Dear Alice, hello!'); + const emailRecordsWrite = await TestDataGenerator.generateRecordsWrite({ + requester : bob, + protocol, + schema : protocolDefinition.labels.email.schema, + data : encodedEmail, + recipientDid : alice.did + }); + const imageReply = await dwn.processMessage(alice.did, emailRecordsWrite.message, emailRecordsWrite.dataStream); + expect(imageReply.status.code).to.equal(202); + + // Bob reads the email he just sent + const bobRecordsRead = await RecordsRead.create({ + recordId : emailRecordsWrite.message.recordId, + authorizationSignatureInput : Jws.createSignatureInput(bob) + }); + const bobReadReply = await dwn.processMessage(alice.did, bobRecordsRead.message); + expect(bobReadReply.status.code).to.equal(200); + + // ImposterBob is not able to read the email + const imposterRecordsRead = await RecordsRead.create({ + recordId : emailRecordsWrite.message.recordId, + authorizationSignatureInput : Jws.createSignatureInput(imposterBob) + }); + const imposterReadReply = await dwn.processMessage(alice.did, imposterRecordsRead.message); + expect(imposterReadReply.status.code).to.equal(401); + expect(imposterReadReply.status.detail).to.include('inbound message action \'read\' not in list of allowed actions'); + }); + }); + it('should return 404 RecordRead if data does not exist', async () => { const alice = await DidKeyResolver.generate(); diff --git a/tests/interfaces/records/handlers/records-write.spec.ts b/tests/interfaces/records/handlers/records-write.spec.ts index 8a53bb9d8..0126c5f18 100644 --- a/tests/interfaces/records/handlers/records-write.spec.ts +++ b/tests/interfaces/records/handlers/records-write.spec.ts @@ -1168,56 +1168,6 @@ describe('RecordsWriteHandler.handle()', () => { expect(reply.status.detail).to.contain(`no allow rule defined for Write`); }); - it('should fail authorization if path to expected recipient in definition is longer than actual length of ancestor message chain', async () => { - const alice = await DidKeyResolver.generate(); - const issuer = await DidKeyResolver.generate(); - - // create an invalid ancestor path that is longer than possible - const invalidProtocolDefinition = { ...credentialIssuanceProtocolDefinition }; - invalidProtocolDefinition.records.credentialApplication.records.credentialResponse.allow.recipient.of - = 'credentialApplication/credentialResponse'; // this is invalid as the ancestor can only be just `credentialApplication` - - // write the VC issuance protocol - const protocol = 'https://identity.foundation/decentralized-web-node/protocols/credential-issuance'; - const protocolConfig = await TestDataGenerator.generateProtocolsConfigure({ - requester : alice, - protocol, - protocolDefinition : invalidProtocolDefinition - }); - - const protocolConfigureReply = await dwn.processMessage(alice.did, protocolConfig.message, protocolConfig.dataStream); - expect(protocolConfigureReply.status.code).to.equal(202); - - // simulate Alice's VC applications with both issuer - const data = Encoder.stringToBytes('irrelevant'); - const messageDataWithIssuerA = await TestDataGenerator.generateRecordsWrite({ - requester : alice, - recipientDid : issuer.did, - schema : credentialIssuanceProtocolDefinition.labels.credentialApplication.schema, - protocol, - data - }); - const contextId = await messageDataWithIssuerA.recordsWrite.getEntryId(); - - let reply = await dwn.processMessage(alice.did, messageDataWithIssuerA.message, messageDataWithIssuerA.dataStream); - expect(reply.status.code).to.equal(202); - - // simulate issuer attempting to respond to Alice's VC application - const invalidResponseByIssuerA = await TestDataGenerator.generateRecordsWrite({ - requester : issuer, - recipientDid : alice.did, - schema : credentialIssuanceProtocolDefinition.labels.credentialResponse.schema, - contextId, - parentId : messageDataWithIssuerA.message.recordId, - protocol, - data - }); - - reply = await dwn.processMessage(alice.did, invalidResponseByIssuerA.message, invalidResponseByIssuerA.dataStream); - expect(reply.status.code).to.equal(401); - expect(reply.status.detail).to.contain('path to expected recipient is longer than actual length of ancestor message chain'); - }); - it('should fail authorization if path to expected recipient in definition has incorrect label', async () => { const alice = await DidKeyResolver.generate(); const issuer = await DidKeyResolver.generate(); diff --git a/tests/vectors/protocol-definitions/email.json b/tests/vectors/protocol-definitions/email.json index ebe6099c6..9d10ae577 100644 --- a/tests/vectors/protocol-definitions/email.json +++ b/tests/vectors/protocol-definitions/email.json @@ -11,6 +11,14 @@ "to": [ "write" ] + }, + "author": { + "of": "email", + "to": ["read"] + }, + "recipient": { + "of": "email", + "to": ["read"] } }, "records": { @@ -20,10 +28,18 @@ "to": [ "write" ] + }, + "author": { + "of": "email/email", + "to": ["read"] + }, + "recipient": { + "of": "email/email", + "to": ["read"] } } } } } } -} \ No newline at end of file +} diff --git a/tests/vectors/protocol-definitions/social-media.json b/tests/vectors/protocol-definitions/social-media.json index 628c9ddb8..857e99cc1 100644 --- a/tests/vectors/protocol-definitions/social-media.json +++ b/tests/vectors/protocol-definitions/social-media.json @@ -24,20 +24,27 @@ "image": { "allow": { "anyone": { - "to": ["write"] + "to": ["read", "write"] } }, "records": { "caption": { "allow": { + "anyone": { + "to": ["read"] + }, "author": { - "of": "image", - "to": ["write"] + "of": "image", + "to": ["write"] } } }, "reply": { "allow": { + "author": { + "of": "image", + "to": ["read"] + }, "recipient": { "of": "image", "to": ["write"]