Initial Commit
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import {
|
||||
fileId,
|
||||
GrantManager,
|
||||
InMemoryPartitionStore,
|
||||
KeyRing,
|
||||
PartitionManager,
|
||||
TokenManager,
|
||||
TransferManager,
|
||||
userId,
|
||||
wrapKey,
|
||||
} from "../src/index.js";
|
||||
import { getDefaultPolicy } from "../src/partitions/partition-policy/partition-policy.js";
|
||||
|
||||
describe("drive-sharing — end-to-end integration", () => {
|
||||
it("create partition, add member, grant access, transfer file", {
|
||||
timeout: 30000,
|
||||
}, async () => {
|
||||
// Build the full lifecycle stack with deterministic clocks / IDs.
|
||||
const events = new Map<string, unknown[]>();
|
||||
const bus = {
|
||||
on: () => () => undefined,
|
||||
emit: (k: string, p: unknown) => {
|
||||
const arr = events.get(k) ?? [];
|
||||
arr.push(p);
|
||||
events.set(k, arr);
|
||||
},
|
||||
};
|
||||
let counter = 0;
|
||||
const idGen = () => `id-${(counter += 1)}`;
|
||||
|
||||
const masterKey = new Uint8Array(32).fill(0x77);
|
||||
const keyRing = new KeyRing({ masterKey });
|
||||
const store = new InMemoryPartitionStore();
|
||||
const partitions = new PartitionManager({
|
||||
store,
|
||||
keyRing,
|
||||
clock: () => 1_000,
|
||||
idGenerator: idGen,
|
||||
events: bus,
|
||||
});
|
||||
const grants = new GrantManager({
|
||||
clock: () => 1_000,
|
||||
idGenerator: idGen,
|
||||
events: bus,
|
||||
});
|
||||
const tokens = new TokenManager({
|
||||
clock: () => 1_000,
|
||||
defaultTTL: 60_000,
|
||||
masterKey,
|
||||
idGenerator: idGen,
|
||||
});
|
||||
|
||||
// 1. Create source and target partitions.
|
||||
const src = await partitions.create({
|
||||
name: "research",
|
||||
type: "shared",
|
||||
ownerId: userId("alice"),
|
||||
});
|
||||
const dst = await partitions.create({
|
||||
name: "review",
|
||||
type: "shared",
|
||||
ownerId: userId("alice"),
|
||||
});
|
||||
|
||||
// 2. Add bob as a member of source.
|
||||
const updatedSrc = await partitions.addMember(src.id, userId("bob"), {
|
||||
permissions: ["read", "share"],
|
||||
});
|
||||
expect(updatedSrc.members.length).toBe(1);
|
||||
|
||||
// 3. Mint a grant for bob to read the file.
|
||||
const grant = grants.create({
|
||||
grantorId: userId("alice"),
|
||||
granteeId: userId("bob"),
|
||||
partitionId: src.id,
|
||||
permissions: ["read"],
|
||||
expiresAt: 5_000_000,
|
||||
});
|
||||
const check = grants.check(grant.id, userId("bob"), "read");
|
||||
expect(check.allowed).toBe(true);
|
||||
|
||||
// 4. Mint a share token for the file.
|
||||
const token = tokens.create({
|
||||
partitionId: src.id,
|
||||
createdBy: userId("alice"),
|
||||
permissions: ["read"],
|
||||
});
|
||||
expect(tokens.validate(token.id).valid).toBe(true);
|
||||
|
||||
// 5. Set up transfer with a real wrapped DEK.
|
||||
const sourceKek = new Uint8Array(32).fill(0xaa);
|
||||
const targetKek = new Uint8Array(32).fill(0xbb);
|
||||
const dek = new Uint8Array(32).fill(0xcc);
|
||||
const aadSrc = new TextEncoder().encode(
|
||||
`drive-sharing/v1/transfer/${src.id}/${fileId("f-1")}`
|
||||
);
|
||||
const wrapped = await wrapKey(sourceKek, dek, { aad: aadSrc });
|
||||
|
||||
const transfer = new TransferManager({
|
||||
kekResolver: async (_uid, pid) =>
|
||||
pid === src.id ? sourceKek : targetKek,
|
||||
policyResolver: async (pid) => {
|
||||
const partition =
|
||||
pid === src.id ? updatedSrc : { ...dst, members: [] };
|
||||
return {
|
||||
policy: getDefaultPolicy(),
|
||||
partition,
|
||||
};
|
||||
},
|
||||
wrappedKeyResolver: async () => wrapped,
|
||||
clock: () => 2_000,
|
||||
});
|
||||
|
||||
const result = await transfer.transfer({
|
||||
fileId: fileId("f-1"),
|
||||
sourcePartitionId: src.id,
|
||||
targetPartitionId: dst.id,
|
||||
userId: userId("alice"),
|
||||
direction: "export",
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.rewrappedKey).toBeDefined();
|
||||
|
||||
// 6. Verify lifecycle event emission.
|
||||
expect(events.get("partition:created")?.length).toBe(2);
|
||||
expect(events.get("member:added")?.length).toBe(1);
|
||||
expect(events.get("grant:created")?.length).toBe(1);
|
||||
|
||||
// 7. Revoke the grant and confirm subsequent checks are denied.
|
||||
grants.revoke(grant.id);
|
||||
expect(grants.check(grant.id, userId("bob"), "read").allowed).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
138
packages/networking/drive/drive-sharing/tests/property.test.ts
Normal file
138
packages/networking/drive/drive-sharing/tests/property.test.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import * as fc from "fast-check";
|
||||
import {
|
||||
GrantManager,
|
||||
InMemoryPartitionStore,
|
||||
KeyRing,
|
||||
mergePolicies,
|
||||
PartitionManager,
|
||||
partitionId,
|
||||
TokenManager,
|
||||
userId,
|
||||
} from "../src/index.js";
|
||||
|
||||
describe("property — grant expiry", () => {
|
||||
it("grant check returns allowed false after advancing clock beyond TTL", {
|
||||
timeout: 30000,
|
||||
}, () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.integer({ min: 1, max: 100_000 }),
|
||||
fc.integer({ min: 1, max: 100 }),
|
||||
(ttl, offset) => {
|
||||
let now = 0;
|
||||
const m = new GrantManager({ clock: () => now });
|
||||
const grant = m.create({
|
||||
grantorId: userId("a"),
|
||||
granteeId: userId("b"),
|
||||
partitionId: partitionId("p"),
|
||||
permissions: ["read"],
|
||||
expiresAt: ttl,
|
||||
});
|
||||
now = ttl + offset;
|
||||
const r = m.check(grant.id, userId("b"), "read");
|
||||
expect(r.allowed).toBe(false);
|
||||
}
|
||||
),
|
||||
{ numRuns: 50 }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("property — token revocation finality", () => {
|
||||
it("revoked token validate always returns valid false", {
|
||||
timeout: 30000,
|
||||
}, () => {
|
||||
fc.assert(
|
||||
fc.property(fc.integer({ min: 1, max: 100 }), (validateCount) => {
|
||||
const m = new TokenManager({
|
||||
clock: () => 0,
|
||||
masterKey: new Uint8Array(32).fill(0xee),
|
||||
});
|
||||
const t = m.create({
|
||||
partitionId: partitionId("p"),
|
||||
createdBy: userId("a"),
|
||||
permissions: ["read"],
|
||||
});
|
||||
m.revoke(t.id);
|
||||
for (let i = 0; i < validateCount; i += 1) {
|
||||
const r = m.validate(t.id);
|
||||
expect(r.valid).toBe(false);
|
||||
}
|
||||
}),
|
||||
{ numRuns: 30 }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("property — policy merge commutativity", () => {
|
||||
it("mergePolicies on disjoint allowedActions yields union", {
|
||||
timeout: 30000,
|
||||
}, () => {
|
||||
fc.assert(
|
||||
fc.property(
|
||||
fc.uniqueArray(fc.constantFrom("read", "write" as const), {
|
||||
minLength: 0,
|
||||
maxLength: 2,
|
||||
}),
|
||||
fc.uniqueArray(fc.constantFrom("delete", "share" as const), {
|
||||
minLength: 0,
|
||||
maxLength: 2,
|
||||
}),
|
||||
(a, b) => {
|
||||
const aP = {
|
||||
encryption: true,
|
||||
reauthRequired: [],
|
||||
allowedActions: a,
|
||||
};
|
||||
const bP = {
|
||||
encryption: true,
|
||||
reauthRequired: [],
|
||||
allowedActions: b,
|
||||
};
|
||||
const ab = mergePolicies(aP, bP);
|
||||
const ba = mergePolicies(bP, aP);
|
||||
// Same set, order may differ.
|
||||
expect(new Set(ab.allowedActions)).toEqual(
|
||||
new Set(ba.allowedActions)
|
||||
);
|
||||
expect(new Set(ab.allowedActions)).toEqual(
|
||||
new Set([...a, ...b])
|
||||
);
|
||||
}
|
||||
),
|
||||
{ numRuns: 50 }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("property — partition member invariant", () => {
|
||||
it("addMember then removeMember restores member count", {
|
||||
timeout: 30000,
|
||||
}, async () => {
|
||||
await fc.assert(
|
||||
fc.asyncProperty(
|
||||
fc.string({ minLength: 1, maxLength: 16 }),
|
||||
async (name) => {
|
||||
const manager = new PartitionManager({
|
||||
store: new InMemoryPartitionStore(),
|
||||
keyRing: new KeyRing(),
|
||||
});
|
||||
const p = await manager.create({
|
||||
name,
|
||||
type: "shared",
|
||||
ownerId: userId("alice"),
|
||||
});
|
||||
const before = p.members.length;
|
||||
const after1 = await manager.addMember(p.id, userId("bob"));
|
||||
const after2 = await manager.removeMember(
|
||||
after1.id,
|
||||
userId("bob")
|
||||
);
|
||||
expect(after2.members.length).toBe(before);
|
||||
}
|
||||
),
|
||||
{ numRuns: 25 }
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user