Skip to content

Commit

Permalink
Added prune protocol action + ProtocolAuthorization refactoring (#…
Browse files Browse the repository at this point in the history
…735)

- Added support for `prune` protocol action.
- Renamed "ancestor chain" to "record chain" because the chain often contains not just the ancestor records, but also record in concern which leads to confusion.
-  Simplified context and implementation of `constructRecordChain()` 
-  Refactored `authorizeAgainstAllowedActions()` to make it more easily understood.
  • Loading branch information
thehenrytsai authored May 13, 2024
1 parent 83a6b41 commit c08fdd3
Show file tree
Hide file tree
Showing 13 changed files with 693 additions and 102 deletions.
2 changes: 2 additions & 0 deletions json-schemas/interface-methods/protocol-rule-set.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@
"type": "string",
"enum": [
"co-delete",
"co-prune",
"co-update",
"create",
"delete",
"prune",
"read",
"update"
]
Expand Down
2 changes: 1 addition & 1 deletion src/core/dwn-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export enum DwnErrorCode {
ProtocolAuthorizationMissingRuleSet = 'ProtocolAuthorizationMissingRuleSet',
ProtocolAuthorizationParentlessIncorrectProtocolPath = 'ProtocolAuthorizationParentlessIncorrectProtocolPath',
ProtocolAuthorizationNotARole = 'ProtocolAuthorizationNotARole',
ProtocolAuthorizationParentNotFoundConstructingAncestorChain = 'ProtocolAuthorizationParentNotFoundConstructingAncestorChain',
ProtocolAuthorizationParentNotFoundConstructingRecordChain = 'ProtocolAuthorizationParentNotFoundConstructingRecordChain',
ProtocolAuthorizationProtocolNotFound = 'ProtocolAuthorizationProtocolNotFound',
ProtocolAuthorizationQueryWithoutRole = 'ProtocolAuthorizationQueryWithoutRole',
ProtocolAuthorizationRoleMissingRecipient = 'ProtocolAuthorizationRoleMissingRecipient',
Expand Down
223 changes: 143 additions & 80 deletions src/core/protocol-authorization.ts

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions src/handlers/records-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,26 @@ export class RecordsDeleteHandler implements MethodHandler {
return messageReply;
};

/**
* Authorizes a RecordsDelete message.
*
* @param newestRecordsWrite Newest RecordsWrite of the record to be deleted.
*/
private static async authorizeRecordsDelete(
tenant: string,
recordsDelete: RecordsDelete,
recordsWriteToDelete: RecordsWrite,
newestRecordsWrite: RecordsWrite,
messageStore: MessageStore
): Promise<void> {

if (Message.isSignedByAuthorDelegate(recordsDelete.message)) {
await recordsDelete.authorizeDelegate(recordsWriteToDelete.message, messageStore);
await recordsDelete.authorizeDelegate(newestRecordsWrite.message, messageStore);
}

if (recordsDelete.author === tenant) {
return;
} else if (recordsWriteToDelete.message.descriptor.protocol !== undefined) {
await ProtocolAuthorization.authorizeDelete(tenant, recordsDelete, recordsWriteToDelete, messageStore);
} else if (newestRecordsWrite.message.descriptor.protocol !== undefined) {
await ProtocolAuthorization.authorizeDelete(tenant, recordsDelete, newestRecordsWrite, messageStore);
} else {
throw new DwnError(
DwnErrorCode.RecordsDeleteAuthorizationFailed,
Expand Down
11 changes: 7 additions & 4 deletions src/interfaces/protocols-configure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ export class ProtocolsConfigure extends AbstractMessage<ProtocolsConfigureMessag
);
}

// Validate that if `who === recipient` and `of === undefined`, then `can` can only contain `co-delete` and `co-update`
// Validate that if `who === recipient` and `of === undefined`, then `can` can only contain `co-update`, `co-delete`, and `co-prune`.
// We do not allow `read`, `write`, or `query` in the `can` array because:
// - `read` - Recipients are always allowed to `read`.
// - `write` - Entails ability to create and update.
Expand All @@ -192,11 +192,14 @@ export class ProtocolsConfigure extends AbstractMessage<ProtocolsConfigureMessag
// - `query` - Only authorized using roles, so allowing direct recipients to query is outside the scope.
if (actionRule.who === ProtocolActor.Recipient && actionRule.of === undefined) {

// throw if `can` contains a value that is not `co-update` or `co-delete`
if (actionRule.can.some((allowedAction) => ![ProtocolAction.CoUpdate, ProtocolAction.CoDelete].includes(allowedAction as ProtocolAction))) {
// throw if `can` contains a value that is not `co-update`, `co-delete`, or `co-prune`
const hasDisallowedAction = actionRule.can.some(
action => ![ProtocolAction.CoUpdate, ProtocolAction.CoDelete, ProtocolAction.CoPrune].includes(action as ProtocolAction)
);
if (hasDisallowedAction) {
throw new DwnError(
DwnErrorCode.ProtocolsConfigureInvalidRecipientOfAction,
'Rules for `recipient` without `of` property must have `can` containing only `co-update` or `co-delete`'
'Rules for `recipient` without `of` property must have `can` containing only `co-update`, `co-delete`, and `co-prune`.'
);
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/records-write.ts
Original file line number Diff line number Diff line change
Expand Up @@ -957,7 +957,7 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
}

/**
* Gets the initial write from the given list or record write.
* Gets the initial write from the given list of `RecordsWrite`.
*/
public static async getInitialWrite(messages: GenericMessage[]): Promise<RecordsWriteMessage> {
for (const message of messages) {
Expand All @@ -966,7 +966,7 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
}
}

throw new DwnError(DwnErrorCode.RecordsWriteGetInitialWriteNotFound, `initial write is not found`);
throw new DwnError(DwnErrorCode.RecordsWriteGetInitialWriteNotFound, `Initial write is not found.`);
}

/**
Expand Down Expand Up @@ -1001,7 +1001,7 @@ export class RecordsWrite implements MessageInterface<RecordsWriteMessage> {
}

/**
* Gets the DID of the author of the given message.
* Gets the DID of the attesters of the given message.
*/
public static getAttesters(message: InternalRecordsWriteMessage): string[] {
const attestationSignatures = message.attestation?.signatures ?? [];
Expand Down
2 changes: 2 additions & 0 deletions src/types/protocols-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ export enum ProtocolActor {

export enum ProtocolAction {
CoDelete = 'co-delete',
CoPrune = 'co-prune',
CoUpdate = 'co-update',
Create = 'create',
Delete = 'delete',
Prune = 'prune',
Query = 'query',
Read = 'read',
Subscribe = 'subscribe',
Expand Down
2 changes: 1 addition & 1 deletion src/utils/records.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ export class Records {
* Checks if the filter supports returning published records.
*/
static filterIncludesPublishedRecords(filter: RecordsFilter): boolean {
// When `published` and `datePublished` range are both undefined, published records can be returned.
// NOTE: published records should still be returned when `published` and `datePublished` range are both undefined.
return filter.datePublished !== undefined || filter.published !== false;
}

Expand Down
2 changes: 1 addition & 1 deletion tests/core/protocol-authorization.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('ProtocolAuthorization', () => {
messageStoreStub.query.resolves({ messages: [] }); // simulate parent not in message store

await expect(ProtocolAuthorization.authorizeWrite(alice.did, recordsWrite, messageStoreStub)).to.be.rejectedWith(
DwnErrorCode.ProtocolAuthorizationParentNotFoundConstructingAncestorChain
DwnErrorCode.ProtocolAuthorizationParentNotFoundConstructingRecordChain
);
});
});
Expand Down
Loading

0 comments on commit c08fdd3

Please sign in to comment.