UNPKG

@cerbos/orm-prisma

Version:

Prisma adapter for Cerbos query plans

1,955 lines (1,765 loc) 130 kB
import { queryPlanToPrisma, PlanKind, QueryPlanToPrismaResult } from "."; import { beforeAll, beforeEach, afterEach, describe, test, expect, } from "@jest/globals"; import { PlanExpression, PlanExpressionOperand, PlanResourcesConditionalResponse, PlanResourcesResponse, } from "@cerbos/core"; import { Prisma, PrismaClient } from "@prisma/client"; import { GRPC as Cerbos } from "@cerbos/grpc"; const prisma = new PrismaClient(); const cerbos = new Cerbos("127.0.0.1:3593", { tls: false }); function createConditionalPlan( condition: PlanExpressionOperand ): PlanResourcesConditionalResponse { return { kind: PlanKind.CONDITIONAL, condition, cerbosCallId: "", requestId: "", validationErrors: [], metadata: undefined, }; } function getExpressionOperand( expression: PlanExpression, index: number ): PlanExpressionOperand { const operand = expression.operands[index]; if (!operand) { throw new Error(`Missing operand at index ${index}`); } return operand; } const fixtureUsers: Prisma.UserCreateInput[] = [ { id: "user1", aBool: true, aNumber: 1, aString: "string", }, { id: "user2", aBool: true, aNumber: 2, aString: "string", }, ]; const fixtureNextLevelResources: Prisma.NextLevelNestedResourceCreateInput[] = [ { id: "nextLevel1", aBool: true, aNumber: 1, aString: "string", }, { id: "nextLevel2", aBool: false, aNumber: 1, aString: "string", }, { id: "nextLevel3", aBool: true, aNumber: 1, aString: "string", }, ]; const fixtureTags: Prisma.TagCreateInput[] = [ { id: "tag1", name: "public", }, { id: "tag2", name: "private", }, { id: "tag3", name: "draft", }, ]; const fixtureNestedResources: Prisma.NestedResourceCreateInput[] = [ { id: "nested1", aBool: true, aNumber: 1, aString: "string", nextlevel: { connect: { id: "nextLevel1", }, }, }, { id: "nested2", aBool: false, aNumber: 1, aString: "string", nextlevel: { connect: { id: "nextLevel2", }, }, }, { id: "nested3", aBool: true, aNumber: 1, aString: "string", nextlevel: { connect: { id: "nextLevel3", }, }, }, ]; const fixtureLabels: Prisma.LabelCreateInput[] = [ { id: "label1", name: "important" }, { id: "label2", name: "archived" }, { id: "label3", name: "flagged" }, ]; const fixtureSubCategories: Prisma.SubCategoryCreateInput[] = [ { id: "sub1", name: "finance", labels: { connect: [{ id: "label1" }, { id: "label2" }], }, }, { id: "sub2", name: "tech", labels: { connect: [{ id: "label2" }, { id: "label3" }], }, }, ]; const fixtureCategories: Prisma.CategoryCreateInput[] = [ { id: "cat1", name: "business", subCategories: { connect: [{ id: "sub1" }], }, }, { id: "cat2", name: "development", subCategories: { connect: [{ id: "sub2" }], }, }, ]; const fixtureResources: Prisma.ResourceCreateInput[] = [ { id: "resource1", aBool: true, aNumber: 1, aString: "string", aOptionalString: "optionalString", createdBy: { connect: { id: "user1", }, }, ownedBy: { connect: [ { id: "user1", }, ], }, nested: { connect: { id: "nested1", }, }, tags: { connect: [ { id: "tag1" }, // public ], }, categories: { connect: [{ id: "cat1" }], }, }, { id: "resource2", aBool: false, aNumber: 2, aString: "string2", createdBy: { connect: { id: "user2", }, }, ownedBy: { connect: [ { id: "user2", }, ], }, nested: { connect: { id: "nested3", }, }, tags: { connect: [ { id: "tag2" }, // private ], }, categories: { connect: [{ id: "cat2" }], }, }, { id: "resource3", aBool: false, aNumber: 3, aString: "string3", createdBy: { connect: { id: "user1", }, }, ownedBy: { connect: [ { id: "user1", }, { id: "user2", }, ], }, nested: { connect: { id: "nested3", }, }, tags: { connect: [ { id: "tag1" }, // public { id: "tag3" }, // draft ], }, categories: { connect: [{ id: "cat1" }, { id: "cat2" }], }, }, ]; beforeAll(async () => { await prisma.resource.deleteMany(); await prisma.nextLevelNestedResource.deleteMany(); await prisma.nestedResource.deleteMany(); await prisma.tag.deleteMany(); await prisma.user.deleteMany(); }); beforeEach(async () => { for (const tag of fixtureTags) { await prisma.tag.create({ data: tag }); } for (const user of fixtureUsers) { await prisma.user.create({ data: user }); } for (const resource of fixtureNextLevelResources) { await prisma.nextLevelNestedResource.create({ data: resource }); } for (const resource of fixtureNestedResources) { await prisma.nestedResource.create({ data: resource }); } for (const label of fixtureLabels) { await prisma.label.create({ data: label }); } for (const subCategory of fixtureSubCategories) { await prisma.subCategory.create({ data: subCategory }); } for (const category of fixtureCategories) { await prisma.category.create({ data: category }); } for (const resource of fixtureResources) { await prisma.resource.create({ data: resource }); } }); afterEach(async () => { await prisma.resource.deleteMany(); await prisma.nestedResource.deleteMany(); await prisma.nextLevelNestedResource.deleteMany(); await prisma.tag.deleteMany(); await prisma.user.deleteMany(); await prisma.category.deleteMany(); await prisma.subCategory.deleteMany(); await prisma.label.deleteMany(); }); // Core Functionality describe("Basic Plan Types", () => { test("always allowed", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "always-allow", }); expect(queryPlan.kind).toEqual(PlanKind.ALWAYS_ALLOWED); const result = queryPlanToPrisma({ queryPlan, }); expect(result).toStrictEqual({ kind: PlanKind.ALWAYS_ALLOWED, }); const query = await prisma.resource.findMany({}); expect(query.length).toEqual(fixtureResources.length); }); test("always denied", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "always-deny", }); expect(queryPlan.kind).toEqual(PlanKind.ALWAYS_DENIED); const result = queryPlanToPrisma({ queryPlan, mapper: {}, }); expect(result).toEqual({ kind: PlanKind.ALWAYS_DENIED, }); }); }); // Field Operations describe("Field Operations", () => { describe("Basic Field Tests", () => { test("conditional - eq", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "equal", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "eq", operands: [{ name: "request.resource.attr.aBool" }, { value: true }], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aBool": { field: "aBool" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aBool: { equals: true } }, }); const query = await prisma.resource.findMany({ where: result.kind === PlanKind.CONDITIONAL ? { ...result.filters } : {}, }); expect(query.map((r) => r.id)).toEqual( fixtureResources.filter((a) => a.aBool).map((r) => r.id) ); }); test("conditional - eq - inverted order", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "equal", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "eq", operands: [{ name: "request.resource.attr.aBool" }, { value: true }], } ); const typeQp = queryPlan as PlanResourcesConditionalResponse; const invertedQueryPlan: PlanResourcesConditionalResponse = { ...typeQp, condition: { ...typeQp.condition, operands: [ getExpressionOperand(typeQp.condition as PlanExpression, 1), getExpressionOperand(typeQp.condition as PlanExpression, 0), ], }, }; const result = queryPlanToPrisma({ queryPlan: invertedQueryPlan, mapper: { "request.resource.attr.aBool": { field: "aBool" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aBool: { equals: true } }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources.filter((a) => a.aBool).map((r) => r.id) ); }); test("conditional - ne", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "ne", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "ne", operands: [ { name: "request.resource.attr.aString" }, { value: "string" }, ], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aString": { field: "aString" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aString: { not: "string" } }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources.filter((a) => a.aString != "string").map((r) => r.id) ); }); test("conditional - explicit-deny", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "explicit-deny", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "not", operands: [ { operator: "eq", operands: [ { name: "request.resource.attr.aBool" }, { value: true }, ], }, ], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aBool": { field: "aBool" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { NOT: { aBool: { equals: true } } }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources.filter((a) => !a.aBool).map((r) => r.id) ); }); }); describe("Comparison Tests", () => { test("conditional - gt", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "gt", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "gt", operands: [{ name: "request.resource.attr.aNumber" }, { value: 1 }], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aNumber": { field: "aNumber" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aNumber: { gt: 1 }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter((r) => { return r.aNumber > 1; }) .map((r) => r.id) ); }); test("conditional - lt", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "lt", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "lt", operands: [{ name: "request.resource.attr.aNumber" }, { value: 2 }], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aNumber": { field: "aNumber" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aNumber: { lt: 2 }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter((r) => { return r.aNumber < 2; }) .map((r) => r.id) ); }); test("conditional - gte", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "gte", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "ge", operands: [{ name: "request.resource.attr.aNumber" }, { value: 1 }], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aNumber": { field: "aNumber" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aNumber: { gte: 1 }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter((r) => { return r.aNumber >= 1; }) .map((r) => r.id) ); }); test("conditional - lte", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "lte", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "le", operands: [{ name: "request.resource.attr.aNumber" }, { value: 2 }], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aNumber": { field: "aNumber" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aNumber: { lte: 2 }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter((r) => { return r.aNumber <= 2; }) .map((r) => r.id) ); }); }); describe("String Tests", () => { test("conditional - contains", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "contains", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operands: [ { name: "request.resource.attr.aString", }, { value: "str", }, ], operator: "contains", } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aString": { field: "aString" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aString: { contains: "str" } }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter((a) => a.aString.includes("str")) .map((r) => r.id) ); }); test("conditional - startsWith", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "starts-with", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operands: [ { name: "request.resource.attr.aString", }, { value: "str", }, ], operator: "startsWith", } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aString": { field: "aString" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aString: { startsWith: "str" } }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter((a) => a.aString.startsWith("str")) .map((r) => r.id) ); }); test("conditional - endsWith", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "ends-with", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operands: [ { name: "request.resource.attr.aString", }, { value: "ing", }, ], operator: "endsWith", } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aString": { field: "aString" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aString: { endsWith: "ing" } }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter((a) => a.aString.endsWith("ing")) .map((r) => r.id) ); }); test("conditional - isSet", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "is-set", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operands: [ { name: "request.resource.attr.aOptionalString", }, { value: null, }, ], operator: "ne", } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aOptionalString": { field: "aOptionalString" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aOptionalString: { not: null } }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources.filter((a) => a.aOptionalString).map((r) => r.id) ); }); }); }); // Collection Operations describe("Collection Operations", () => { describe("Basic Collections", () => { test("conditional - in", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "in", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "in", operands: [ { name: "request.resource.attr.aString" }, { value: ["string", "anotherString"] }, ], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aString": { field: "aString" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aString: { in: ["string", "anotherString"] }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter((r) => { return ["string", "anotherString"].includes(r.aString); }) .map((r) => r.id) ); }); test("conditional - in - scalar value", async () => { const queryPlan = createConditionalPlan({ operator: "in", operands: [ { name: "request.resource.attr.aString" }, { value: "string" }, ], }); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.aString": { field: "aString" }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { aString: "string" }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id).sort()).toEqual( fixtureResources .filter((r) => r.aString === "string") .map((r) => r.id) .sort() ); }); test("conditional - relation in", async () => { const queryPlan = createConditionalPlan({ operator: "in", operands: [ { name: "request.resource.attr.categories.name" }, { value: ["business"] }, ], }); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.categories": { relation: { name: "categories", type: "many", fields: { name: { field: "name" }, }, }, }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { categories: { some: { name: "business", }, }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id).sort()).toEqual( fixtureResources .filter((resource) => { const categoryRefs = (resource.categories?.connect as Prisma.CategoryWhereUniqueInput[]) ?? []; return categoryRefs.some((categoryRef) => { const category = fixtureCategories.find( (fc) => fc.id === categoryRef.id ); return category?.name === "business"; }); }) .map((r) => r.id) .sort() ); }); test("conditional - relation in multiple values", async () => { const queryPlan = createConditionalPlan({ operator: "in", operands: [ { name: "request.resource.attr.categories.name" }, { value: ["business", "development"] }, ], }); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.categories": { relation: { name: "categories", type: "many", fields: { name: { field: "name" }, }, }, }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { categories: { some: { name: { in: ["business", "development"] }, }, }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id).sort()).toEqual( fixtureResources .filter((resource) => { const categoryRefs = (resource.categories?.connect as Prisma.CategoryWhereUniqueInput[]) ?? []; return categoryRefs.some((categoryRef) => { const category = fixtureCategories.find( (fc) => fc.id === categoryRef.id ); return ["business", "development"].includes(category?.name ?? ""); }); }) .map((r) => r.id) .sort() ); }); test("conditional - except relation subset", async () => { const queryPlan = createConditionalPlan({ operator: "except", operands: [ { name: "request.resource.attr.categories" }, { operator: "lambda", operands: [ { operator: "eq", operands: [ { name: "cat.name" }, { value: "business" }, ], }, { name: "cat" }, ], }, ], }); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.categories": { relation: { name: "categories", type: "many", fields: { name: { field: "name" }, }, }, }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { categories: { some: { NOT: { name: { equals: "business" }, }, }, }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id).sort()).toEqual( fixtureResources .filter((resource) => { const categoryRefs = (resource.categories?.connect as Prisma.CategoryWhereUniqueInput[]) ?? []; return categoryRefs.some((categoryRef) => { const category = fixtureCategories.find( (fc) => fc.id === categoryRef.id ); return category?.name !== "business"; }); }) .map((r) => r.id) .sort() ); }); test("conditional - except nested relation subset", async () => { const queryPlan = createConditionalPlan({ operator: "except", operands: [ { name: "request.resource.attr.categories" }, { operator: "lambda", operands: [ { operator: "exists", operands: [ { name: "cat.subCategories" }, { operator: "lambda", operands: [ { operator: "eq", operands: [ { name: "sub.name" }, { value: "finance" }, ], }, { name: "sub" }, ], }, ], }, { name: "cat" }, ], }, ], }); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.categories": { relation: { name: "categories", type: "many", fields: { subCategories: { relation: { name: "subCategories", type: "many", fields: { name: { field: "name" }, }, }, }, }, }, }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { categories: { some: { NOT: { subCategories: { some: { name: { equals: "finance" }, }, }, }, }, }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id).sort()).toEqual( fixtureResources .filter((resource) => { const categoryRefs = (resource.categories?.connect as Prisma.CategoryWhereUniqueInput[]) ?? []; return categoryRefs.some((categoryRef) => { const category = fixtureCategories.find( (fc) => fc.id === categoryRef.id ); if (!category) { return false; } const subCategoryRefs = (category.subCategories?.connect as Prisma.SubCategoryWhereUniqueInput[]) ?? []; return !subCategoryRefs.some((subCategoryRef) => { const subCategory = fixtureSubCategories.find( (fsc) => fsc.id === subCategoryRef.id ); return subCategory?.name === "finance"; }); }); }) .map((r) => r.id) .sort() ); }); test("conditional - exists single", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "exists-single", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "exists", operands: [ { name: "request.resource.attr.tags", }, { operator: "lambda", operands: [ { operator: "eq", operands: [ { name: "tag.id", }, { value: "tag1", }, ], }, { name: "tag", }, ], }, ], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.tags": { relation: { name: "tags", type: "many", }, }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { tags: { some: { id: { equals: "tag1", }, }, }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter( (a) => Array.isArray(a.tags?.connect) && a.tags?.connect .map((t) => { return fixtureTags.find((f) => f.id === t.id); }) .filter((t) => t?.id == "tag1").length > 0 ) .map((r) => r.id) ); }); test("conditional - exists multiple", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "exists-multiple", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "exists", operands: [ { name: "request.resource.attr.tags", }, { operator: "lambda", operands: [ { operator: "and", operands: [ { operator: "eq", operands: [ { name: "tag.id", }, { value: "tag1", }, ], }, { operator: "eq", operands: [ { name: "tag.name", }, { value: "public", }, ], }, ], }, { name: "tag", }, ], }, ], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.tags": { relation: { name: "tags", type: "many", }, }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { tags: { some: { AND: [ { id: { equals: "tag1", }, }, { name: { equals: "public", }, }, ], }, }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter( (a) => Array.isArray(a.tags?.connect) && a.tags?.connect .map((t) => { return fixtureTags.find((f) => f.id === t.id); }) .filter((t) => t?.id === "tag1" && t?.name === "public") .length > 0 ) .map((r) => r.id) ); }); test("conditional - exists_one", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "exists-one", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "exists_one", operands: [ { name: "request.resource.attr.tags", }, { operator: "lambda", operands: [ { operator: "eq", operands: [ { name: "tag.name", }, { value: "public", }, ], }, { name: "tag", }, ], }, ], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.tags": { relation: { name: "tags", type: "many", }, }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { tags: { some: { name: { equals: "public" }, }, }, AND: [ { tags: { every: { OR: [ { name: { equals: "public" } }, { NOT: { name: { equals: "public" } } }, ], }, }, }, ], }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter( (a) => Array.isArray(a.tags?.connect) && a.tags?.connect .map((t) => { return fixtureTags.find((f) => f.id === t.id); }) .filter((t) => t?.id === "tag1" && t?.name === "public") .length > 0 ) .map((r) => r.id) ); }); test("conditional - all", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "all", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "all", operands: [ { name: "request.resource.attr.tags", }, { operator: "lambda", operands: [ { operator: "eq", operands: [ { name: "tag.name", }, { value: "public", }, ], }, { name: "tag", }, ], }, ], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.tags": { relation: { name: "tags", type: "many", }, }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { tags: { every: { name: { equals: "public" }, }, }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter( (a) => Array.isArray(a.tags?.connect) && a.tags?.connect .map((t) => { return fixtureTags.find((f) => f.id === t.id); }) .every((t) => t?.name === "public") ) .map((r) => r.id) ); }); test("conditional - filter", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "filter", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "filter", operands: [ { name: "request.resource.attr.tags", }, { operator: "lambda", operands: [ { operator: "eq", operands: [ { name: "tag.name", }, { value: "public", }, ], }, { name: "tag", }, ], }, ], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.tags": { relation: { name: "tags", type: "many", }, }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { tags: { some: { name: { equals: "public" }, }, }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter( (a) => Array.isArray(a.tags?.connect) && a.tags?.connect .map((t) => { return fixtureTags.find((f) => f.id === t.id); }) .filter((t) => t?.name === "public").length > 0 ) .map((r) => r.id) ); }); test("conditional - map collection", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "map-collection", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); expect((queryPlan as PlanResourcesConditionalResponse).condition).toEqual( { operator: "hasIntersection", operands: [ { operator: "map", operands: [ { name: "request.resource.attr.tags" }, { operator: "lambda", operands: [{ name: "tag.name" }, { name: "tag" }], }, ], }, { value: ["public", "private"] }, ], } ); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.tags": { relation: { name: "tags", type: "many", fields: { name: { field: "name" }, }, }, }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { tags: { some: { name: { in: ["public", "private"] }, }, }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); // Should return resources that have either "public" or "private" tags expect(query.map((r) => r.id)).toEqual( fixtureResources .filter((r) => { const tagNames = Array.isArray(r.tags?.connect) ? r.tags.connect.map( (t) => fixtureTags.find((ft) => ft.id === t.id)?.name ) || [] : []; return tagNames.some((name) => ["public", "private"].includes(name || "") ); }) .map((r) => r.id) ); }); }); }); // Relations describe("Relations", () => { describe("Simple Relations", () => { describe("One-to-One", () => { test("conditional - relation is", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "relation-is", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); const conditions = (queryPlan as PlanResourcesConditionalResponse) .condition; expect(conditions).toEqual({ operator: "eq", operands: [ { name: "request.resource.attr.createdBy" }, { value: "user1" }, ], }); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.resource.attr.createdBy": { relation: { name: "createdBy", type: "one", field: "id", }, }, }, }); expect(result).toStrictEqual({ kind: PlanKind.CONDITIONAL, filters: { createdBy: { is: { id: { equals: "user1", }, }, }, }, }); if (result.kind !== PlanKind.CONDITIONAL) { throw new Error("Expected CONDITIONAL result"); } const query = await prisma.resource.findMany({ where: { ...result.filters }, }); expect(query.map((r) => r.id)).toEqual( fixtureResources .filter((r) => { if (!r.createdBy?.connect) return false; return (r.createdBy.connect as { id: string }).id == "user1"; }) .map((r) => r.id) ); }); test("conditional - relation is not", async () => { const queryPlan = await cerbos.planResources({ principal: { id: "user1", roles: ["USER"] }, resource: { kind: "resource" }, action: "relation-is-not", }); expect(queryPlan.kind).toEqual(PlanKind.CONDITIONAL); const conditions = (queryPlan as PlanResourcesConditionalResponse) .condition; expect(conditions).toEqual({ operator: "not", operands: [ { operator: "eq", operands: [ { name: "request.resource.attr.createdBy" }, { value: "user1" }, ], }, ], }); const result = queryPlanToPrisma({ queryPlan, mapper: { "request.r