Skip to content

Commit 5fa931c

Browse files
author
Diane Huxley
authored
#306 Validate data format on protocol authd writes (#320)
* Validate data format on protocol authd writes * Lint
1 parent f6eb76c commit 5fa931c

File tree

6 files changed

+111
-2
lines changed

6 files changed

+111
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Decentralized Web Node (DWN) SDK
44

55
Code Coverage
6-
![Statements](https://img.shields.io/badge/statements-93.99%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-93.5%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-91.72%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-93.99%25-brightgreen.svg?style=flat)
6+
![Statements](https://img.shields.io/badge/statements-94.03%25-brightgreen.svg?style=flat) ![Branches](https://img.shields.io/badge/branches-93.54%25-brightgreen.svg?style=flat) ![Functions](https://img.shields.io/badge/functions-91.75%25-brightgreen.svg?style=flat) ![Lines](https://img.shields.io/badge/lines-94.03%25-brightgreen.svg?style=flat)
77

88
## Introduction
99

json-schemas/protocol-definition.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@
1717
"properties": {
1818
"schema": {
1919
"type": "string"
20+
},
21+
"dataFormats": {
22+
"type": "array",
23+
"items": {
24+
"type": "string"
25+
}
2026
}
2127
}
2228
}

src/core/dwn-error.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export enum DwnErrorCode {
2020
MessageStoreDataCidMismatch = 'MessageStoreDataCidMismatch',
2121
MessageStoreDataNotFound = 'MessageStoreDataNotFound',
2222
MessageStoreDataSizeMismatch = 'MessageStoreDataSizeMismatch',
23+
ProtocolAuthorizationIncorrectDataFormat = 'ProtocolAuthorizationIncorrectDataFormat',
2324
ProtocolAuthorizationIncorrectProtocolPath = 'ProtocolAuthorizationIncorrectProtocolPath',
2425
ProtocolAuthorizationInvalidSchema = 'ProtocolAuthorizationInvalidSchema',
2526
RecordsDecryptNoMatchingKeyDerivationScheme = 'RecordsDecryptNoMatchingKeyDerivationScheme',

src/core/protocol-authorization.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ export class ProtocolAuthorization {
5555
recordSchemaToLabelMap
5656
);
5757

58+
ProtocolAuthorization.verifyDataFormat(
59+
incomingMessage.message,
60+
protocolDefinition,
61+
recordSchemaToLabelMap,
62+
);
63+
5864
// verify method invoked against the allowed actions
5965
ProtocolAuthorization.verifyAllowedActions(
6066
tenant,
@@ -236,6 +242,40 @@ export class ProtocolAuthorization {
236242
}
237243
}
238244

245+
/**
246+
* Verifies the `dataFormat` declared in the given message (if it is a RecordsWrite) matches dataFormat of the schema label
247+
* in the given protocol.
248+
* @throws {DwnError} if fails verification.
249+
*/
250+
private static verifyDataFormat(
251+
inboundMessage: RecordsReadMessage | RecordsWriteMessage,
252+
protocolDefinition: ProtocolDefinition,
253+
recordSchemaToLabelMap: Map<string, string>
254+
): void {
255+
// skip verification if this is not a RecordsWrite
256+
if (inboundMessage.descriptor.method !== DwnMethodName.Write) {
257+
return;
258+
}
259+
const recordsWriteMessage = inboundMessage as RecordsWriteMessage;
260+
261+
const currentRecordSchema = recordsWriteMessage.descriptor.schema!;
262+
const currentRecordSchemaLabel = recordSchemaToLabelMap.get(currentRecordSchema)!;
263+
const expectedDataFormats = protocolDefinition.labels[currentRecordSchemaLabel].dataFormats;
264+
265+
// no `dataFormats` specified in protocol definition means that all dataFormats are allowed
266+
if (expectedDataFormats === undefined) {
267+
return;
268+
}
269+
270+
if (!expectedDataFormats.includes(recordsWriteMessage.descriptor.dataFormat)) {
271+
throw new DwnError(
272+
DwnErrorCode.ProtocolAuthorizationIncorrectDataFormat,
273+
`record with schema '${currentRecordSchema}' must have data format in (${expectedDataFormats}), \
274+
instead has '${recordsWriteMessage.descriptor.dataFormat}'`
275+
);
276+
}
277+
}
278+
239279
/**
240280
* Verifies the actions specified in the given message matches the allowed actions in the rule set.
241281
* @throws {Error} if action not allowed.

src/interfaces/protocols/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ export type ProtocolsConfigureDescriptor = {
1111

1212
export type ProtocolDefinition = {
1313
labels: {
14-
[key: string]: { schema: string };
14+
[key: string]: {
15+
schema: string,
16+
dataFormats?: string[],
17+
};
1518
};
1619
records: {
1720
[key: string]: ProtocolRuleSet;

tests/interfaces/records/handlers/records-write.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,65 @@ describe('RecordsWriteHandler.handle()', () => {
10291029
expect(reply.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationIncorrectProtocolPath);
10301030
});
10311031

1032+
it('should fail authorization if given `dataFormat` is mismatching with the dataFormats in protocol definition', async () => {
1033+
const alice = await DidKeyResolver.generate();
1034+
1035+
const protocolDefinition = {
1036+
labels: {
1037+
image: {
1038+
schema : 'https://example.com/schema',
1039+
dataFormats : ['image/jpeg', 'image/png']
1040+
}
1041+
},
1042+
records: {
1043+
image: {
1044+
allow: {
1045+
anyone: { to: ['write'] }
1046+
}
1047+
}
1048+
}
1049+
};
1050+
1051+
const protocol = 'https://tbd.website/decentralized-web-node/protocols/social-media';
1052+
const protocolConfig = await TestDataGenerator.generateProtocolsConfigure({
1053+
requester : alice,
1054+
protocol,
1055+
protocolDefinition : protocolDefinition,
1056+
});
1057+
1058+
const protocolConfigureReply = await dwn.processMessage(alice.did, protocolConfig.message, protocolConfig.dataStream);
1059+
expect(protocolConfigureReply.status.code).to.equal(202);
1060+
1061+
// write record with matching dataFormat
1062+
const data = Encoder.stringToBytes('any data');
1063+
const recordsWriteMatch = await TestDataGenerator.generateRecordsWrite({
1064+
requester : alice,
1065+
recipientDid : alice.did,
1066+
protocol,
1067+
protocolPath : 'image',
1068+
schema : protocolDefinition.labels.image.schema,
1069+
dataFormat : 'image/jpeg',
1070+
data
1071+
});
1072+
const replyMatch = await dwn.processMessage(alice.did, recordsWriteMatch.message, recordsWriteMatch.dataStream);
1073+
expect(replyMatch.status.code).to.equal(202);
1074+
1075+
// write record with mismatch dataFormat
1076+
const recordsWriteMismatch = await TestDataGenerator.generateRecordsWrite({
1077+
requester : alice,
1078+
recipientDid : alice.did,
1079+
protocol,
1080+
protocolPath : 'image',
1081+
schema : protocolDefinition.labels.image.schema,
1082+
dataFormat : 'not/allowed/dataFormat',
1083+
data
1084+
});
1085+
1086+
const replyMismatch = await dwn.processMessage(alice.did, recordsWriteMismatch.message, recordsWriteMismatch.dataStream);
1087+
expect(replyMismatch.status.code).to.equal(401);
1088+
expect(replyMismatch.status.detail).to.contain(DwnErrorCode.ProtocolAuthorizationIncorrectDataFormat);
1089+
});
1090+
10321091
it('should fail authorization if record schema is not allowed at the hierarchical level attempted for the RecordsWrite', async () => {
10331092
const alice = await DidKeyResolver.generate();
10341093

0 commit comments

Comments
 (0)