From 74e9ebcdbcea89f171bf5199f06f813bb8e71a2a Mon Sep 17 00:00:00 2001 From: trungnotchung Date: Thu, 14 Nov 2024 19:01:50 +0700 Subject: [PATCH] feat: add acl-cro --- .../blueprints/src/AccessControlList/index.ts | 95 +++++++++++++++++++ packages/blueprints/tsconfig.json | 2 +- packages/object/src/constants.ts | 5 + 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 packages/blueprints/src/AccessControlList/index.ts create mode 100644 packages/object/src/constants.ts diff --git a/packages/blueprints/src/AccessControlList/index.ts b/packages/blueprints/src/AccessControlList/index.ts new file mode 100644 index 00000000..3e56a34e --- /dev/null +++ b/packages/blueprints/src/AccessControlList/index.ts @@ -0,0 +1,95 @@ +import { + ActionType, + type CRO, + type Operation, + type ResolveConflictsType, + SemanticsType, + type Vertex, +} from "@topology-foundation/object"; +import { Role } from "@topology-foundation/object/src/constants.js"; + +export class ACL implements CRO { + operations: string[] = ["grant", "revoke"]; + roles: Map; + semanticsType = SemanticsType.pair; + + constructor(nodeIds: string[] | undefined) { + this.roles = new Map(); + if (nodeIds) { + for (const nodeId of nodeIds) { + this.roles.set(nodeId, Role.ADMIN); + } + } + } + + private _grant(nodeId: string): void { + if (!this.roles.get(nodeId) || this.roles.get(nodeId) === Role.NONE) { + this.roles.set(nodeId, Role.ADMIN); + } + } + + grant(nodeId: string): void { + this._grant(nodeId); + } + + private _revoke(nodeId: string): void { + if (this.roles.get(nodeId) && this.roles.get(nodeId) !== Role.ADMIN) { + this.roles.set(nodeId, Role.NONE); + } + } + + revoke(nodeId: string): void { + this._revoke(nodeId); + } + + hasRole(nodeId: string, role: number): boolean { + return this.roles.get(nodeId) === role; + } + + getNodesWithWritePermission(): string[] { + return Array.from(this.roles.entries()) + .filter(([_, role]) => role !== Role.NONE) + .map(([value, _]) => value); + } + + getNodesWithAdminPermission(): string[] { + return Array.from(this.roles.entries()) + .filter(([_, role]) => role === Role.ADMIN) + .map(([value, _]) => value); + } + + resolveConflicts(vertices: Vertex[]): ResolveConflictsType { + if ( + vertices[0].operation && + vertices[1].operation && + vertices[0].operation?.type !== vertices[1].operation?.type && + vertices[0].operation?.value === vertices[1].operation?.value + ) { + return vertices[0].operation.type === "revoke" + ? { action: ActionType.DropRight } + : { action: ActionType.DropLeft }; + } + return { action: ActionType.Nop }; + } + + mergeCallback(operations: Operation[]): void { + const adminNodeIds = this.getNodesWithAdminPermission(); + this.roles = new Map(); + for (const nodeId of adminNodeIds) { + this.roles.set(nodeId, Role.ADMIN); + } + + for (const op of operations) { + switch (op.type) { + case "grant": + if (op.value !== null) this._grant(op.value); + break; + case "revoke": + if (op.value !== null) this._revoke(op.value); + break; + default: + break; + } + } + } +} diff --git a/packages/blueprints/tsconfig.json b/packages/blueprints/tsconfig.json index 3e8b28b1..078d5b11 100644 --- a/packages/blueprints/tsconfig.json +++ b/packages/blueprints/tsconfig.json @@ -8,6 +8,6 @@ "path": "../object" } ], - "include": ["src/**/*.ts"], + "include": ["src/**/*.ts", "../object/src/constants.ts"], "exclude": ["src/**/*.asc.ts"] } diff --git a/packages/object/src/constants.ts b/packages/object/src/constants.ts new file mode 100644 index 00000000..14ddccb5 --- /dev/null +++ b/packages/object/src/constants.ts @@ -0,0 +1,5 @@ +export enum Role { + NONE = 0, + GUEST = 1, + ADMIN = 2, +}