diff --git a/ts/src/core/privacy.test.ts b/ts/src/core/privacy.test.ts index 92e0c7dbb..7b291a37f 100644 --- a/ts/src/core/privacy.test.ts +++ b/ts/src/core/privacy.test.ts @@ -20,8 +20,17 @@ import { AllowIfEntIsVisiblePolicy, DenyIfEntIsVisiblePolicy, AllowIfEdgeExistsRule, + AllowIfEdgeDoesNotExistRule, DenyIfEdgeExistsRule, DenyIfEdgeDoesNotExistRule, + AllowIfEdgeExistsFromViewerToEntRule, + AllowIfEdgeExistsFromEntToViewerRule, + AllowIfEdgeExistsFromViewerToEntPropertyRule, + AllowIfEdgeExistsFromEntPropertyToViewerRule, + DenyIfEdgeExistsFromViewerToEntRule, + DenyIfEdgeExistsFromEntToViewerRule, + DenyIfEdgeExistsFromViewerToEntPropertyRule, + DenyIfEdgeExistsFromEntPropertyToViewerRule, } from "./privacy"; import { LoggedOutViewer, IDViewer } from "./viewer"; @@ -289,7 +298,7 @@ describe("AllowIfViewerIsRule", () => { async function addEdge(user: User, id2: ID) { const builder = new SimpleBuilder( - loggedOutViewer, + user.viewer, getBuilderSchemaFromFields({}, User), new Map(), WriteOperation.Edit, @@ -299,9 +308,21 @@ async function addEdge(user: User, id2: ID) { await builder.saveX(); } +async function addInboundEdge(user: User, id2: ID) { + const builder = new SimpleBuilder( + user.viewer, + getBuilderSchemaFromFields({}, User), + new Map(), + WriteOperation.Edit, + user, + ); + builder.orchestrator.addInboundEdge(id2, "edge", "User"); + await builder.saveX(); +} + async function softDeleteEdge(user: User, id2: ID) { const builder = new SimpleBuilder( - loggedOutViewer, + user.viewer, getBuilderSchemaFromFields({}, User), new Map(), WriteOperation.Edit, @@ -311,9 +332,21 @@ async function softDeleteEdge(user: User, id2: ID) { await builder.saveX(); } +async function softDeleteInboundEdge(user: User, id2: ID) { + const builder = new SimpleBuilder( + user.viewer, + getBuilderSchemaFromFields({}, User), + new Map(), + WriteOperation.Edit, + user, + ); + builder.orchestrator.removeInboundEdge(id2, "edge"); + await builder.saveX(); +} + async function reallyDeleteEdge(user: User, id2: ID) { const builder = new SimpleBuilder( - loggedOutViewer, + user.viewer, getBuilderSchemaFromFields({}, User), new Map(), WriteOperation.Edit, @@ -325,6 +358,20 @@ async function reallyDeleteEdge(user: User, id2: ID) { await builder.saveX(); } +async function reallyDeleteInboundEdge(user: User, id2: ID) { + const builder = new SimpleBuilder( + user.viewer, + getBuilderSchemaFromFields({}, User), + new Map(), + WriteOperation.Edit, + user, + ); + builder.orchestrator.removeInboundEdge(id2, "edge", { + disableTransformations: true, + }); + await builder.saveX(); +} + describe("AllowIfEdgeExistsRule", () => { const id1 = v1(); const id2 = v1(); @@ -382,6 +429,330 @@ describe("AllowIfEdgeExistsRule", () => { }); }); +describe("AllowIfEdgeDoesNotExistRule", () => { + const id1 = v1(); + const id2 = v1(); + const policy = { + rules: [new AllowIfEdgeDoesNotExistRule(id1, id2, "edge"), AlwaysDenyRule], + }; + const policy2 = { + rules: [ + new AllowIfEdgeDoesNotExistRule(id1, id2, "edge", { + disableTransformations: true, + }), + AlwaysDenyRule, + ], + }; + const user = getUser(loggedOutViewer, id1, policy); + const user2 = getUser(loggedOutViewer, id1, policy2); + + test("edge exists", async () => { + await addEdge(user, id2); + + const bool = await applyPrivacyPolicy(user.viewer, policy, user); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(user2.viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge does not exist", async () => { + const bool = await applyPrivacyPolicy(user.viewer, policy, user); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(user2.viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge soft deleted", async () => { + await addEdge(user, id2); + await softDeleteEdge(user, id2); + + const bool = await applyPrivacyPolicy(user.viewer, policy, user); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(user2.viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge really deleted", async () => { + await addEdge(user, id2); + await reallyDeleteEdge(user, id2); + + const bool = await applyPrivacyPolicy(user.viewer, policy, user); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(user2.viewer, policy2, user2); + expect(bool2).toBe(true); + }); +}); + +describe("AllowIfEdgeExistsFromViewerToEntRule", () => { + const id1 = v1(); + const id2 = v1(); + const policy = { + rules: [new AllowIfEdgeExistsFromViewerToEntRule("edge")], + }; + const policy2 = { + rules: [ + new AllowIfEdgeExistsFromViewerToEntRule("edge", { + disableTransformations: true, + }), + ], + }; + const id1Viewer = new IDViewer(id1); + const user = getUser(id1Viewer, id1, policy); + const user2 = getUser(id1Viewer, id2, policy2); + + test("edge exists", async () => { + await addEdge(user, id2); + + // edge exists for user -> user2 + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + // edge exists + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge does not exist", async () => { + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge soft deleted", async () => { + await addEdge(user, id2); + await softDeleteEdge(user, id2); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge really deleted", async () => { + await addEdge(user, id2); + await reallyDeleteEdge(user, id2); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); +}); + +describe("AllowIfEdgeExistsFromEntToViewerRule", () => { + const id1 = v1(); + const id2 = v1(); + const policy = { + rules: [new AllowIfEdgeExistsFromEntToViewerRule("edge")], + }; + const policy2 = { + rules: [ + new AllowIfEdgeExistsFromEntToViewerRule("edge", { + disableTransformations: true, + }), + ], + }; + const id1Viewer = new IDViewer(id1); + const user = getUser(id1Viewer, id1, policy); + const user2 = getUser(id1Viewer, id2, policy2); + + test("edge exists", async () => { + await addEdge(user2, id1); + + // edge exists for user2 -> user + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + // edge exists + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge does not exist", async () => { + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge soft deleted", async () => { + await addEdge(user2, id1); + await softDeleteEdge(user2, id1); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge really deleted", async () => { + await addEdge(user2, id1); + await reallyDeleteEdge(user2, id1); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); +}); + +describe("AllowIfEdgeExistsFromViewerToEntPropertyRule", () => { + const id1 = v1(); + const id2 = v1(); + const id1AccountId = v1(); + const id2AccountId = v1(); + const policy = { + rules: [ + new AllowIfEdgeExistsFromViewerToEntPropertyRule( + "accountID", + "edge", + ), + ], + }; + const policy2 = { + rules: [ + new AllowIfEdgeExistsFromViewerToEntPropertyRule( + "accountID", + "edge", + { + disableTransformations: true, + }, + ), + ], + }; + const id1Viewer = new IDViewer(id1); + const user = getUser(id1Viewer, id1, policy, id1AccountId); + const user2 = getUser(id1Viewer, id2, policy2, id2AccountId); + + test("edge exists", async () => { + await addEdge(user, id2AccountId); + + // edge exists for user -> id1AccountId + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + // edge exists + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge does not exist", async () => { + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge soft deleted", async () => { + await addEdge(user, id2AccountId); + await softDeleteEdge(user, id2AccountId); + + // edge exists for user -> id1AccountId + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + // edge exists + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge really deleted", async () => { + await addEdge(user, id2AccountId); + await reallyDeleteEdge(user, id2AccountId); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); +}); + +describe("AllowIfEdgeExistsFromEntPropertyToViewerRule", () => { + const id1 = v1(); + const id2 = v1(); + const id1AccountId = v1(); + const id2AccountId = v1(); + const policy = { + rules: [ + new AllowIfEdgeExistsFromEntPropertyToViewerRule( + "accountID", + "edge", + ), + ], + }; + const policy2 = { + rules: [ + new AllowIfEdgeExistsFromEntPropertyToViewerRule( + "accountID", + "edge", + { + disableTransformations: true, + }, + ), + ], + }; + const id1Viewer = new IDViewer(id1); + const user = getUser(id1Viewer, id1, policy, id1AccountId); + const user2 = getUser(id1Viewer, id2, policy2, id2AccountId); + + test("edge exists", async () => { + await addInboundEdge(user, id2AccountId); + + // edge exists for user -> id1AccountId + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + // edge exists + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge does not exist", async () => { + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge soft deleted", async () => { + await addInboundEdge(user, id2AccountId); + await softDeleteInboundEdge(user, id2AccountId); + + // edge exists for user -> id1AccountId + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + // edge exists + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge really deleted", async () => { + await addEdge(user, id2AccountId); + await reallyDeleteInboundEdge(user, id2AccountId); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); +}); + describe("DenyIfEdgeExistsRule", () => { const id1 = v1(); const id2 = v1(); @@ -498,6 +869,266 @@ describe("DenyIfEdgeDoesNotExistRule", () => { }); }); +describe("DenyIfEdgeExistsFromViewerToEntRule", () => { + const id1 = v1(); + const id2 = v1(); + const policy = { + rules: [new DenyIfEdgeExistsFromViewerToEntRule("edge"), AlwaysAllowRule], + }; + const policy2 = { + rules: [ + new DenyIfEdgeExistsFromViewerToEntRule("edge", { + disableTransformations: true, + }), + AlwaysAllowRule, + ], + }; + const id1Viewer = new IDViewer(id1); + const user = getUser(id1Viewer, id1, policy); + const user2 = getUser(id1Viewer, id2, policy2); + + test("edge exists", async () => { + await addEdge(user, id2); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge does not exist", async () => { + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge soft deleted", async () => { + await addEdge(user, id2); + await softDeleteEdge(user, id2); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge really deleted", async () => { + await addEdge(user, id2); + await reallyDeleteEdge(user, id2); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); +}); + +describe("DenyIfEdgeExistsFromEntToViewerRule", () => { + const id1 = v1(); + const id2 = v1(); + const policy = { + rules: [new DenyIfEdgeExistsFromEntToViewerRule("edge"), AlwaysAllowRule], + }; + const policy2 = { + rules: [ + new DenyIfEdgeExistsFromEntToViewerRule("edge", { + disableTransformations: true, + }), + AlwaysAllowRule, + ], + }; + const id1Viewer = new IDViewer(id1); + const user = getUser(id1Viewer, id1, policy); + const user2 = getUser(id1Viewer, id2, policy2); + + test("edge exists", async () => { + await addEdge(user2, id1); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge does not exist", async () => { + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge soft deleted", async () => { + await addEdge(user2, id1); + await softDeleteEdge(user2, id1); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge really deleted", async () => { + await addEdge(user2, id1); + await reallyDeleteEdge(user2, id1); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); +}); + +describe("DenyIfEdgeExistsFromViewerToEntPropertyRule", () => { + const id1 = v1(); + const id2 = v1(); + const id1AccountId = v1(); + const id2AccountId = v1(); + const policy = { + rules: [ + new DenyIfEdgeExistsFromViewerToEntPropertyRule( + "accountID", + "edge", + ), + AlwaysAllowRule, + ], + }; + const policy2 = { + rules: [ + new DenyIfEdgeExistsFromViewerToEntPropertyRule( + "accountID", + "edge", + { + disableTransformations: true, + }, + ), + AlwaysAllowRule, + ], + }; + const id1Viewer = new IDViewer(id1); + const user = getUser(id1Viewer, id1, policy, id1AccountId); + const user2 = getUser(id1Viewer, id2, policy2, id2AccountId); + + test("edge exists", async () => { + await addEdge(user, id2AccountId); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge does not exist", async () => { + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge soft deleted", async () => { + await addEdge(user, id2AccountId); + await softDeleteEdge(user, id2AccountId); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge really deleted", async () => { + await addEdge(user, id2AccountId); + await reallyDeleteEdge(user, id2AccountId); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); +}); + +describe("DenyIfEdgeExistsFromEntPropertyToViewerRule", () => { + const id1 = v1(); + const id2 = v1(); + const id1AccountId = v1(); + const id2AccountId = v1(); + const policy = { + rules: [ + new DenyIfEdgeExistsFromEntPropertyToViewerRule( + "accountID", + "edge", + ), + AlwaysAllowRule, + ], + }; + const policy2 = { + rules: [ + new DenyIfEdgeExistsFromEntPropertyToViewerRule( + "accountID", + "edge", + { + disableTransformations: true, + }, + ), + AlwaysAllowRule, + ], + }; + const id1Viewer = new IDViewer(id1); + const user = getUser(id1Viewer, id1, policy, id1AccountId); + const user2 = getUser(id1Viewer, id2, policy2, id2AccountId); + + test("edge exists", async () => { + await addInboundEdge(user, id2AccountId); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(false); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge does not exist", async () => { + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); + + test("edge soft deleted", async () => { + await addInboundEdge(user, id2AccountId); + await softDeleteInboundEdge(user, id2AccountId); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(false); + }); + + test("edge really deleted", async () => { + await addInboundEdge(user, id2AccountId); + await reallyDeleteInboundEdge(user, id2AccountId); + + const bool = await applyPrivacyPolicy(id1Viewer, policy, user2); + expect(bool).toBe(true); + + const bool2 = await applyPrivacyPolicy(id1Viewer, policy2, user2); + expect(bool2).toBe(true); + }); +}); + describe("applyPrivacyPolicyX", () => { const policy = { rules: [DenyIfLoggedOutRule, AlwaysAllowRule], @@ -614,7 +1245,10 @@ describe("DenyIfEntIsVisibleRule", () => { }); class BlockedEntError extends Error { - constructor(public privacyPolicy: PrivacyPolicy, public entID: ID) { + constructor( + public privacyPolicy: PrivacyPolicy, + public entID: ID, + ) { super(`blocked privacy!`); } } diff --git a/ts/src/core/privacy.ts b/ts/src/core/privacy.ts index d0ad19e32..fc4a0a659 100644 --- a/ts/src/core/privacy.ts +++ b/ts/src/core/privacy.ts @@ -197,7 +197,10 @@ export class AllowIfViewerIsEntPropertyRule export class AllowIfEntPropertyIsRule implements PrivacyPolicyRule { - constructor(private property: keyof T, private val: any) {} + constructor( + private property: keyof T, + private val: any, + ) {} async apply(v: Viewer, ent?: T): Promise { const result: any = ent && ent[this.property]; @@ -211,7 +214,10 @@ export class AllowIfEntPropertyIsRule export class DenyIfEntPropertyIsRule implements PrivacyPolicyRule { - constructor(private property: keyof T, private val: any) {} + constructor( + private property: keyof T, + private val: any, + ) {} async apply(v: Viewer, ent?: T): Promise { const result: any = ent && ent[this.property]; @@ -227,7 +233,10 @@ export class AllowIfEntIsVisibleRule< TViewer extends Viewer, > implements PrivacyPolicyRule { - constructor(private id: ID, private options: LoadEntOptions) {} + constructor( + private id: ID, + private options: LoadEntOptions, + ) {} async apply(v: TViewer, _ent?: Ent): Promise { const visible = await loadEnt(v, this.id, this.options); @@ -243,7 +252,10 @@ export class AllowIfEntIsNotVisibleRule< TViewer extends Viewer, > implements PrivacyPolicyRule { - constructor(private id: ID, private options: LoadEntOptions) {} + constructor( + private id: ID, + private options: LoadEntOptions, + ) {} async apply(v: TViewer, _ent?: Ent): Promise { const visible = await loadEnt(v, this.id, this.options); @@ -259,7 +271,10 @@ export class AllowIfEntIsVisiblePolicy< TViewer extends Viewer, > implements PrivacyPolicy { - constructor(private id: ID, private options: LoadEntOptions) {} + constructor( + private id: ID, + private options: LoadEntOptions, + ) {} rules = [new AllowIfEntIsVisibleRule(this.id, this.options), AlwaysDenyRule]; } @@ -269,7 +284,10 @@ export class DenyIfEntIsVisiblePolicy< TViewer extends Viewer, > implements PrivacyPolicy { - constructor(private id: ID, private options: LoadEntOptions) {} + constructor( + private id: ID, + private options: LoadEntOptions, + ) {} rules = [new DenyIfEntIsVisibleRule(this.id, this.options), AlwaysAllowRule]; } @@ -279,7 +297,10 @@ export class DenyIfEntIsVisibleRule< TViewer extends Viewer, > implements PrivacyPolicyRule { - constructor(private id: ID, private options: LoadEntOptions) {} + constructor( + private id: ID, + private options: LoadEntOptions, + ) {} async apply(v: TViewer, _ent?: Ent): Promise { const visible = await loadEnt(v, this.id, this.options); @@ -295,7 +316,10 @@ export class DenyIfEntIsNotVisibleRule< TViewer extends Viewer, > implements PrivacyPolicyRule { - constructor(private id: ID, private options: LoadEntOptions) {} + constructor( + private id: ID, + private options: LoadEntOptions, + ) {} async apply(v: TViewer, _ent?: Ent): Promise { const visible = await loadEnt(v, this.id, this.options); @@ -329,6 +353,30 @@ async function allowIfEdgeExistsRule( return Skip(); } +async function allowIfEdgeDoesNotExistRule( + id1: ID | null | undefined, + id2: ID | null | undefined, + edgeType: string, + context?: Context, + options?: EdgeQueryableDataOptionsConfigureLoader, +): Promise { + if (!id1 || !id2) { + return Allow(); + } + const edge = await loadEdgeForID2({ + id1, + edgeType, + id2, + context, + ctr: AssocEdge, + queryOptions: options, + }); + if (!edge) { + return Allow(); + } + return Skip(); +} + export class AllowIfEdgeExistsRule implements PrivacyPolicyRule { constructor( private id1: ID, @@ -348,6 +396,30 @@ export class AllowIfEdgeExistsRule implements PrivacyPolicyRule { } } +export class AllowIfEdgeDoesNotExistRule implements PrivacyPolicyRule { + constructor( + private id1: ID, + private id2: ID, + private edgeType: string, + private options?: EdgeQueryableDataOptionsConfigureLoader, + ) {} + + async apply(v: Viewer, _ent?: Ent): Promise { + return allowIfEdgeDoesNotExistRule( + this.id1, + this.id2, + this.edgeType, + v.context, + this.options, + ); + } +} + +/** + * @deprecated use AllowIfEdgeExistsFromViewerToEntRule + * This is a privacy policy rule that checks if the viewer has an inbound edge to the ent. + * e.g. does edge exist from viewer's id to ent's id + */ export class AllowIfViewerInboundEdgeExistsRule implements PrivacyPolicyRule { constructor( private edgeType: string, @@ -365,6 +437,17 @@ export class AllowIfViewerInboundEdgeExistsRule implements PrivacyPolicyRule { } } +/** + * This is a privacy policy rule that checks if edge exists from viewer's id to ent's id + * Allows the viewer to see the ent if the edge exists + */ +export class AllowIfEdgeExistsFromViewerToEntRule extends AllowIfViewerInboundEdgeExistsRule {} + +/** + * @deprecated use AllowIfEdgeExistsFromEntToViewerRule + * This is a privacy policy rule that checks if the viewer has an outbound edge to the ent. + * e.g. does edge exist from ent's id to viewer's id + */ export class AllowIfViewerOutboundEdgeExistsRule implements PrivacyPolicyRule { constructor( private edgeType: string, @@ -382,6 +465,56 @@ export class AllowIfViewerOutboundEdgeExistsRule implements PrivacyPolicyRule { } } +/** + * This is a privacy policy rule that checks if edge exists from ent's id to viewer's id + */ +export class AllowIfEdgeExistsFromEntToViewerRule extends AllowIfViewerOutboundEdgeExistsRule {} + +/** + * This is a privacy policy rule that checks if edge exists from viewer to id of given ent property + */ +export class AllowIfEdgeExistsFromViewerToEntPropertyRule + implements PrivacyPolicyRule +{ + constructor( + private property: keyof T, + private edgeType: string, + private options?: EdgeQueryableDataOptionsConfigureLoader, + ) {} + + async apply(v: Viewer, ent?: T): Promise { + const result: any = ent && ent[this.property]; + return allowIfEdgeExistsRule( + v.viewerID, + result, + this.edgeType, + v.context, + this.options, + ); + } +} + +export class AllowIfEdgeExistsFromEntPropertyToViewerRule + implements PrivacyPolicyRule +{ + constructor( + private property: keyof T, + private edgeType: string, + private options?: EdgeQueryableDataOptionsConfigureLoader, + ) {} + + async apply(v: Viewer, ent?: T): Promise { + const result: any = ent && ent[this.property]; + return allowIfEdgeExistsRule( + result, + v.viewerID, + this.edgeType, + v.context, + this.options, + ); + } +} + async function denyIfEdgeExistsRule( id1: ID | null | undefined, id2: ID | null | undefined, @@ -450,6 +583,9 @@ export class DenyIfEdgeExistsRule implements PrivacyPolicyRule { } } +/** + * @deprecated use DenyIfEdgeExistsFromViewerToEntRule + */ export class DenyIfViewerInboundEdgeExistsRule implements PrivacyPolicyRule { constructor( private edgeType: string, @@ -467,6 +603,15 @@ export class DenyIfViewerInboundEdgeExistsRule implements PrivacyPolicyRule { } } +/** + * This is a privacy policy rule that checks if edge exists from viewer's id to ent's id + * Denies the viewer from seeing the ent if the edge exists + */ +export class DenyIfEdgeExistsFromViewerToEntRule extends DenyIfViewerInboundEdgeExistsRule {} + +/** + * @deprecated use DenyIfEdgeExistsFromEntToViewerRule + */ export class DenyIfViewerOutboundEdgeExistsRule implements PrivacyPolicyRule { constructor( private edgeType: string, @@ -484,6 +629,75 @@ export class DenyIfViewerOutboundEdgeExistsRule implements PrivacyPolicyRule { } } +/** + * This is a privacy policy rule that checks if edge exists from ent's id to viewer's id + */ +export class DenyIfEdgeExistsFromEntToViewerRule extends DenyIfViewerOutboundEdgeExistsRule {} + +/** + * @deprecated use DenyIfEdgeExistsFromViewerToEntPropertyRule + */ +export class DenyIfViewerInboundEdgeToEntPropertyExistsRule + implements PrivacyPolicyRule +{ + constructor( + private property: keyof T, + private edgeType: string, + private options?: EdgeQueryableDataOptionsConfigureLoader, + ) {} + + async apply(v: Viewer, ent?: T): Promise { + const result: any = ent && ent[this.property]; + return denyIfEdgeExistsRule( + v.viewerID, + result, + this.edgeType, + v.context, + this.options, + ); + } +} + +/** + * This is a privacy policy rule that checks if edge exists from viewer's id to ent's id + * Denies the viewer from seeing the ent if the edge exists + */ +export class DenyIfEdgeExistsFromViewerToEntPropertyRule< + T extends Ent, +> extends DenyIfViewerInboundEdgeToEntPropertyExistsRule {} + +/** + * @deprecated use DenyIfEdgeExistsFromEntToViewerPropertyRule + */ +export class DenyIfViewerOutboundEdgeToEntPropertyExistsRule + implements PrivacyPolicyRule +{ + constructor( + private property: keyof T, + private edgeType: string, + private options?: EdgeQueryableDataOptionsConfigureLoader, + ) {} + + async apply(v: Viewer, ent?: T): Promise { + const result: any = ent && ent[this.property]; + return denyIfEdgeExistsRule( + result, + v.viewerID, + this.edgeType, + v.context, + this.options, + ); + } +} + +/** + * This is a privacy policy rule that checks if edge exists from ent's id to viewer's id + * Denies the viewer from seeing the ent if the edge exists + */ +export class DenyIfEdgeExistsFromEntPropertyToViewerRule< + T extends Ent, +> extends DenyIfViewerOutboundEdgeToEntPropertyExistsRule {} + export class DenyIfEdgeDoesNotExistRule implements PrivacyPolicyRule { constructor( private id1: ID, @@ -541,9 +755,55 @@ export class DenyIfViewerOutboundEdgeDoesNotExistRule } } +export class DenyIfViewerInboundEdgeToEntPropertyDoesNotExistRule + implements PrivacyPolicyRule +{ + constructor( + private property: keyof T, + private edgeType: string, + private options?: EdgeQueryableDataOptionsConfigureLoader, + ) {} + + async apply(v: Viewer, ent?: T): Promise { + const result: any = ent && ent[this.property]; + return denyIfEdgeDoesNotExistRule( + v.viewerID, + result, + this.edgeType, + v.context, + this.options, + ); + } +} + +export class DenyIfViewerOutboundEdgeToEntPropertyDoesNotExistRule< + T extends Ent, +> implements PrivacyPolicyRule +{ + constructor( + private property: keyof T, + private edgeType: string, + private options?: EdgeQueryableDataOptionsConfigureLoader, + ) {} + + async apply(v: Viewer, ent?: T): Promise { + const result: any = ent && ent[this.property]; + return denyIfEdgeDoesNotExistRule( + result, + v.viewerID, + this.edgeType, + v.context, + this.options, + ); + } +} + // need a Deny version of this too export class AllowIfConditionAppliesRule implements PrivacyPolicyRule { - constructor(private fn: FuncRule, private rule: PrivacyPolicyRule) {} + constructor( + private fn: FuncRule, + private rule: PrivacyPolicyRule, + ) {} async apply(v: Viewer, ent?: Ent): Promise { const result = await this.fn(v, ent); @@ -556,10 +816,10 @@ export class AllowIfConditionAppliesRule implements PrivacyPolicyRule { } interface DelayedFuncRule { - (v: Viewer, ent?: Ent): - | null - | PrivacyPolicyRule - | Promise; + ( + v: Viewer, + ent?: Ent, + ): null | PrivacyPolicyRule | Promise; } // use this when there's a computation needed to get the rule and then the privacy is applied on said rule