Skip to content

Commit

Permalink
Move protocolRole to authorization payload
Browse files Browse the repository at this point in the history
  • Loading branch information
Diane Huxley committed Sep 25, 2023
1 parent 7f22727 commit 300b156
Show file tree
Hide file tree
Showing 12 changed files with 26 additions and 60 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Decentralized Web Node (DWN) SDK <!-- omit in toc -->

Code Coverage
![Statements](https://img.shields.io/badge/statements-97.77%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.03%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.28%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.77%25-brightgreen.svg?style=flat)
![Statements](https://img.shields.io/badge/statements-97.77%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-95.04%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-94.28%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-97.77%25-brightgreen.svg?style=flat)

- [Introduction](#introduction)
- [Installation](#installation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
},
"permissionsGrantId": {
"type": "string"
},
"protocolRole": {
"$comment": "Used in the Records interface to authorize role-authorized actions for protocol records",
"type": "string"
}
}
}
3 changes: 0 additions & 3 deletions json-schemas/interface-methods/records-read.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@
},
"filter": {
"$ref": "https://identity.foundation/dwn/json-schemas/records-filter.json"
},
"protocolRole": {
"type": "string"
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/core/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,11 @@ export abstract class Message<M extends GenericMessage> {
public static async signAsAuthorization(
descriptor: Descriptor,
signatureInput: Signer,
permissionsGrantId?: string,
additionalPayloadProperties?: { permissionsGrantId?: string, protocolRole?: string }
): Promise<GeneralJws> {
const descriptorCid = await Cid.computeCid(descriptor);

const authPayload: BaseAuthorizationPayload = { descriptorCid, permissionsGrantId };
const authPayload: BaseAuthorizationPayload = { descriptorCid, ...additionalPayloadProperties };
removeUndefinedProperties(authPayload);
const authPayloadStr = JSON.stringify(authPayload);
const authPayloadBytes = new TextEncoder().encode(authPayloadStr);
Expand Down
11 changes: 2 additions & 9 deletions src/core/protocol-authorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,20 +276,13 @@ export class ProtocolAuthorization {
return;
}

const protocolRole = (incomingMessage as RecordsRead).message.descriptor.protocolRole;
const protocolRole = (incomingMessage as RecordsRead).authorizationPayload?.protocolRole;

// Only verify role if there is a role being invoked
if (protocolRole === undefined) {
return;
}

if (incomingMessage.author === undefined) {
throw new DwnError(
DwnErrorCode.ProtocolAuthorizationRoleInvokedByAuthorless,
'Authorless messages may not invoke a protocolRole'
);
}

const roleRuleSet = ProtocolAuthorization.getRuleSetAtProtocolPath(protocolRole, protocolDefinition);
if (roleRuleSet?.$globalRole === undefined) {
throw new DwnError(
Expand Down Expand Up @@ -356,7 +349,7 @@ export class ProtocolAuthorization {
// Get role being invoked. Currently only Reads support role-based authorization
let invokedRole: string | undefined;
if (incomingMessage.message.descriptor.method === DwnMethodName.Read) {
invokedRole = incomingMessage.message.descriptor.protocolRole;
invokedRole = (incomingMessage as RecordsRead).authorizationPayload?.protocolRole;
}

for (const actionRule of actionRules) {
Expand Down
6 changes: 5 additions & 1 deletion src/interfaces/protocols-configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ export class ProtocolsConfigure extends Message<ProtocolsConfigureMessage> {
definition : ProtocolsConfigure.normalizeDefinition(options.definition)
};

const authorization = await Message.signAsAuthorization(descriptor, options.authorizationSigner, options.permissionsGrantId);
const authorization = await Message.signAsAuthorization(
descriptor,
options.authorizationSigner,
{ permissionsGrantId: options.permissionsGrantId }
);
const message = { descriptor, authorization };

Message.validateJsonSchema(message);
Expand Down
6 changes: 5 additions & 1 deletion src/interfaces/protocols-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ export class ProtocolsQuery extends Message<ProtocolsQueryMessage> {
// only generate the `authorization` property if signature input is given
let authorization: GeneralJws | undefined;
if (options.authorizationSigner !== undefined) {
authorization = await Message.signAsAuthorization(descriptor, options.authorizationSigner, options.permissionsGrantId);
authorization = await Message.signAsAuthorization(
descriptor,
options.authorizationSigner,
{ permissionsGrantId: options.permissionsGrantId }
);
}

const message = { descriptor, authorization };
Expand Down
5 changes: 2 additions & 3 deletions src/interfaces/records-read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,22 @@ export class RecordsRead extends Message<RecordsReadMessage> {
* @throws {DwnError} when a combination of required RecordsReadOptions are missing
*/
public static async create(options: RecordsReadOptions): Promise<RecordsRead> {
const { filter, authorizationSigner, permissionsGrantId } = options;
const { filter, authorizationSigner, permissionsGrantId, protocolRole } = options;
const currentTime = getCurrentTimeInHighPrecision();

const descriptor: RecordsReadDescriptor = {
interface : DwnInterfaceName.Records,
method : DwnMethodName.Read,
filter : Records.normalizeFilter(filter),
messageTimestamp : options.date ?? currentTime,
protocolRole : options.protocolRole,
};

removeUndefinedProperties(descriptor);

// only generate the `authorization` property if signature input is given
let authorization = undefined;
if (authorizationSigner !== undefined) {
authorization = await Message.signAsAuthorization(descriptor, authorizationSigner, permissionsGrantId);
authorization = await Message.signAsAuthorization(descriptor, authorizationSigner, { permissionsGrantId, protocolRole });
}
const message: RecordsReadMessage = { descriptor, authorization };

Expand Down
1 change: 1 addition & 0 deletions src/interfaces/records-write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,7 @@ export class RecordsWrite {
if (encryptionCid !== undefined) { authorizationPayload.encryptionCid = encryptionCid; } // assign `encryptionCid` only if it is defined
if (permissionsGrantId !== undefined) { authorizationPayload.permissionsGrantId = permissionsGrantId; }


const authorizationPayloadBytes = Encoder.objectToBytes(authorizationPayload);

const builder = await GeneralJwsBuilder.create(authorizationPayloadBytes, [signer]);
Expand Down
4 changes: 4 additions & 0 deletions src/types/message-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export type GenericMessage = {
export type BaseAuthorizationPayload = {
descriptorCid: string;
permissionsGrantId?: string;
/**
* Used in the Records interface to authorize role-authorized actions for protocol records.
*/
protocolRole?: string;
};

/**
Expand Down
1 change: 0 additions & 1 deletion src/types/records-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ export type RecordsReadDescriptor = {
method: DwnMethodName.Read;
filter: RecordsFilter;
messageTimestamp: string;
protocolRole?: string;
};

export type RecordsDeleteMessage = GenericMessage & {
Expand Down
39 changes: 0 additions & 39 deletions tests/handlers/records-read.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,45 +524,6 @@ export function testRecordsReadHandler(): void {
expect(chatReadReply.status.code).to.equal(200);
});

it('rejects role-authorized reads if the message has no author', async () => {
// scenario: Alice writes a chat message writes a chat message. An anonymous message tries and fails
// to invoke a role to read it.

const alice = await DidKeyResolver.generate();

const protocolDefinition = friendRoleProtocolDefinition;

const protocolsConfig = await TestDataGenerator.generateProtocolsConfigure({
author: alice,
protocolDefinition
});
const protocolWriteReply = await dwn.processMessage(alice.did, protocolsConfig.message);
expect(protocolWriteReply.status.code).to.equal(202);

// Alice writes a 'chat' record
const chatRecord = await TestDataGenerator.generateRecordsWrite({
author : alice,
recipient : alice.did,
protocol : protocolDefinition.protocol,
protocolPath : 'chat',
data : new TextEncoder().encode('Bob can read this cuz he is my friend'),
});
const chatReply = await dwn.processMessage(alice.did, chatRecord.message, chatRecord.dataStream);
expect(chatReply.status.code).to.equal(202);

// An anonymous message reads Alice's chat record
const readChatRecord = await RecordsRead.create({
// authorizationSigner intentionally omitted
filter: {
recordId: chatRecord.message.recordId,
},
protocolRole: 'friend'
});
const chatReadReply = await dwn.processMessage(alice.did, readChatRecord.message);
expect(chatReadReply.status.code).to.equal(401);
expect(chatReadReply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationRoleInvokedByAuthorless);
});

it('rejects role-authorized reads if the protocolRole is not a valid protocol path to a role record', async () => {
// scenario: Alice writes a chat message writes a chat message. Bob tries to invoke the 'chat' role,
// but 'chat' is not a role.
Expand Down

0 comments on commit 300b156

Please sign in to comment.