From 4cd136be44be0b51f4264ccf5693579a9dd2625c Mon Sep 17 00:00:00 2001 From: Liran Cohen Date: Tue, 16 Jul 2024 17:08:23 -0400 Subject: [PATCH] Missing JSON Schema properties for Permission Scopes (#781) This PR adds some missing schema properties expected in the various protocol scopes. I wrote tests that validate that the messages are processed, but I think there need to be some additional testing regarding the functionality of various scopes in a subsequent PR/Issue. --- json-schemas/permissions/scopes.json | 21 ++ src/index.ts | 2 +- tests/features/permissions.spec.ts | 315 +++++++++++++++++++++++++++ 3 files changed, 337 insertions(+), 1 deletion(-) diff --git a/json-schemas/permissions/scopes.json b/json-schemas/permissions/scopes.json index 4be1b0d5e..13ac0d529 100644 --- a/json-schemas/permissions/scopes.json +++ b/json-schemas/permissions/scopes.json @@ -92,6 +92,12 @@ }, "protocol": { "type": "string" + }, + "contextId": { + "type": "string" + }, + "protocolPath": { + "type": "string" } } }, @@ -156,8 +162,17 @@ "interface": { "const": "Records" }, + "method": { + "const": "Query" + }, "protocol": { "type": "string" + }, + "contextId": { + "type": "string" + }, + "protocolPath": { + "type": "string" } } }, @@ -177,6 +192,12 @@ }, "protocol": { "type": "string" + }, + "contextId": { + "type": "string" + }, + "protocolPath": { + "type": "string" } } } diff --git a/src/index.ts b/src/index.ts index f85073d97..7a939196f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ export type { GenericMessage, GenericMessageReply, MessageSort, MessageSubscript export type { MessagesFilter, MessagesReadMessage as MessagesReadMessage, MessagesReadReply as MessagesReadReply, MessagesReadReplyEntry as MessagesReadReplyEntry, MessagesQueryMessage, MessagesQueryReply, MessagesSubscribeDescriptor, MessagesSubscribeMessage, MessagesSubscribeReply, MessageSubscriptionHandler } from './types/messages-types.js'; export type { Filter, EqualFilter, OneOfFilter, RangeFilter, RangeCriterion, PaginationCursor, QueryOptions } from './types/query-types.js'; export type { ProtocolsConfigureDescriptor, ProtocolDefinition, ProtocolTypes, ProtocolRuleSet, ProtocolsQueryFilter, ProtocolsConfigureMessage, ProtocolsQueryMessage, ProtocolsQueryReply } from './types/protocols-types.js'; -export type { EncryptionProperty, RecordsDeleteMessage, RecordsQueryMessage, RecordsQueryReply, RecordsQueryReplyEntry, RecordsReadMessage, RecordsReadReply, RecordsSubscribeDescriptor, RecordsSubscribeMessage, RecordsSubscribeReply, RecordSubscriptionHandler, RecordsWriteDescriptor, RecordsWriteTags, RecordsWriteTagValue, RecordsWriteMessage } from './types/records-types.js'; +export type { DataEncodedRecordsWriteMessage, EncryptionProperty, RecordsDeleteMessage, RecordsQueryMessage, RecordsQueryReply, RecordsQueryReplyEntry, RecordsReadMessage, RecordsReadReply, RecordsSubscribeDescriptor, RecordsSubscribeMessage, RecordsSubscribeReply, RecordSubscriptionHandler, RecordsWriteDescriptor, RecordsWriteTags, RecordsWriteTagValue, RecordsWriteMessage } from './types/records-types.js'; export { authenticate } from './core/auth.js'; export { ActiveTenantCheckResult, AllowAllTenantGate, TenantGate } from './core/tenant-gate.js'; export { Cid } from './utils/cid.js'; diff --git a/tests/features/permissions.spec.ts b/tests/features/permissions.spec.ts index 8f1f163b3..e7a458099 100644 --- a/tests/features/permissions.spec.ts +++ b/tests/features/permissions.spec.ts @@ -478,6 +478,321 @@ export function testPermissions(): void { expect(revokeWriteReply.status.code).to.equal(202); }); + // These set of tets are primarily to ensure SchemaValidation passes for the various permission request and grant messages and their scopes + describe('ensure loaded scope properties for permission requests are processed', () => { + it('MessagesQuery', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); + const bob = await TestDataGenerator.generateDidKeyPersona(); + + // create a permission grant with protocol + const messagesQueryPermissions = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to query from Alice test-context', + scope : { + interface : DwnInterfaceName.Messages, + method : DwnMethodName.Query, + protocol : 'https://example.com/protocol/test', + } + }); + + const messagesQueryPermissionsReply = await dwn.processMessage(alice.did, messagesQueryPermissions.recordsWrite.message, { + dataStream: DataStream.fromBytes(messagesQueryPermissions.permissionGrantBytes) + }); + expect(messagesQueryPermissionsReply.status.code).to.equal(202); + }); + + it('MessagesRead', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); + const bob = await TestDataGenerator.generateDidKeyPersona(); + + // create a permission grant with protocol + const messagesReadPermissions = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to read from Alice test-context', + scope : { + interface : DwnInterfaceName.Messages, + method : DwnMethodName.Read, + protocol : 'https://example.com/protocol/test', + } + }); + + const messagesReadPermissionsReply = await dwn.processMessage(alice.did, messagesReadPermissions.recordsWrite.message, { + dataStream: DataStream.fromBytes(messagesReadPermissions.permissionGrantBytes) + }); + expect(messagesReadPermissionsReply.status.code).to.equal(202); + }); + + it('MessagesSubscribe', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); + const bob = await TestDataGenerator.generateDidKeyPersona(); + + // create a permission grant with protocol + const messagesSubscribePermissions = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to subscribe from Alice test-context', + scope : { + interface : DwnInterfaceName.Messages, + method : DwnMethodName.Subscribe, + protocol : 'https://example.com/protocol/test', + } + }); + + const messagesSubscribePermissionsReply = await dwn.processMessage(alice.did, messagesSubscribePermissions.recordsWrite.message, { + dataStream: DataStream.fromBytes(messagesSubscribePermissions.permissionGrantBytes) + }); + expect(messagesSubscribePermissionsReply.status.code).to.equal(202); + }); + + it('RecordsDelete', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); + const bob = await TestDataGenerator.generateDidKeyPersona(); + + // create a permission grant with protocol and contextId + const withContextId = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to delete from Alice test-context', + scope : { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Delete, + protocol : 'https://example.com/protocol/test', + contextId : 'test-context' + } + }); + + const withContextIdReply = await dwn.processMessage(alice.did, withContextId.recordsWrite.message, { + dataStream: DataStream.fromBytes(withContextId.permissionGrantBytes) + }); + expect(withContextIdReply.status.code).to.equal(202); + + // create a permission request with protocol and protocolPath + const withProtocolPath = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to delete from Alice foo/bar', + scope : { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Delete, + protocol : 'https://example.com/protocol/test', + protocolPath : 'foo/bar' + } + }); + + const withProtocolPathReply = await dwn.processMessage(alice.did, withProtocolPath.recordsWrite.message, { + dataStream: DataStream.fromBytes(withProtocolPath.permissionGrantBytes) + }); + expect(withProtocolPathReply.status.code).to.equal(202); + }); + + it('RecordsQuery', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); + const bob = await TestDataGenerator.generateDidKeyPersona(); + + // create a permission grant with protocol and contextId scope + const withContextId = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to query from Alice test-context', + delegated : true, + scope : { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Query, + protocol : 'https://example.com/protocol/test', + contextId : 'test-context' + } + }); + + const withContextIdReply = await dwn.processMessage(alice.did, withContextId.recordsWrite.message, { + dataStream: DataStream.fromBytes(withContextId.permissionGrantBytes) + }); + expect(withContextIdReply.status.code).to.equal(202); + + // create a permission request with protocol and protocolPath scope + const withProtocolPath = await PermissionsProtocol.createRequest({ + signer : Jws.createSigner(bob), + description : 'Requesting to query from Alice foo/bar', + delegated : true, + scope : { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Query, + protocol : 'https://example.com/protocol/test', + protocolPath : 'foo/bar' + } + }); + + const withProtocolPathReply = await dwn.processMessage(bob.did, withProtocolPath.recordsWrite.message, { + dataStream: DataStream.fromBytes(withProtocolPath.permissionRequestBytes) + }); + expect(withProtocolPathReply.status.code).to.equal(202); + }); + + it('RecordsRead', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); + const bob = await TestDataGenerator.generateDidKeyPersona(); + + // create a permission grant with protocol and contextId scope + const withContextId = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to read to Alice test-context', + delegated : true, + scope : { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Read, + protocol : 'https://example.com/protocol/test', + contextId : 'test-context' + } + }); + + const withContextIdReply = await dwn.processMessage(alice.did, withContextId.recordsWrite.message, { + dataStream: DataStream.fromBytes(withContextId.permissionGrantBytes) + }); + expect(withContextIdReply.status.code).to.equal(202); + + // create a permission request with protocol and protocolPath scope + const withProtocolPath = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to read to Alice foo/bar', + delegated : true, + scope : { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Read, + protocol : 'https://example.com/protocol/test', + protocolPath : 'foo/bar' + } + }); + + const withProtocolPathReply = await dwn.processMessage(alice.did, withProtocolPath.recordsWrite.message, { + dataStream: DataStream.fromBytes(withProtocolPath.permissionGrantBytes) + }); + expect(withProtocolPathReply.status.code).to.equal(202); + }); + + it('RecordsSubscribe', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); + const bob = await TestDataGenerator.generateDidKeyPersona(); + + // create a permission grant with protocol and contextId scope + const withContextId = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to subscribe to Alice test-context', + delegated : true, + scope : { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Subscribe, + protocol : 'https://example.com/protocol/test', + contextId : 'test-context' + } + }); + + const withContextIdReply = await dwn.processMessage(alice.did, withContextId.recordsWrite.message, { + dataStream: DataStream.fromBytes(withContextId.permissionGrantBytes) + }); + expect(withContextIdReply.status.code).to.equal(202); + + // create a permission request with protocol and protocolPath scope + const withProtocolPath = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to subscribe to Alice foo/bar', + delegated : true, + scope : { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Subscribe, + protocol : 'https://example.com/protocol/test', + protocolPath : 'foo/bar' + } + }); + + const withProtocolPathReply = await dwn.processMessage(alice.did, withProtocolPath.recordsWrite.message, { + dataStream: DataStream.fromBytes(withProtocolPath.permissionGrantBytes) + }); + expect(withProtocolPathReply.status.code).to.equal(202); + }); + + it('RecordsWrite', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); + const bob = await TestDataGenerator.generateDidKeyPersona(); + + // create a permission grant with protocol and contextId scope + const withContextId = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to write to Alice test-context', + delegated : true, + scope : { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Write, + protocol : 'https://example.com/protocol/test', + contextId : 'test-context' + } + }); + + const withContextIdReply = await dwn.processMessage(alice.did, withContextId.recordsWrite.message, { + dataStream: DataStream.fromBytes(withContextId.permissionGrantBytes) + }); + expect(withContextIdReply.status.code).to.equal(202); + + // create a permission request with protocol and protocolPath scope + const withProtocolPath = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to write to Alice foo/bar', + delegated : true, + scope : { + interface : DwnInterfaceName.Records, + method : DwnMethodName.Write, + protocol : 'https://example.com/protocol/test', + protocolPath : 'foo/bar' + } + }); + + const withProtocolPathReply = await dwn.processMessage(alice.did, withProtocolPath.recordsWrite.message, { + dataStream: DataStream.fromBytes(withProtocolPath.permissionGrantBytes) + }); + expect(withProtocolPathReply.status.code).to.equal(202); + }); + + it('ProtocolsQuery', async () => { + const alice = await TestDataGenerator.generateDidKeyPersona(); + const bob = await TestDataGenerator.generateDidKeyPersona(); + + // create a permission grant with protocol query that is unrestricted + const protocolQueryPermissions = await PermissionsProtocol.createGrant({ + signer : Jws.createSigner(alice), + grantedTo : bob.did, + dateExpires : Time.createOffsetTimestamp({ seconds: 100 }), + description : 'Requesting to query from Alice test-context', + scope : { + interface : DwnInterfaceName.Protocols, + method : DwnMethodName.Query, + } + }); + + const protocolQueryPermissionsReply = await dwn.processMessage(alice.did, protocolQueryPermissions.recordsWrite.message, { + dataStream: DataStream.fromBytes(protocolQueryPermissions.permissionGrantBytes) + }); + expect(protocolQueryPermissionsReply.status.code).to.equal(202); + }); + }); + describe('validateScopeAndTags', async () => { it('should be called for a Request or Grant record', async () => { // spy on `validateScope`