Skip to content

Commit

Permalink
feat: handle acl operations (#311)
Browse files Browse the repository at this point in the history
Co-authored-by: anhnd350309 <[email protected]>
  • Loading branch information
trungnotchung and anhnd350309 authored Jan 2, 2025
1 parent 1beba5e commit 79133d0
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 14 deletions.
56 changes: 43 additions & 13 deletions packages/object/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,23 +102,35 @@ export class DRPObject implements IDRPObject {
}

// This function is black magic, it allows us to intercept calls to the DRP object
proxyDRPHandler(): ProxyHandler<object> {
proxyDRPHandler(parentProp?: string): ProxyHandler<object> {
const obj = this;
return {
get(target, propKey, receiver) {
if (typeof target[propKey as keyof object] === "function") {
const value = Reflect.get(target, propKey, receiver);

if (typeof value === "function") {
const fullPropKey = parentProp
? `${parentProp}.${String(propKey)}`
: String(propKey);
return new Proxy(target[propKey as keyof object], {
apply(applyTarget, thisArg, args) {
if ((thisArg.operations as string[]).includes(propKey as string))
obj.callFn(
propKey as string,
args.length === 1 ? args[0] : args,
);
obj.callFn(fullPropKey, args.length === 1 ? args[0] : args);
return Reflect.apply(applyTarget, thisArg, args);
},
});
}
return Reflect.get(target, propKey, receiver);

if (typeof value === "object" && value !== null && propKey === "acl") {
return new Proxy(
value,
obj.proxyDRPHandler(
parentProp ? `${parentProp}.${String(propKey)}` : String(propKey),
),
);
}

return value;
},
};
}
Expand Down Expand Up @@ -184,6 +196,28 @@ export class DRPObject implements IDRPObject {
}
}

private _applyOperation(drp: DRP, operation: Operation) {
const { type, value } = operation;

const typeParts = type.split(".");
// biome-ignore lint: target can be anything
let target: any = drp;
for (let i = 0; i < typeParts.length - 1; i++) {
target = target[typeParts[i]];
if (!target) {
throw new Error(`Invalid operation type: ${type}`);
}
}

const methodName = typeParts[typeParts.length - 1];
if (typeof target[methodName] !== "function") {
throw new Error(`${type} is not a function`);
}

const args = Array.isArray(value) ? value : [value];
target[methodName](...args);
}

private _computeState(
vertexDependencies: Hash[],
vertexOperation?: Operation | undefined,
Expand Down Expand Up @@ -216,14 +250,10 @@ export class DRPObject implements IDRPObject {
}

for (const op of linearizedOperations) {
const args = Array.isArray(op.value) ? op.value : [op.value];
drp[op.type](...args);
this._applyOperation(drp, op);
}
if (vertexOperation) {
const args = Array.isArray(vertexOperation.value)
? vertexOperation.value
: [vertexOperation.value];
drp[vertexOperation.type](...args);
this._applyOperation(drp, vertexOperation);
}

const varNames: string[] = Object.keys(drp);
Expand Down
68 changes: 67 additions & 1 deletion packages/object/tests/hashgraph.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AddWinsSetWithACL } from "@topology-foundation/blueprints/src/AddWinsSetWithACL/index.js";
import { beforeEach, describe, expect, test } from "vitest";
import { AddWinsSet } from "../../blueprints/src/AddWinsSet/index.js";
import { PseudoRandomWinsSet } from "../../blueprints/src/PseudoRandomWinsSet/index.js";
import { DRPObject, type Operation, OperationType } from "../src/index.js";

describe("HashGraph construction tests", () => {
Expand Down Expand Up @@ -633,3 +633,69 @@ describe("Vertex timestamp tests", () => {
).toThrowError("Invalid timestamp detected.");
});
});

describe("Operation with ACL tests", () => {
let obj1: DRPObject;
let obj2: DRPObject;

beforeEach(async () => {
const peerIdToPublicKey = new Map<string, string>([
["peer1", "publicKey1"],
]);
obj1 = new DRPObject(
"peer1",
new AddWinsSetWithACL<number>(peerIdToPublicKey),
);
obj2 = new DRPObject(
"peer2",
new AddWinsSetWithACL<number>(peerIdToPublicKey),
);
});

test("Node with admin permission can grant permission to other nodes", () => {
/*
ROOT -- V1:GRANT("peer2")
*/

const drp1 = obj1.drp as AddWinsSetWithACL<number>;
const drp2 = obj2.drp as AddWinsSetWithACL<number>;

drp1.acl.grant("peer1", "peer2", "publicKey2");
obj2.merge(obj1.hashGraph.getAllVertices());
expect(drp2.acl.isWriter("peer2")).toBe(true);
});

test("Node with writer permission can create vertices", () => {
/*
ROOT -- V1:GRANT("peer2") -- V2:ADD(1)
*/
const drp1 = obj1.drp as AddWinsSetWithACL<number>;
const drp2 = obj2.drp as AddWinsSetWithACL<number>;

drp1.acl.grant("peer1", "peer2", "publicKey2");
obj2.merge(obj1.hashGraph.getAllVertices());

drp2.add("peer2", 1);
obj1.merge(obj2.hashGraph.getAllVertices());
expect(drp1.contains(1)).toBe(true);
});

test("Revoke permission from writer", () => {
/*
ROOT -- V1:GRANT("peer2") -- V2:ADD(1) -- V3:REVOKE("peer2")
*/
const drp1 = obj1.drp as AddWinsSetWithACL<number>;
const drp2 = obj2.drp as AddWinsSetWithACL<number>;

drp1.acl.grant("peer1", "peer2", "publicKey2");
obj2.merge(obj1.hashGraph.getAllVertices());

expect(drp2.acl.isWriter("peer2")).toBe(true);
drp2.add("peer2", 1);

obj1.merge(obj2.hashGraph.getAllVertices());
drp1.acl.revoke("peer1", "peer2");
obj2.merge(obj1.hashGraph.getAllVertices());
expect(drp2.acl.isWriter("peer2")).toBe(false);
});
});

0 comments on commit 79133d0

Please sign in to comment.