Skip to content

Commit

Permalink
feat: add previousValue to privacy policy context for updates (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
wschurman authored May 31, 2024
1 parent 60fc9a4 commit af495a9
Show file tree
Hide file tree
Showing 26 changed files with 371 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ export default class AllowIfUserOwnerPrivacyRule<
async evaluateAsync(
viewerContext: ExampleViewerContext,
_queryContext: EntityQueryContext,
_evaluationContext: EntityPrivacyPolicyEvaluationContext,
_evaluationContext: EntityPrivacyPolicyEvaluationContext<
TFields,
TID,
ExampleViewerContext,
TEntity,
TSelectedFields
>,
entity: TEntity
): Promise<RuleEvaluationResult> {
if (viewerContext.isUserViewerContext()) {
Expand Down
8 changes: 4 additions & 4 deletions packages/entity/src/EntityAssociationLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export default class EntityAssociationLoader<
.getViewerContext()
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
.getLoaderFactory()
.forLoad(queryContext, { cascadingDeleteCause: null });
.forLoad(queryContext, { previousValue: null, cascadingDeleteCause: null });

return (await loader.loadByIDAsync(associatedEntityID as unknown as TAssociatedID)) as Result<
null extends TFields[TIdentifyingField] ? TAssociatedEntity | null : TAssociatedEntity
Expand Down Expand Up @@ -128,7 +128,7 @@ export default class EntityAssociationLoader<
.getViewerContext()
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
.getLoaderFactory()
.forLoad(queryContext, { cascadingDeleteCause: null });
.forLoad(queryContext, { previousValue: null, cascadingDeleteCause: null });
return await loader.loadManyByFieldEqualingAsync(
associatedEntityFieldContainingThisID,
thisID as any
Expand Down Expand Up @@ -185,7 +185,7 @@ export default class EntityAssociationLoader<
.getViewerContext()
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
.getLoaderFactory()
.forLoad(queryContext, { cascadingDeleteCause: null });
.forLoad(queryContext, { previousValue: null, cascadingDeleteCause: null });
return await loader.loadByFieldEqualingAsync(
associatedEntityLookupByField,
associatedFieldValue as any
Expand Down Expand Up @@ -243,7 +243,7 @@ export default class EntityAssociationLoader<
.getViewerContext()
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
.getLoaderFactory()
.forLoad(queryContext, { cascadingDeleteCause: null });
.forLoad(queryContext, { previousValue: null, cascadingDeleteCause: null });
return await loader.loadManyByFieldEqualingAsync(
associatedEntityLookupByField,
associatedFieldValue as any
Expand Down
8 changes: 7 additions & 1 deletion packages/entity/src/EntityLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ export default class EntityLoader<
constructor(
private readonly viewerContext: TViewerContext,
private readonly queryContext: EntityQueryContext,
private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext,
private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
TFields,
TID,
TViewerContext,
TEntity,
TSelectedFields
>,
private readonly entityConfiguration: EntityConfiguration<TFields>,
private readonly entityClass: IEntityClass<
TFields,
Expand Down
8 changes: 7 additions & 1 deletion packages/entity/src/EntityLoaderFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ export default class EntityLoaderFactory<
forLoad(
viewerContext: TViewerContext,
queryContext: EntityQueryContext,
privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
TFields,
TID,
TViewerContext,
TEntity,
TSelectedFields
>
): EntityLoader<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
return new EntityLoader(
viewerContext,
Expand Down
14 changes: 10 additions & 4 deletions packages/entity/src/EntityMutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ export class CreateMutator<
this.validateFields(this.fieldsForEntity);

const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext, {
previousValue: null,
cascadingDeleteCause: null,
});

Expand All @@ -224,7 +225,7 @@ export class CreateMutator<
this.privacyPolicy.authorizeCreateAsync(
this.viewerContext,
queryContext,
{ cascadingDeleteCause: null },
{ previousValue: null, cascadingDeleteCause: null },
temporaryEntityForPrivacyCheck,
this.metricsAdapter
)
Expand Down Expand Up @@ -423,6 +424,7 @@ export class UpdateMutator<
this.validateFields(this.updatedFields);

const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext, {
previousValue: this.originalEntity,
cascadingDeleteCause,
});

Expand All @@ -431,7 +433,7 @@ export class UpdateMutator<
this.privacyPolicy.authorizeUpdateAsync(
this.viewerContext,
queryContext,
{ cascadingDeleteCause },
{ previousValue: this.originalEntity, cascadingDeleteCause },
entityAboutToBeUpdated,
this.metricsAdapter
)
Expand Down Expand Up @@ -633,7 +635,7 @@ export class DeleteMutator<
this.privacyPolicy.authorizeDeleteAsync(
this.viewerContext,
queryContext,
{ cascadingDeleteCause },
{ previousValue: null, cascadingDeleteCause },
this.entity,
this.metricsAdapter
)
Expand Down Expand Up @@ -671,6 +673,7 @@ export class DeleteMutator<
}

const entityLoader = this.entityLoaderFactory.forLoad(this.viewerContext, queryContext, {
previousValue: null,
cascadingDeleteCause,
});
queryContext.appendPostCommitInvalidationCallback(
Expand Down Expand Up @@ -774,7 +777,10 @@ export class DeleteMutator<
}

const inboundReferenceEntities = await loaderFactory
.forLoad(queryContext, { cascadingDeleteCause: newCascadingDeleteCause })
.forLoad(queryContext, {
previousValue: null,
cascadingDeleteCause: newCascadingDeleteCause,
})
.enforcing()
.loadManyByFieldEqualingAsync(
fieldName,
Expand Down
62 changes: 55 additions & 7 deletions packages/entity/src/EntityPrivacyPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,19 @@ import PrivacyPolicyRule, { RuleEvaluationResult } from './rules/PrivacyPolicyRu
/**
* Information about the reason this privacy policy is being evaluated.
*/
export type EntityPrivacyPolicyEvaluationContext = {
export type EntityPrivacyPolicyEvaluationContext<
TFields extends object,
TID extends NonNullable<TFields[TSelectedFields]>,
TViewerContext extends ViewerContext,
TEntity extends ReadonlyEntity<TFields, TID, TViewerContext, TSelectedFields>,
TSelectedFields extends keyof TFields = keyof TFields
> = {
/**
* When this privacy policy is being evaluated as a result of an update, this will be populated with the value
* of the entity before the update. Note that this doesn't only apply to UPDATE authorization actions though:
* when an entity is updated it is re-LOADed after the update completes.
*/
previousValue: TEntity | null;
/**
* When this privacy policy is being evaluated as a result of a cascading deletion, this will be populated
* with information on the cascading delete.
Expand Down Expand Up @@ -154,7 +166,13 @@ export default abstract class EntityPrivacyPolicy<
async authorizeCreateAsync(
viewerContext: TViewerContext,
queryContext: EntityQueryContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext<
TFields,
TID,
TViewerContext,
TEntity,
TSelectedFields
>,
entity: TEntity,
metricsAdapter: IEntityMetricsAdapter
): Promise<TEntity> {
Expand All @@ -180,7 +198,13 @@ export default abstract class EntityPrivacyPolicy<
async authorizeReadAsync(
viewerContext: TViewerContext,
queryContext: EntityQueryContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext<
TFields,
TID,
TViewerContext,
TEntity,
TSelectedFields
>,
entity: TEntity,
metricsAdapter: IEntityMetricsAdapter
): Promise<TEntity> {
Expand All @@ -206,7 +230,13 @@ export default abstract class EntityPrivacyPolicy<
async authorizeUpdateAsync(
viewerContext: TViewerContext,
queryContext: EntityQueryContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext<
TFields,
TID,
TViewerContext,
TEntity,
TSelectedFields
>,
entity: TEntity,
metricsAdapter: IEntityMetricsAdapter
): Promise<TEntity> {
Expand All @@ -232,7 +262,13 @@ export default abstract class EntityPrivacyPolicy<
async authorizeDeleteAsync(
viewerContext: TViewerContext,
queryContext: EntityQueryContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext<
TFields,
TID,
TViewerContext,
TEntity,
TSelectedFields
>,
entity: TEntity,
metricsAdapter: IEntityMetricsAdapter
): Promise<TEntity> {
Expand All @@ -251,7 +287,13 @@ export default abstract class EntityPrivacyPolicy<
ruleset: readonly PrivacyPolicyRule<TFields, TID, TViewerContext, TEntity, TSelectedFields>[],
viewerContext: TViewerContext,
queryContext: EntityQueryContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext<
TFields,
TID,
TViewerContext,
TEntity,
TSelectedFields
>,
entity: TEntity,
action: EntityAuthorizationAction,
metricsAdapter: IEntityMetricsAdapter
Expand Down Expand Up @@ -354,7 +396,13 @@ export default abstract class EntityPrivacyPolicy<
ruleset: readonly PrivacyPolicyRule<TFields, TID, TViewerContext, TEntity, TSelectedFields>[],
viewerContext: TViewerContext,
queryContext: EntityQueryContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext<
TFields,
TID,
TViewerContext,
TEntity,
TSelectedFields
>,
entity: TEntity,
action: EntityAuthorizationAction
): Promise<TEntity> {
Expand Down
2 changes: 1 addition & 1 deletion packages/entity/src/ReadonlyEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,6 @@ export default abstract class ReadonlyEntity<
return viewerContext
.getViewerScopedEntityCompanionForClass(this)
.getLoaderFactory()
.forLoad(queryContext, { cascadingDeleteCause: null });
.forLoad(queryContext, { previousValue: null, cascadingDeleteCause: null });
}
}
8 changes: 7 additions & 1 deletion packages/entity/src/ViewerScopedEntityLoaderFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ export default class ViewerScopedEntityLoaderFactory<

forLoad(
queryContext: EntityQueryContext,
privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext
privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext<
TFields,
TID,
TViewerContext,
TEntity,
TSelectedFields
>
): EntityLoader<TFields, TID, TViewerContext, TEntity, TPrivacyPolicy, TSelectedFields> {
return this.entityLoaderFactory.forLoad(
this.viewerContext,
Expand Down
7 changes: 6 additions & 1 deletion packages/entity/src/__tests__/EntityCommonUseCases-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ class DenyIfNotOwnerPrivacyPolicyRule extends PrivacyPolicyRule<
async evaluateAsync(
viewerContext: TestUserViewerContext,
_queryContext: EntityQueryContext,
_evaluationContext: EntityPrivacyPolicyEvaluationContext,
_evaluationContext: EntityPrivacyPolicyEvaluationContext<
BlahFields,
string,
TestUserViewerContext,
BlahEntity
>,
entity: BlahEntity
): Promise<RuleEvaluationResult> {
if (viewerContext.getUserID() === entity.getField('ownerID')) {
Expand Down
8 changes: 7 additions & 1 deletion packages/entity/src/__tests__/EntityEdges-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,13 @@ const makeEntityClasses = (edgeDeletionBehavior: EntityEdgeDeletionBehavior) =>
async evaluateAsync(
_viewerContext: TestViewerContext,
_queryContext: EntityQueryContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext,
evaluationContext: EntityPrivacyPolicyEvaluationContext<
any,
string,
TestViewerContext,
any,
any
>,
entity: any
): Promise<RuleEvaluationResult> {
if (privacyPolicyEvaluationRecords.shouldRecord) {
Expand Down
12 changes: 11 additions & 1 deletion packages/entity/src/__tests__/EntityLoader-constructor-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,17 @@ export default class TestEntity extends Entity<
describe(EntityLoader, () => {
it('handles thrown errors and literals from constructor', async () => {
const viewerContext = instance(mock(ViewerContext));
const privacyPolicyEvaluationContext = instance(mock<EntityPrivacyPolicyEvaluationContext>());
const privacyPolicyEvaluationContext = instance(
mock<
EntityPrivacyPolicyEvaluationContext<
TestFields,
string,
ViewerContext,
TestEntity,
TestFieldSelection
>
>()
);
const metricsAdapter = instance(mock<IEntityMetricsAdapter>());
const queryContext = StubQueryContextProvider.getQueryContext();

Expand Down
Loading

0 comments on commit af495a9

Please sign in to comment.