forked from EduCraft/curriculum-project-hub
fix: enforce active organization boundary
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
PrismaPrincipalResolver,
|
||||
syncExternalPrincipalMemberships,
|
||||
} from "../../src/permission.js";
|
||||
import { lockActiveOrganization } from "../../src/org/status.js";
|
||||
|
||||
describe("ADR-0019 authorization", () => {
|
||||
beforeEach(async () => {
|
||||
@@ -96,6 +97,75 @@ describe("ADR-0019 authorization", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["SUSPENDED", "ARCHIVED"] as const)(
|
||||
"denies every project authorization when the organization is %s",
|
||||
async (status) => {
|
||||
await prisma.project.create({
|
||||
data: { id: `p-org-${status}`, organizationId: DEFAULT_ORG_ID, name: status, workspaceDir: `/tmp/org-${status}` },
|
||||
});
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
id: `u-org-${status}`,
|
||||
feishuOpenId: `ou_org_${status}`,
|
||||
displayName: status,
|
||||
organizationMemberships: { create: { organizationId: DEFAULT_ORG_ID, role: "OWNER" } },
|
||||
},
|
||||
});
|
||||
await prisma.permissionGrant.create({
|
||||
data: {
|
||||
resourceType: "PROJECT",
|
||||
resourceId: `p-org-${status}`,
|
||||
principalType: "USER",
|
||||
principalId: `ou_org_${status}`,
|
||||
role: "MANAGE",
|
||||
},
|
||||
});
|
||||
await prisma.organization.update({ where: { id: DEFAULT_ORG_ID }, data: { status } });
|
||||
|
||||
for (const action of ["project.read", "project.edit", "collaborator.manage", "agent.trigger", "agent.cancel"] as const) {
|
||||
const decision = await createPermissionAuthorizer(prisma).can({
|
||||
actor: { feishuOpenId: `ou_org_${status}` },
|
||||
action,
|
||||
resource: { type: "PROJECT", id: `p-org-${status}` },
|
||||
});
|
||||
expect(decision.allowed).toBe(false);
|
||||
expect(decision.reason).toContain(`organization ${DEFAULT_ORG_ID} is ${status}`);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it("serializes a lifecycle transition behind an admitted customer transaction", async () => {
|
||||
let releaseOperation!: () => void;
|
||||
const operationHeld = new Promise<void>((resolve) => {
|
||||
releaseOperation = resolve;
|
||||
});
|
||||
let markLocked!: () => void;
|
||||
const rowLocked = new Promise<void>((resolve) => {
|
||||
markLocked = resolve;
|
||||
});
|
||||
const operation = prisma.$transaction(async (tx) => {
|
||||
await lockActiveOrganization(tx, DEFAULT_ORG_ID);
|
||||
markLocked();
|
||||
await operationHeld;
|
||||
});
|
||||
await rowLocked;
|
||||
|
||||
let transitionCommitted = false;
|
||||
const transition = prisma.organization.update({
|
||||
where: { id: DEFAULT_ORG_ID },
|
||||
data: { status: "SUSPENDED" },
|
||||
}).then(() => {
|
||||
transitionCommitted = true;
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
expect(transitionCommitted).toBe(false);
|
||||
|
||||
releaseOperation();
|
||||
await operation;
|
||||
await transition;
|
||||
expect(transitionCommitted).toBe(true);
|
||||
});
|
||||
|
||||
it("uses external Feishu principal memberships as grantable principals", async () => {
|
||||
await prisma.project.create({
|
||||
data: { id: "p-auth-ext", organizationId: DEFAULT_ORG_ID, name: "External", workspaceDir: "/tmp/auth-ext" },
|
||||
@@ -177,6 +247,36 @@ describe("ADR-0019 authorization", () => {
|
||||
expect(activeB.principals).not.toContainEqual({ type: "FEISHU_DEPARTMENT", id: "dep-b" });
|
||||
});
|
||||
|
||||
it("blocks directory sync and team grants for an archived organization", async () => {
|
||||
const team = await prisma.team.create({
|
||||
data: { organizationId: DEFAULT_ORG_ID, slug: "archived-team", name: "Archived Team" },
|
||||
});
|
||||
await prisma.project.create({
|
||||
data: { id: "p-archived-grant", organizationId: DEFAULT_ORG_ID, name: "Archived", workspaceDir: "/tmp/archived" },
|
||||
});
|
||||
await prisma.organization.update({ where: { id: DEFAULT_ORG_ID }, data: { status: "ARCHIVED" } });
|
||||
const expected = `organization ${DEFAULT_ORG_ID} is ARCHIVED`;
|
||||
|
||||
await expect(syncExternalPrincipalMemberships(prisma, {
|
||||
organizationId: DEFAULT_ORG_ID,
|
||||
source: "blocked-sync",
|
||||
memberships: [{
|
||||
feishuOpenId: "ou_blocked_sync",
|
||||
displayName: "Blocked",
|
||||
principalType: "FEISHU_DEPARTMENT",
|
||||
principalId: "dep-blocked",
|
||||
}],
|
||||
})).rejects.toThrow(expected);
|
||||
await expect(grantTeamProjectAccess(prisma, {
|
||||
projectId: "p-archived-grant",
|
||||
teamId: team.id,
|
||||
role: "EDIT",
|
||||
})).rejects.toThrow(expected);
|
||||
|
||||
await expect(prisma.externalDirectoryConnection.count()).resolves.toBe(0);
|
||||
await expect(prisma.permissionGrant.count({ where: { resourceId: "p-archived-grant" } })).resolves.toBe(0);
|
||||
});
|
||||
|
||||
it("lets PermissionSettings constrain agent trigger without granting by itself", async () => {
|
||||
await prisma.project.create({
|
||||
data: { id: "p-auth-policy", organizationId: DEFAULT_ORG_ID, name: "Policy", workspaceDir: "/tmp/auth-policy" },
|
||||
|
||||
Reference in New Issue
Block a user