From 661fad206aa94a73cff1a8a9d2997c4d2d628cc1 Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Thu, 13 Jun 2024 15:04:01 -0400 Subject: [PATCH] RecordsPermissionScope Grants must be scoped to a protocol (#750) Modifies `RecordsPermissionScope` to require scoping to a specific protocol. This also removes the ability to scope to a schema as you would just scope to a protocolPath instead. The motivation behind this was to make querying for all Messages tied to a specific protocol for subset sync. Without this change getting grants, and more importantly the grant revocations which have unrestricted scopes would be challenging/not performant. Since we are planning on getting rid of flat-spaced records anyway, this is a step in the right direction. - Remove `schema` from `RecordsPermissionScope` - `RecordsPermissionScope` requires `protocol` to be set - The `protocol` provided within the `RecordsPermissionScope` is also added as a `protocol` tag to the `RecordsWriteMessage` descriptor. - When validating the Scope of a Grant `RecordsWriteMessage`, ensure the `protocol` tag on the record is the same as the scope `protocol` defined in the `RecordsPermissionScope`. `grant` case is covered by this PR. --- src/core/dwn-error.ts | 5 +- src/core/records-grant-authorization.ts | 53 +---- src/protocols/permissions.ts | 70 +++--- src/types/permission-types.ts | 25 +- tests/features/permissions.spec.ts | 290 ++++++++++-------------- tests/handlers/protocols-query.spec.ts | 2 +- tests/handlers/records-read.spec.ts | 190 +--------------- tests/handlers/records-write.spec.ts | 242 +++++--------------- tests/utils/records.spec.ts | 7 +- 9 files changed, 245 insertions(+), 639 deletions(-) diff --git a/src/core/dwn-error.ts b/src/core/dwn-error.ts index c846ec6ce..7292a93d0 100644 --- a/src/core/dwn-error.ts +++ b/src/core/dwn-error.ts @@ -47,9 +47,11 @@ export enum DwnErrorCode { MessageGetInvalidCid = 'MessageGetInvalidCid', ParseCidCodecNotSupported = 'ParseCidCodecNotSupported', ParseCidMultihashNotSupported = 'ParseCidMultihashNotSupported', + PermissionsProtocolCreateGrantRecordsScopeMissingProtocol = 'PermissionsProtocolCreateGrantRecordsScopeMissingProtocol', PermissionsProtocolValidateSchemaUnexpectedRecord = 'PermissionsProtocolValidateSchemaUnexpectedRecord', PermissionsProtocolValidateScopeContextIdProhibitedProperties = 'PermissionsProtocolValidateScopeContextIdProhibitedProperties', - PermissionsProtocolValidateScopeSchemaProhibitedProperties = 'PermissionsProtocolValidateScopeSchemaProhibitedProperties', + PermissionsProtocolValidateScopeProtocolMismatch = 'PermissionsProtocolValidateScopeProtocolMismatch', + PermissionsProtocolValidateScopeMissingProtocolTag = 'PermissionsProtocolValidateScopeMissingProtocolTag', PrivateKeySignerUnableToDeduceAlgorithm = 'PrivateKeySignerUnableToDeduceAlgorithm', PrivateKeySignerUnableToDeduceKeyId = 'PrivateKeySignerUnableToDeduceKeyId', PrivateKeySignerUnsupportedCurve = 'PrivateKeySignerUnsupportedCurve', @@ -101,7 +103,6 @@ export enum DwnErrorCode { RecordsGrantAuthorizationDeleteProtocolScopeMismatch = 'RecordsGrantAuthorizationDeleteProtocolScopeMismatch', RecordsGrantAuthorizationQueryOrSubscribeProtocolScopeMismatch = 'RecordsGrantAuthorizationQueryOrSubscribeProtocolScopeMismatch', RecordsGrantAuthorizationScopeContextIdMismatch = 'RecordsGrantAuthorizationScopeContextIdMismatch', - RecordsGrantAuthorizationScopeMissingProtocol = 'RecordsGrantAuthorizationScopeMissingProtocol', RecordsGrantAuthorizationScopeNotRecords = `RecordsGrantAuthorizationScopeNotRecords`, RecordsGrantAuthorizationScopeProtocolMismatch = 'RecordsGrantAuthorizationScopeProtocolMismatch', RecordsGrantAuthorizationScopeProtocolPathMismatch = 'RecordsGrantAuthorizationScopeProtocolPathMismatch', diff --git a/src/core/records-grant-authorization.ts b/src/core/records-grant-authorization.ts index 4a97ef4ae..3b9542ea1 100644 --- a/src/core/records-grant-authorization.ts +++ b/src/core/records-grant-authorization.ts @@ -138,37 +138,12 @@ export class RecordsGrantAuthorization { } /** - * Verifies the given record against the scope of the given grant. + * Verifies a record against the scope of the given grant. */ private static verifyScope( - recordsWriteMessage: RecordsWriteMessage, - grantScope: RecordsPermissionScope, - ): void { - if (RecordsGrantAuthorization.isUnrestrictedScope(grantScope)) { - // scope has no restrictions beyond interface and method. Message is authorized to access any record. - return; - } else if (recordsWriteMessage.descriptor.protocol !== undefined) { - // authorization of protocol records must have grants that explicitly include the protocol - RecordsGrantAuthorization.verifyProtocolRecordScope(recordsWriteMessage, grantScope); - } else { - RecordsGrantAuthorization.verifyFlatRecordScope(recordsWriteMessage, grantScope); - } - } - - /** - * Verifies a protocol record against the scope of the given grant. - */ - private static verifyProtocolRecordScope( recordsWriteMessage: RecordsWriteMessage, grantScope: RecordsPermissionScope ): void { - // Protocol records must have grants specifying the protocol - if (grantScope.protocol === undefined) { - throw new DwnError( - DwnErrorCode.RecordsGrantAuthorizationScopeMissingProtocol, - 'Grant for protocol record must specify protocol in its scope' - ); - } // The record's protocol must match the protocol specified in the record if (grantScope.protocol !== recordsWriteMessage.descriptor.protocol) { @@ -197,23 +172,6 @@ export class RecordsGrantAuthorization { } } - /** - * Verifies a non-protocol record against the scope of the given grant. - */ - private static verifyFlatRecordScope( - recordsWriteMessage: RecordsWriteMessage, - grantScope: RecordsPermissionScope - ): void { - if (grantScope.schema !== undefined) { - if (grantScope.schema !== recordsWriteMessage.descriptor.schema) { - throw new DwnError( - DwnErrorCode.RecordsGrantAuthorizationScopeSchema, - `Record does not have schema in permission grant scope with schema '${grantScope.schema}'` - ); - } - } - } - /** * Verifies grant `conditions`. * Currently the only condition is `published` which only applies to RecordsWrites @@ -236,13 +194,4 @@ export class RecordsGrantAuthorization { ); } } - - /** - * Checks if scope has no restrictions beyond interface and method. - * Grant-holder is authorized to access any record. - */ - private static isUnrestrictedScope(grantScope: RecordsPermissionScope): boolean { - return grantScope.protocol === undefined && - grantScope.schema === undefined; - } } diff --git a/src/protocols/permissions.ts b/src/protocols/permissions.ts index 3cd482716..7d203277d 100644 --- a/src/protocols/permissions.ts +++ b/src/protocols/permissions.ts @@ -12,7 +12,7 @@ import { Time } from '../utils/time.js'; import { validateJsonSchema } from '../schema-validator.js'; import { DwnError, DwnErrorCode } from '../core/dwn-error.js'; import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js'; -import { normalizeProtocolUrl, normalizeSchemaUrl } from '../utils/url.js'; +import { normalizeProtocolUrl, validateProtocolUrlNormalized } from '../utils/url.js'; /** * Options for creating a permission request. @@ -199,6 +199,14 @@ export class PermissionsProtocol { permissionGrantBytes: Uint8Array, dataEncodedMessage: DataEncodedRecordsWriteMessage, }> { + + if (this.isRecordPermissionScope(options.scope) && options.scope.protocol === undefined) { + throw new DwnError( + DwnErrorCode.PermissionsProtocolCreateGrantRecordsScopeMissingProtocol, + 'Permission grants for Records must have a scope with a `protocol` property' + ); + } + const scope = PermissionsProtocol.normalizePermissionScope(options.scope); const permissionGrantData: PermissionGrantData = { @@ -210,6 +218,13 @@ export class PermissionsProtocol { conditions : options.conditions, }; + let permissionTags = undefined; + if (this.isRecordPermissionScope(scope)) { + permissionTags = { + protocol: scope.protocol + }; + } + const permissionGrantBytes = Encoder.objectToBytes(permissionGrantData); const recordsWrite = await RecordsWrite.create({ signer : options.signer, @@ -220,6 +235,7 @@ export class PermissionsProtocol { protocolPath : PermissionsProtocol.grantPath, dataFormat : 'application/json', data : permissionGrantBytes, + tags : permissionTags, }); const dataEncodedMessage: DataEncodedRecordsWriteMessage = { @@ -277,12 +293,12 @@ export class PermissionsProtocol { // more nuanced validation that are annoying/difficult to do using JSON schema const permissionGrantData = dataObject as PermissionGrantData; - PermissionsProtocol.validateScope(permissionGrantData.scope); + PermissionsProtocol.validateScope(permissionGrantData.scope, recordsWriteMessage); Time.validateTimestamp(permissionGrantData.dateExpires); } else if (recordsWriteMessage.descriptor.protocolPath === PermissionsProtocol.revocationPath) { validateJsonSchema('PermissionRevocationData', dataObject); } else { - // defensive programming, should be unreachable externally + // defensive programming, should not be unreachable externally throw new DwnError( DwnErrorCode.PermissionsProtocolValidateSchemaUnexpectedRecord, `Unexpected permission record: ${recordsWriteMessage.descriptor.protocolPath}` @@ -336,13 +352,7 @@ export class PermissionsProtocol { const scope = { ...permissionScope }; if (PermissionsProtocol.isRecordPermissionScope(scope)) { - // normalize protocol and schema URLs if they are present - if (scope.protocol !== undefined) { - scope.protocol = normalizeProtocolUrl(scope.protocol); - } - if (scope.schema !== undefined) { - scope.schema = normalizeSchemaUrl(scope.schema); - } + scope.protocol = normalizeProtocolUrl(scope.protocol); } return scope; @@ -355,34 +365,38 @@ export class PermissionsProtocol { return scope.interface === 'Records'; } - /** * Validates scope. */ - private static validateScope(scope: PermissionScope): void { + private static validateScope(scope: PermissionScope, grantRecord: RecordsWriteMessage): void { if (!this.isRecordPermissionScope(scope)) { return; } + // else we are dealing with a RecordsPermissionScope - // `schema` scopes may not have protocol-related fields - if (scope.schema !== undefined) { - if (scope.protocol !== undefined || scope.contextId !== undefined || scope.protocolPath) { - throw new DwnError( - DwnErrorCode.PermissionsProtocolValidateScopeSchemaProhibitedProperties, - 'Permission grants that have `schema` present cannot also have protocol-related properties present' - ); - } + if (grantRecord.descriptor.tags === undefined || grantRecord.descriptor.tags.protocol === undefined) { + throw new DwnError( + DwnErrorCode.PermissionsProtocolValidateScopeMissingProtocolTag, + 'Permission grants must have a `tags` property that contains a protocol tag' + ); } + const taggedProtocol = grantRecord.descriptor.tags.protocol as string; + validateProtocolUrlNormalized(taggedProtocol); - if (scope.protocol !== undefined) { - // `contextId` and `protocolPath` are mutually exclusive - if (scope.contextId !== undefined && scope.protocolPath !== undefined) { - throw new DwnError( - DwnErrorCode.PermissionsProtocolValidateScopeContextIdProhibitedProperties, - 'Permission grants cannot have both `contextId` and `protocolPath` present' - ); - } + if (scope.protocol !== taggedProtocol) { + throw new DwnError( + DwnErrorCode.PermissionsProtocolValidateScopeProtocolMismatch, + `Permission grants must have a scope with a protocol that matches the tagged protocol: ${taggedProtocol}` + ); + } + + // `contextId` and `protocolPath` are mutually exclusive + if (scope.contextId !== undefined && scope.protocolPath !== undefined) { + throw new DwnError( + DwnErrorCode.PermissionsProtocolValidateScopeContextIdProhibitedProperties, + 'Permission grants cannot have both `contextId` and `protocolPath` present' + ); } } }; \ No newline at end of file diff --git a/src/types/permission-types.ts b/src/types/permission-types.ts index 37c8e1d04..1e7adc055 100644 --- a/src/types/permission-types.ts +++ b/src/types/permission-types.ts @@ -68,10 +68,22 @@ export type PermissionRevocationData = { /** * The data model for a permission scope. */ -export type PermissionScope = { - interface: DwnInterfaceName; - method: DwnMethodName; -} | RecordsPermissionScope; +export type PermissionScope = ProtocolPermissionScope | MessagesPermissionScope | EventsPermissionScope | RecordsPermissionScope; + +export type ProtocolPermissionScope = { + interface: DwnInterfaceName.Protocols; + method: DwnMethodName.Configure | DwnMethodName.Query; +}; + +export type MessagesPermissionScope = { + interface: DwnInterfaceName.Messages; + method: DwnMethodName.Get; +}; + +export type EventsPermissionScope = { + interface: DwnInterfaceName.Events; + method: DwnMethodName.Get | DwnMethodName.Query | DwnMethodName.Subscribe; +}; /** * The data model for a permission scope that is specific to the Records interface. @@ -79,14 +91,11 @@ export type PermissionScope = { export type RecordsPermissionScope = { interface: DwnInterfaceName.Records; method: DwnMethodName.Read | DwnMethodName.Write | DwnMethodName.Query | DwnMethodName.Subscribe | DwnMethodName.Delete; - /** May only be present when `schema` is undefined */ - protocol?: string; + protocol: string; /** May only be present when `protocol` is defined and `protocolPath` is undefined */ contextId?: string; /** May only be present when `protocol` is defined and `contextId` is undefined */ protocolPath?: string; - /** May only be present when `protocol` is undefined */ - schema?: string; }; export enum PermissionConditionPublication { diff --git a/tests/features/permissions.spec.ts b/tests/features/permissions.spec.ts index 1efce539d..6f8e2009c 100644 --- a/tests/features/permissions.spec.ts +++ b/tests/features/permissions.spec.ts @@ -16,7 +16,7 @@ import { TestDataGenerator } from '../utils/test-data-generator.js'; import { TestEventStream } from '../test-event-stream.js'; import { TestStores } from '../test-stores.js'; import { DidKey, UniversalResolver } from '@web5/dids'; -import { DwnErrorCode, DwnInterfaceName, DwnMethodName, Encoder, RecordsQuery, RecordsWrite, Time } from '../../src/index.js'; +import { DwnErrorCode, DwnInterfaceName, DwnMethodName, Encoder, RecordsQuery, Time } from '../../src/index.js'; chai.use(chaiAsPromised); @@ -56,6 +56,7 @@ export function testPermissions(): void { }); after(async () => { + sinon.restore(); await dwn.close(); }); @@ -207,216 +208,167 @@ export function testPermissions(): void { expect(revocationReadReply2.record?.recordId).to.equal(revokeWrite.recordsWrite.message.recordId); }); - describe('schema validation', () => { - it('should reject with 400 if a permission message fails schema validation', async () => { - // Scenario: - // 1. Verify that a permission request is rejected with the data payload is invalid - // 2. Verify that a permission grant is rejected with the data payload is invalid - // 3. Write a valid permission grant - // 4. Verify that a permission revocation is rejected with the data payload is invalid - // 5. Verify that an unexpected/unknown permission record is rejected + it('should fail if a RecordsPermissionScope is provided without a protocol', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); + const bob = await TestDataGenerator.generateDidKeyPersona(); - const alice = await TestDataGenerator.generateDidKeyPersona(); - const bob = await TestDataGenerator.generateDidKeyPersona(); + const permissionScope = { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Write + }; - // 1. Verify that a permission request is rejected with the data payload is invalid - const invalidPermissionRequestData = { - description: 'missing required properties such as `scope`' - }; + const grantWrite = PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Allow Bob to write', + grantedTo : bob.did, + scope : permissionScope as any // explicity as any to test the validation + }); + expect(grantWrite).to.eventually.be.rejectedWith(DwnErrorCode.PermissionsProtocolCreateGrantRecordsScopeMissingProtocol); + }); - const requestBytes = Encoder.objectToBytes(invalidPermissionRequestData); - const requestRecordsWrite = await RecordsWrite.create({ - signer : Jws.createSigner(bob), - protocol : PermissionsProtocol.uri, - protocolPath : PermissionsProtocol.requestPath, - dataFormat : 'application/json', - data : requestBytes, - }); - const requestWriteReply = await dwn.processMessage( - alice.did, - requestRecordsWrite.message, - { dataStream: DataStream.fromBytes(requestBytes) } - ); + it('should fail if an invalid protocolPath is used during Permissions schema validation', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); + + const { message, dataBytes } = await TestDataGenerator.generateRecordsWrite({ + author : alice, + protocol : PermissionsProtocol.uri, + protocolPath : 'invalid/path', + data : Encoder.stringToBytes(JSON.stringify({})) + }); - expect(requestWriteReply.status.code).to.equal(400); - expect(requestWriteReply.status.detail).to.contain(DwnErrorCode.SchemaValidatorFailure); + try { + PermissionsProtocol.validateSchema(message, dataBytes!); + expect.fail('Expected to throw'); + } catch (error:any) { + expect(error.message).to.include(DwnErrorCode.PermissionsProtocolValidateSchemaUnexpectedRecord); + expect(error.message).to.include('invalid/path'); + } + }); - // 2. Verify that a permission grant is rejected with the data payload is invalid - const invalidPermissionGrantData = { - description: 'missing required properties such as `scope`' - }; + describe('validateScope', async () => { + it('should be called for a Grant record', async () => { + // spy on `validateScope` + const validateScopeSpy = sinon.spy(PermissionsProtocol as any, 'validateScope'); - const grantBytes = Encoder.objectToBytes(invalidPermissionGrantData); - const grantRecordsWrite = await RecordsWrite.create({ - signer : Jws.createSigner(alice), - recipient : bob.did, - protocol : PermissionsProtocol.uri, - protocolPath : PermissionsProtocol.grantPath, - dataFormat : 'application/json', - data : grantBytes, - }); - const invalidGrantWriteReply = await dwn.processMessage( - alice.did, - grantRecordsWrite.message, - { dataStream: DataStream.fromBytes(grantBytes) } - ); + const alice = await TestDataGenerator.generateDidKeyPersona(); + const bob = await TestDataGenerator.generateDidKeyPersona(); - expect(invalidGrantWriteReply.status.code).to.equal(400); - expect(invalidGrantWriteReply.status.detail).to.contain(DwnErrorCode.SchemaValidatorFailure); + const permissionScope: PermissionScope = { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Write, + protocol : 'https://example.com/protocol/test' + }; - // 3. Write a valid permission grant - const grantWrite = await PermissionsProtocol.createGrant({ + // create a grant + const grantedToBob = await PermissionsProtocol.createGrant({ signer : Jws.createSigner(alice), dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), description : 'Allow Bob to write', grantedTo : bob.did, - scope : { interface: DwnInterfaceName.Records, method: DwnMethodName.Write } + scope : permissionScope }); const grantWriteReply = await dwn.processMessage( alice.did, - grantWrite.recordsWrite.message, - { dataStream: DataStream.fromBytes(grantWrite.permissionGrantBytes) } + grantedToBob.recordsWrite.message, + { dataStream: DataStream.fromBytes(grantedToBob.permissionGrantBytes) } ); expect(grantWriteReply.status.code).to.equal(202); + expect(validateScopeSpy.calledOnce).to.be.true; + }); - // 4. Verify that a permission revocation is rejected with the data payload is invalid - const invalidPermissionRevocationData = { - unknownProperty: 'unknown property', - }; - - const revocationBytes = Encoder.objectToBytes(invalidPermissionRevocationData); - const revocationRecordsWrite = await RecordsWrite.create({ - signer : Jws.createSigner(alice), - parentContextId : grantWrite.recordsWrite.message.recordId, - protocol : PermissionsProtocol.uri, - protocolPath : PermissionsProtocol.revocationPath, - dataFormat : 'application/json', - data : revocationBytes, - }); - const revokeWriteReply = await dwn.processMessage( - alice.did, - revocationRecordsWrite.message, - { dataStream: DataStream.fromBytes(revocationBytes) } - ); - - expect(revokeWriteReply.status.code).to.equal(400); - expect(revokeWriteReply.status.detail).to.contain(DwnErrorCode.SchemaValidatorAdditionalPropertyNotAllowed); + it('should throw if the scope is a RecordsPermissionScope and a protocol tag is not defined on the grant record', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); - // 5. Verify that an unexpected/unknown permission record is rejected - const unknownPermissionRecordData = { - unknownProperty: 'unknown property', + // create a permission grant without a protocol tag + const permissionScope: PermissionScope = { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Write, + protocol : 'https://example.com/protocol/test' }; - const unknownRecordBytes = Encoder.objectToBytes(unknownPermissionRecordData); - const unknownRecordsWrite = await RecordsWrite.create({ - signer : Jws.createSigner(alice), + const grantRecordsWrite = await TestDataGenerator.generateRecordsWrite({ + author : alice, protocol : PermissionsProtocol.uri, - protocolPath : 'unknown-path', - dataFormat : 'application/json', - data : revocationBytes, + protocolPath : PermissionsProtocol.grantPath, + data : Encoder.stringToBytes(JSON.stringify({})), + tags : { someTag: 'someValue' } // not a protocol tag }); - expect(() => PermissionsProtocol.validateSchema(unknownRecordsWrite.message, unknownRecordBytes)) - .to.throw(DwnErrorCode.PermissionsProtocolValidateSchemaUnexpectedRecord); + expect( + () => PermissionsProtocol['validateScope'](permissionScope, grantRecordsWrite.message) + ).to.throw(DwnErrorCode.PermissionsProtocolValidateScopeMissingProtocolTag); }); - it('ensures that `schema` and protocol related fields `protocol`, `contextId` or `protocolPath` are not both present', async () => { + it('should throw if the scope is a RecordsPermissionScope and the grant record has no tags', async () => { const alice = await TestDataGenerator.generateDidKeyPersona(); - const bob = await TestDataGenerator.generateDidKeyPersona(); - // `schema` and `protocol` may not both be present in grant `scope` - const schemaAndProtocolGrant = await PermissionsProtocol.createGrant({ - signer : Jws.createSigner(alice), - dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), - description : 'Allow Bob to write', - grantedTo : bob.did, - scope : { - interface : DwnInterfaceName.Records, - method : DwnMethodName.Write, - schema : 'some-schema', - protocol : 'some-protocol' - } + // create a permission grant without a protocol tag + const permissionScope: PermissionScope = { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Write, + protocol : 'https://example.com/protocol/test' + }; + + const grantRecordsWrite = await TestDataGenerator.generateRecordsWrite({ + author : alice, + protocol : PermissionsProtocol.uri, + protocolPath : PermissionsProtocol.grantPath, + data : Encoder.stringToBytes(JSON.stringify({})), }); - const schemaAndProtocolGrantReply = await dwn.processMessage( - alice.did, - schemaAndProtocolGrant.recordsWrite.message, - { dataStream: DataStream.fromBytes(schemaAndProtocolGrant.permissionGrantBytes) } - ); - expect(schemaAndProtocolGrantReply.status.code).to.eq(400); - expect(schemaAndProtocolGrantReply.status.detail).to.contain(DwnErrorCode.PermissionsProtocolValidateScopeSchemaProhibitedProperties); + expect( + () => PermissionsProtocol['validateScope'](permissionScope, grantRecordsWrite.message) + ).to.throw(DwnErrorCode.PermissionsProtocolValidateScopeMissingProtocolTag); + }); - // `schema` and `contextId` may not both be present in grant `scope` - const schemaAndContextIdGrant = await PermissionsProtocol.createGrant({ - signer : Jws.createSigner(alice), - dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), - description : 'Allow Bob to write', - grantedTo : bob.did, - scope : { - interface : DwnInterfaceName.Records, - method : DwnMethodName.Write, - schema : 'some-schema', - contextId : 'some-context-id' - } - }); + it('should throw if the protocol tag in the grant record does not match the protocol defined in the scope', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); - const schemaAndContextIdGrantReply = await dwn.processMessage( - alice.did, - schemaAndContextIdGrant.recordsWrite.message, - { dataStream: DataStream.fromBytes(schemaAndContextIdGrant.permissionGrantBytes) } - ); - expect(schemaAndContextIdGrantReply.status.code).to.eq(400); - expect(schemaAndContextIdGrantReply.status.detail).to.contain(DwnErrorCode.PermissionsProtocolValidateScopeSchemaProhibitedProperties); + // create a permission grant with a protocol tag that does not match the scope + const permissionScope: PermissionScope = { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Write, + protocol : 'https://example.com/protocol/test' + }; - // `schema` and `protocolPath` may not both be present in grant `scope` - const schemaAndProtocolPathGrant = await PermissionsProtocol.createGrant({ - signer : Jws.createSigner(alice), - dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), - description : 'Allow Bob to write', - grantedTo : bob.did, - scope : { - interface : DwnInterfaceName.Records, - method : DwnMethodName.Write, - schema : 'some-schema', - protocolPath : 'some-protocol-path' - } + const grantRecordsWrite = await TestDataGenerator.generateRecordsWrite({ + author : alice, + protocol : PermissionsProtocol.uri, + protocolPath : PermissionsProtocol.grantPath, + data : Encoder.stringToBytes(JSON.stringify({ })), + tags : { protocol: 'https://example.com/protocol/invalid' } }); - const schemaAndProtocolPathGrantReply = await dwn.processMessage( - alice.did, - schemaAndProtocolPathGrant.recordsWrite.message, - { dataStream: DataStream.fromBytes(schemaAndProtocolPathGrant.permissionGrantBytes) } - ); - expect(schemaAndProtocolPathGrantReply.status.code).to.eq(400); - expect(schemaAndProtocolPathGrantReply.status.detail).to.contain(DwnErrorCode.PermissionsProtocolValidateScopeSchemaProhibitedProperties); + expect( + () => PermissionsProtocol['validateScope'](permissionScope, grantRecordsWrite.message) + ).to.throw(DwnErrorCode.PermissionsProtocolValidateScopeProtocolMismatch); }); - it('ensures that `contextId` and `protocolPath` are not both present in grant scope', async () => { + it('should throw if protocolPath and contextId are both defined in the scope', async () => { const alice = await TestDataGenerator.generateDidKeyPersona(); - const bob = await TestDataGenerator.generateDidKeyPersona(); - const grant = await PermissionsProtocol.createGrant({ - signer : Jws.createSigner(alice), - dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), - description : 'Allow Bob to write', - grantedTo : bob.did, - scope : { - interface : DwnInterfaceName.Records, - method : DwnMethodName.Write, - protocol : 'some-protocol', - contextId : 'some-context-id', - protocolPath : 'some-protocol-path' - } + // create a permission grant with a protocol tag that does not match the scope + const permissionScope: PermissionScope = { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Write, + protocol : 'https://example.com/protocol/test', + protocolPath : 'test/path', + contextId : 'test-context' + }; + + const grantRecordsWrite = await TestDataGenerator.generateRecordsWrite({ + author : alice, + protocol : PermissionsProtocol.uri, + protocolPath : PermissionsProtocol.grantPath, + data : Encoder.stringToBytes(JSON.stringify({ })), + tags : { protocol: 'https://example.com/protocol/test' } }); - const schemaAndProtocolGrantReply = await dwn.processMessage( - alice.did, - grant.recordsWrite.message, - { dataStream: DataStream.fromBytes(grant.permissionGrantBytes) } - ); - expect(schemaAndProtocolGrantReply.status.code).to.eq(400); - expect(schemaAndProtocolGrantReply.status.detail).to.contain(DwnErrorCode.PermissionsProtocolValidateScopeContextIdProhibitedProperties); + expect( + () => PermissionsProtocol['validateScope'](permissionScope, grantRecordsWrite.message) + ).to.throw(DwnErrorCode.PermissionsProtocolValidateScopeContextIdProhibitedProperties); }); }); }); diff --git a/tests/handlers/protocols-query.spec.ts b/tests/handlers/protocols-query.spec.ts index f6f011177..e53bdda82 100644 --- a/tests/handlers/protocols-query.spec.ts +++ b/tests/handlers/protocols-query.spec.ts @@ -365,7 +365,7 @@ export function testProtocolsQueryHandler(): void { signer : Jws.createSigner(alice), grantedTo : bob.did, dateExpires : Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }), - scope : { interface: DwnInterfaceName.Records, method: DwnMethodName.Read } + scope : { interface: DwnInterfaceName.Records, method: DwnMethodName.Read, protocol: 'https://example.com/protocol/test' } }); const dataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes); diff --git a/tests/handlers/records-read.spec.ts b/tests/handlers/records-read.spec.ts index 6a07f3ac8..7bddd5f98 100644 --- a/tests/handlers/records-read.spec.ts +++ b/tests/handlers/records-read.spec.ts @@ -815,6 +815,7 @@ export function testRecordsReadHandler(): void { scope : { interface : DwnInterfaceName.Records, method : DwnMethodName.Write, + protocol : 'http://example.com/protocol/test', } }); const grantDataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes); @@ -838,47 +839,6 @@ export function testRecordsReadHandler(): void { expect(recordsReadReply.status.detail).to.contain(DwnErrorCode.GrantAuthorizationMethodMismatch); }); - it('allows external parties to read a record using a grant with unrestricted RecordsRead scope', async () => { - // scenario: Alice gives Bob a grant allowing him to read any record in her DWN. - // Bob invokes that grant to read a record. - - const alice = await TestDataGenerator.generateDidKeyPersona(); - const bob = await TestDataGenerator.generateDidKeyPersona(); - - // Alice writes a record to her DWN - const { message, dataStream } = await TestDataGenerator.generateRecordsWrite({ - author: alice, - }); - const writeReply = await dwn.processMessage(alice.did, message, { dataStream }); - expect(writeReply.status.code).to.equal(202); - - // Alice issues a permission grant allowing Bob to read any record in her DWN - const permissionGrant = await PermissionsProtocol.createGrant({ - signer : Jws.createSigner(alice), - grantedTo : bob.did, - dateExpires : Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }), // 24 hours - scope : { - interface : DwnInterfaceName.Records, - method : DwnMethodName.Read, - // No further restrictions on grant scope - } - }); - const grantDataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes); - const grantReply = await dwn.processMessage(alice.did, permissionGrant.recordsWrite.message, { dataStream: grantDataStream }); - expect(grantReply.status.code).to.equal(202); - - // Bob invokes that grant to read a record from Alice's DWN - const recordsRead = await RecordsRead.create({ - filter: { - recordId: message.recordId, - }, - signer : Jws.createSigner(bob), - permissionGrantId : permissionGrant.recordsWrite.message.recordId, - }); - const readReply = await dwn.processMessage(alice.did, recordsRead.message); - expect(readReply.status.code).to.equal(200); - }); - describe('protocol records', () => { it('allows reads of protocol records with unrestricted grant scopes', async () => { // scenario: Alice writes a protocol record. Alice gives Bob a grant to read all records in her DWN @@ -914,6 +874,7 @@ export function testRecordsReadHandler(): void { scope : { interface : DwnInterfaceName.Records, method : DwnMethodName.Read, + protocol : protocolDefinition.protocol, } }); const grantDataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes); @@ -1074,64 +1035,6 @@ export function testRecordsReadHandler(): void { expect(recordsReadWithoutGrantReply.status.detail).to.contain(DwnErrorCode.RecordsGrantAuthorizationScopeProtocolMismatch); }); - it('rejects reads of protocol records with non-protocol grant scopes', async () => { - // scenario: Alice writes a protocol record. Alice gives Bob a grant to read a records of a certain schema. - // Bob invokes that grant to read the protocol record, but fails. - - const alice = await TestDataGenerator.generateDidKeyPersona(); - const bob = await TestDataGenerator.generateDidKeyPersona(); - - const protocolDefinition = minimalProtocolDefinition; - - // Alice installs the protocol - const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ - author: alice, - protocolDefinition - }); - const protocolsConfigureReply = await dwn.processMessage(alice.did, protocolsConfig.message); - expect(protocolsConfigureReply.status.code).to.equal(202); - - // Alice writes a record which Bob will later try to read - const { recordsWrite, dataStream } = await TestDataGenerator.generateRecordsWrite({ - author : alice, - protocol : protocolDefinition.protocol, - protocolPath : 'foo', - }); - const recordsWriteReply = await dwn.processMessage(alice.did, recordsWrite.message, { dataStream }); - expect(recordsWriteReply.status.code).to.equal(202); - - // Alice gives Bob a permission grant with scope RecordsRead - const permissionGrant = await PermissionsProtocol.createGrant({ - signer : Jws.createSigner(alice), - grantedTo : bob.did, - dateExpires : Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }), // 24 hours - scope : { - interface : DwnInterfaceName.Records, - method : DwnMethodName.Read, - schema : 'some-schema' - } - }); - const grantDataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes); - const permissionGrantWriteReply = await dwn.processMessage( - alice.did, - permissionGrant.recordsWrite.message, - { dataStream: grantDataStream } - ); - expect(permissionGrantWriteReply.status.code).to.equal(202); - - // Bob is unable to read the record using the mismatched permission grant - const recordsReadWithoutGrant = await RecordsRead.create({ - filter: { - recordId: recordsWrite.message.recordId, - }, - signer : Jws.createSigner(bob), - permissionGrantId : permissionGrant.recordsWrite.message.recordId, - }); - const recordsReadWithoutGrantReply = await dwn.processMessage(alice.did, recordsReadWithoutGrant.message); - expect(recordsReadWithoutGrantReply.status.code).to.equal(401); - expect(recordsReadWithoutGrantReply.status.detail).to.contain(DwnErrorCode.RecordsGrantAuthorizationScopeMissingProtocol); - }); - it('allows reads of records in the contextId specified in the grant', async () => { // scenario: Alice grants Bob access to RecordsRead records with a specific contextId. // Bob uses it to read a record in that context. @@ -1362,95 +1265,6 @@ export function testRecordsReadHandler(): void { expect(recordsReadWithoutGrantReply.status.detail).to.contain(DwnErrorCode.RecordsGrantAuthorizationScopeProtocolPathMismatch); }); }); - - describe('grant scope schema', () => { - it('allows access if the RecordsRead grant scope schema includes the schema of the record', async () => { - // scenario: Alice gives Bob a grant allowing him to read records with matching schema in her DWN. - // Bob invokes that grant to read a record. - - const alice = await TestDataGenerator.generateDidKeyPersona(); - const bob = await TestDataGenerator.generateDidKeyPersona(); - - // Alice writes a record to her DWN - const { message, dataStream } = await TestDataGenerator.generateRecordsWrite({ - author : alice, - schema : 'some-schema', - }); - const writeReply = await dwn.processMessage(alice.did, message, { dataStream }); - expect(writeReply.status.code).to.equal(202); - - // Alice issues a permission grant allowing Bob to read a specific recordId - const permissionGrant = await PermissionsProtocol.createGrant({ - signer : Jws.createSigner(alice), - grantedTo : bob.did, - dateExpires : Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }), // 24 hours - scope : { - interface : DwnInterfaceName.Records, - method : DwnMethodName.Read, - schema : message.descriptor.schema - } - }); - const grantDataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes); - const grantReply = await dwn.processMessage(alice.did, permissionGrant.recordsWrite.message, { dataStream: grantDataStream }); - expect(grantReply.status.code).to.equal(202); - - // Bob invokes that grant to read a record from Alice's DWN - const recordsRead = await RecordsRead.create({ - filter: { - recordId: message.recordId, - }, - signer : Jws.createSigner(bob), - permissionGrantId : permissionGrant.recordsWrite.message.recordId, - }); - const readReply = await dwn.processMessage(alice.did, recordsRead.message); - expect(readReply.status.code).to.equal(200); - }); - - it('rejects with 401 if the RecordsRead grant scope schema does not have the same schema as the record', async () => { - // scenario: Alice gives Bob a grant allowing him to read records with matching schema in her DWN. - // Bob invokes that grant to read a different record and is rejected. - - const alice = await TestDataGenerator.generateDidKeyPersona(); - const bob = await TestDataGenerator.generateDidKeyPersona(); - - // Alice writes a record to her DWN - const recordSchema = 'record-schema'; - const { message, dataStream } = await TestDataGenerator.generateRecordsWrite({ - author : alice, - schema : recordSchema, - }); - const writeReply = await dwn.processMessage(alice.did, message, { dataStream }); - expect(writeReply.status.code).to.equal(202); - - // Alice issues a permission grant allowing Bob to read a specific recordId - const scopeSchema = 'scope-schema'; - const permissionGrant = await PermissionsProtocol.createGrant({ - signer : Jws.createSigner(alice), - grantedTo : bob.did, - dateExpires : Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }), // 24 hours - scope : { - interface : DwnInterfaceName.Records, - method : DwnMethodName.Read, - schema : scopeSchema // different schema than the record Bob will try to read - } - }); - const grantDataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes); - const grantReply = await dwn.processMessage(alice.did, permissionGrant.recordsWrite.message, { dataStream: grantDataStream }); - expect(grantReply.status.code).to.equal(202); - - // Bob invokes that grant to read a record from Alice's DWN - const recordsRead = await RecordsRead.create({ - filter: { - recordId: message.recordId, - }, - signer : Jws.createSigner(bob), - permissionGrantId : permissionGrant.recordsWrite.message.recordId, - }); - const readReply = await dwn.processMessage(alice.did, recordsRead.message); - expect(readReply.status.code).to.equal(401); - expect(readReply.status.detail).to.include(DwnErrorCode.RecordsGrantAuthorizationScopeSchema); - }); - }); }); it('should return 404 RecordRead if data does not exist', async () => { diff --git a/tests/handlers/records-write.spec.ts b/tests/handlers/records-write.spec.ts index f1f58fc0a..4608d9e7c 100644 --- a/tests/handlers/records-write.spec.ts +++ b/tests/handlers/records-write.spec.ts @@ -3503,57 +3503,6 @@ export function testRecordsWriteHandler(): void { }); describe('grant based writes', () => { - it('allows external parties to write a record using a grant with unrestricted RecordsWrite scope', async () => { - // scenario: Alice gives Bob a grant with unrestricted RecordsWrite scope. - // Bob is able to write both a protocol and a non-protocol record. - - const alice = await TestDataGenerator.generateDidKeyPersona(); - const bob = await TestDataGenerator.generateDidKeyPersona(); - - const protocolDefinition = minimalProtocolDefinition; - - // Alice installs the protocol - const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ - author: alice, - protocolDefinition - }); - const protocolsConfigureReply = await dwn.processMessage(alice.did, protocolsConfig.message); - expect(protocolsConfigureReply.status.code).to.equal(202); - - // Alice issues Bob a permission grant for unrestricted RecordsWrite access - const permissionGrant = await PermissionsProtocol.createGrant({ - signer : Jws.createSigner(alice), - grantedTo : bob.did, - dateExpires : Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }), - scope : { interface: DwnInterfaceName.Records, method: DwnMethodName.Write } - }); - const grantDataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes); - - const grantRecordsWriteReply = await dwn.processMessage(alice.did, permissionGrant.recordsWrite.message, { dataStream: grantDataStream }); - expect(grantRecordsWriteReply.status.code).to.equal(202); - - // Bob invokes the grant to write a protocol record to Alice's DWN - const permissionGrantId = permissionGrant.recordsWrite.message.recordId; - const protocolRecordsWrite = await TestDataGenerator.generateRecordsWrite({ - author : bob, - protocol : protocolDefinition.protocol, - protocolPath : 'foo', - permissionGrantId, - }); - const recordsWriteReply = - await dwn.processMessage(alice.did, protocolRecordsWrite.message, { dataStream: protocolRecordsWrite.dataStream }); - expect(recordsWriteReply.status.code).to.equal(202); - - // Bob writes a non-protocol record to Alice's DWN - const nonProtocolRecordsWrite = await TestDataGenerator.generateRecordsWrite({ - author: bob, - permissionGrantId, - }); - const recordsWriteReply2 = - await dwn.processMessage(alice.did, nonProtocolRecordsWrite.message, { dataStream: nonProtocolRecordsWrite.dataStream }); - expect(recordsWriteReply2.status.code).to.equal(202); - }); - describe('protocol records', () => { it('allows writes of protocol records with matching protocol grant scopes', async () => { // scenario: Alice gives Bob a grant to read all records in the protocol @@ -3650,54 +3599,6 @@ export function testRecordsWriteHandler(): void { expect(recordsWriteReply.status.detail).to.contain(DwnErrorCode.RecordsGrantAuthorizationScopeProtocolMismatch); }); - it('rejects writes of protocol records with non-protocol grant scopes', async () => { - // scenario: Alice issues Bob a grant allowing him to write some non-protocol records. - // Bob invokes the grant to write a protocol record - - const alice = await TestDataGenerator.generateDidKeyPersona(); - const bob = await TestDataGenerator.generateDidKeyPersona(); - - const protocolDefinition = minimalProtocolDefinition; - - // Alice installs the protocol - const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ - author: alice, - protocolDefinition - }); - const protocolsConfigureReply = await dwn.processMessage(alice.did, protocolsConfig.message); - expect(protocolsConfigureReply.status.code).to.equal(202); - - // Alice gives Bob a permission grant with a non-protocol scope - const permissionGrant = await PermissionsProtocol.createGrant({ - signer : Jws.createSigner(alice), - grantedTo : bob.did, - dateExpires : Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }), // 24 hours - scope : { - interface : DwnInterfaceName.Records, - method : DwnMethodName.Write, - schema : 'some-schema', - } - }); - const grantDataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes); - const permissionGrantWriteReply = await dwn.processMessage( - alice.did, - permissionGrant.recordsWrite.message, - { dataStream: grantDataStream } - ); - expect(permissionGrantWriteReply.status.code).to.equal(202); - - // Bob invokes the grant, failing to write to a different protocol than the grant allows - const { recordsWrite, dataStream } = await TestDataGenerator.generateRecordsWrite({ - author : bob, - protocol : protocolDefinition.protocol, - protocolPath : 'foo', - permissionGrantId : permissionGrant.recordsWrite.message.recordId, - }); - const recordsWriteReply = await dwn.processMessage(alice.did, recordsWrite.message, { dataStream }); - expect(recordsWriteReply.status.code).to.equal(401); - expect(recordsWriteReply.status.detail).to.contain(DwnErrorCode.RecordsGrantAuthorizationScopeMissingProtocol); - }); - it('allows writes of protocol records with matching contextId grant scopes', async () => { // scenario: Alice gives Bob a grant to write to a specific contextId. // Bob invokes that grant to write a record in the allowed contextId. @@ -3925,83 +3826,6 @@ export function testRecordsWriteHandler(): void { }); }); - describe('grant scope schema', () => { - it('allows access if the RecordsWrite grant scope schema includes the schema of the record', async () => { - // scenario: Alice issues Bob a grant allowing him to write to flat records of a given schema. - // Bob invokes that grant to write a record with matching schema - - const alice = await TestDataGenerator.generateDidKeyPersona(); - const bob = await TestDataGenerator.generateDidKeyPersona(); - - // Alice gives Bob a permission grant for a certain schema - const schema = 'http://example.com/schema'; - const permissionGrant = await PermissionsProtocol.createGrant({ - signer : Jws.createSigner(alice), - grantedTo : bob.did, - dateExpires : Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }), // 24 hours - scope : { - interface : DwnInterfaceName.Records, - method : DwnMethodName.Write, - schema, - } - }); - const grantDataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes); - const permissionGrantWriteReply = await dwn.processMessage( - alice.did, - permissionGrant.recordsWrite.message, - { dataStream: grantDataStream } - ); - expect(permissionGrantWriteReply.status.code).to.equal(202); - - // Bob invokes the grant to write a record - const { recordsWrite, dataStream } = await TestDataGenerator.generateRecordsWrite({ - author : bob, - schema, - permissionGrantId : permissionGrant.recordsWrite.message.recordId, - }); - const recordsWriteReply = await dwn.processMessage(alice.did, recordsWrite.message, { dataStream }); - expect(recordsWriteReply.status.code).to.equal(202); - }); - - it('rejects with 401 if RecordsWrite grant scope schema does not have the same schema as the record', async () => { - // scenario: Alice issues a grant for Bob to write flat records of a certain schema. - // Bob tries and fails to write records of a different schema - - const alice = await TestDataGenerator.generateDidKeyPersona(); - const bob = await TestDataGenerator.generateDidKeyPersona(); - - - // Alice gives Bob a permission grant for a certain schema - const permissionGrant = await PermissionsProtocol.createGrant({ - signer : Jws.createSigner(alice), - grantedTo : bob.did, - dateExpires : Time.createOffsetTimestamp({ seconds: 60 * 60 * 24 }), // 24 hours - scope : { - interface : DwnInterfaceName.Records, - method : DwnMethodName.Write, - schema : 'some-schema', - } - }); - const grantDataStream = DataStream.fromBytes(permissionGrant.permissionGrantBytes); - const permissionGrantWriteReply = await dwn.processMessage( - alice.did, - permissionGrant.recordsWrite.message, - { dataStream: grantDataStream } - ); - expect(permissionGrantWriteReply.status.code).to.equal(202); - - // Bob invokes the grant, failing write a record - const { recordsWrite, dataStream } = await TestDataGenerator.generateRecordsWrite({ - author : bob, - schema : 'some-other-schema', - permissionGrantId : permissionGrant.recordsWrite.message.recordId, - }); - const recordsWriteReply = await dwn.processMessage(alice.did, recordsWrite.message, { dataStream }); - expect(recordsWriteReply.status.code).to.equal(401); - expect(recordsWriteReply.status.detail).to.contain(DwnErrorCode.RecordsGrantAuthorizationScopeSchema); - }); - }); - describe('grant condition published', () => { it('Rejects unpublished records if grant condition `published` === required', async () => { // scenario: Alice gives Bob a grant with condition `published` === required. @@ -4010,6 +3834,15 @@ export function testRecordsWriteHandler(): void { const alice = await TestDataGenerator.generateDidKeyPersona(); const bob = await TestDataGenerator.generateDidKeyPersona(); + // Alice installs a protocol + const protocolDefinition = minimalProtocolDefinition; + const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ + author: alice, + protocolDefinition, + }); + const protocolsConfigureReply = await dwn.processMessage(alice.did, protocolsConfig.message); + expect(protocolsConfigureReply.status.code).to.equal(202); + // Alice creates a grant for Bob with `published` === required const permissionGrant = await PermissionsProtocol.createGrant({ signer : Jws.createSigner(alice), @@ -4018,6 +3851,7 @@ export function testRecordsWriteHandler(): void { scope : { interface : DwnInterfaceName.Records, method : DwnMethodName.Write, + protocol : protocolDefinition.protocol, }, conditions: { publication: PermissionConditionPublication.Required, @@ -4035,8 +3869,10 @@ export function testRecordsWriteHandler(): void { // Bob is able to write a published record const publishedRecordsWrite = await TestDataGenerator.generateRecordsWrite({ - author : bob, - published : true, + author : bob, + protocol : protocolDefinition.protocol, + protocolPath : 'foo', + published : true, permissionGrantId }); const publishedRecordsWriteReply = await dwn.processMessage( @@ -4048,8 +3884,10 @@ export function testRecordsWriteHandler(): void { // Bob is not able to write an unpublished record const unpublishedRecordsWrite = await TestDataGenerator.generateRecordsWrite({ - author : bob, - published : false, + author : bob, + protocol : protocolDefinition.protocol, + protocolPath : 'foo', + published : false, permissionGrantId }); const unpublishedRecordsWriteReply = @@ -4065,6 +3903,15 @@ export function testRecordsWriteHandler(): void { const alice = await TestDataGenerator.generateDidKeyPersona(); const bob = await TestDataGenerator.generateDidKeyPersona(); + // Alice installs a protocol + const protocolDefinition = minimalProtocolDefinition; + const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ + author: alice, + protocolDefinition, + }); + const protocolsConfigureReply = await dwn.processMessage(alice.did, protocolsConfig.message); + expect(protocolsConfigureReply.status.code).to.equal(202); + // Alice creates a grant for Bob with `published` === prohibited const permissionGrant = await PermissionsProtocol.createGrant({ signer : Jws.createSigner(alice), @@ -4073,6 +3920,7 @@ export function testRecordsWriteHandler(): void { scope : { interface : DwnInterfaceName.Records, method : DwnMethodName.Write, + protocol : protocolDefinition.protocol, }, conditions: { publication: PermissionConditionPublication.Prohibited @@ -4090,8 +3938,10 @@ export function testRecordsWriteHandler(): void { // Bob not is able to write a published record const publishedRecordsWrite = await TestDataGenerator.generateRecordsWrite({ - author : bob, - published : true, + author : bob, + protocol : protocolDefinition.protocol, + protocolPath : 'foo', + published : true, permissionGrantId }); const publishedRecordsWriteReply = await dwn.processMessage( @@ -4104,8 +3954,10 @@ export function testRecordsWriteHandler(): void { // Bob is able to write an unpublished record const unpublishedRecordsWrite = await TestDataGenerator.generateRecordsWrite({ - author : bob, - published : false, + author : bob, + protocol : protocolDefinition.protocol, + protocolPath : 'foo', + published : false, permissionGrantId }); const unpublishedRecordsWriteReply = @@ -4120,6 +3972,15 @@ export function testRecordsWriteHandler(): void { const alice = await TestDataGenerator.generateDidKeyPersona(); const bob = await TestDataGenerator.generateDidKeyPersona(); + // Alice installs a protocol + const protocolDefinition = minimalProtocolDefinition; + const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({ + author: alice, + protocolDefinition, + }); + const protocolsConfigureReply = await dwn.processMessage(alice.did, protocolsConfig.message); + expect(protocolsConfigureReply.status.code).to.equal(202); + // Alice creates a grant for Bob with `published` === prohibited const permissionGrant = await PermissionsProtocol.createGrant({ signer : Jws.createSigner(alice), @@ -4128,6 +3989,7 @@ export function testRecordsWriteHandler(): void { scope : { interface : DwnInterfaceName.Records, method : DwnMethodName.Write, + protocol : protocolDefinition.protocol, }, conditions: { // publication: '', // intentionally undefined @@ -4145,8 +4007,10 @@ export function testRecordsWriteHandler(): void { // Bob is able to write a published record const publishedRecordsWrite = await TestDataGenerator.generateRecordsWrite({ - author : bob, - published : true, + author : bob, + protocol : protocolDefinition.protocol, + protocolPath : 'foo', + published : true, permissionGrantId }); const publishedRecordsWriteReply = await dwn.processMessage( @@ -4158,8 +4022,10 @@ export function testRecordsWriteHandler(): void { // Bob is able to write an unpublished record const unpublishedRecordsWrite = await TestDataGenerator.generateRecordsWrite({ - author : bob, - published : false, + author : bob, + protocol : protocolDefinition.protocol, + protocolPath : 'foo', + published : false, permissionGrantId }); const unpublishedRecordsWriteReply = diff --git a/tests/utils/records.spec.ts b/tests/utils/records.spec.ts index c9862f2a5..970e322cc 100644 --- a/tests/utils/records.spec.ts +++ b/tests/utils/records.spec.ts @@ -38,11 +38,11 @@ describe('Records', () => { const alice = await TestDataGenerator.generatePersona(); const deviceX = await TestDataGenerator.generatePersona(); - // create a delegation scope from alice to deviceX for writing records with schema `foo/bar` + // create a delegation scope from alice to deviceX for writing records in a protocol const scope:PermissionScope = { interface : DwnInterfaceName.Records, method : DwnMethodName.Write, - schema : 'foo/bar', + protocol : 'https://example.com/protocol/test', }; // create the delegated grant message @@ -60,7 +60,8 @@ describe('Records', () => { const { message } = await RecordsWrite.create({ signer : Jws.createSigner(deviceX), delegatedGrant : bobGrant.dataEncodedMessage, - schema : 'foo/bar', + protocol : 'https://example.com/protocol/test', + protocolPath : 'test/path', dataFormat : 'application/json', data : writeData, });