Skip to content

Commit

Permalink
feat: add authorization metrics (#142)
Browse files Browse the repository at this point in the history
  • Loading branch information
wschurman authored Oct 25, 2021
1 parent 1405a91 commit 7cf54d8
Show file tree
Hide file tree
Showing 15 changed files with 401 additions and 104 deletions.
12 changes: 10 additions & 2 deletions packages/entity/src/Entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,16 @@ export default abstract class Entity<
.getQueryContextProvider()
.getQueryContext()
): Promise<boolean> {
const companion = existingEntity
.getViewerContext()
.getViewerScopedEntityCompanionForClass(this);
const privacyPolicy = new (this.getCompanionDefinition().privacyPolicyClass)();
const evaluationResult = await asyncResult(
privacyPolicy.authorizeUpdateAsync(
existingEntity.getViewerContext(),
queryContext,
existingEntity
existingEntity,
companion.getMetricsAdapter()
)
);
return evaluationResult.ok;
Expand Down Expand Up @@ -292,12 +296,16 @@ export default abstract class Entity<
.getQueryContextProvider()
.getQueryContext()
): Promise<boolean> {
const companion = existingEntity
.getViewerContext()
.getViewerScopedEntityCompanionForClass(this);
const privacyPolicy = new (this.getCompanionDefinition().privacyPolicyClass)();
const evaluationResult = await asyncResult(
privacyPolicy.authorizeDeleteAsync(
existingEntity.getViewerContext(),
queryContext,
existingEntity
existingEntity,
companion.getMetricsAdapter()
)
);
return evaluationResult.ok;
Expand Down
12 changes: 10 additions & 2 deletions packages/entity/src/EntityCompanion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default class EntityCompanion<
TEntity,
TSelectedFields
>,
metricsAdapter: IEntityMetricsAdapter
private readonly metricsAdapter: IEntityMetricsAdapter
) {
const privacyPolicy = new PrivacyPolicyClass();
this.entityLoaderFactory = new EntityLoaderFactory<
Expand All @@ -87,7 +87,8 @@ export default class EntityCompanion<
tableDataCoordinator.entityConfiguration,
entityClass,
privacyPolicy,
tableDataCoordinator.dataManager
tableDataCoordinator.dataManager,
metricsAdapter
);
this.entityMutatorFactory = new EntityMutatorFactory(
tableDataCoordinator.entityConfiguration,
Expand Down Expand Up @@ -129,4 +130,11 @@ export default class EntityCompanion<
getQueryContextProvider(): EntityQueryContextProvider {
return this.tableDataCoordinator.getQueryContextProvider();
}

/**
* Get the {@link IEntityMetricsAdapter} for this companion.
*/
getMetricsAdapter(): IEntityMetricsAdapter {
return this.metricsAdapter;
}
}
2 changes: 1 addition & 1 deletion packages/entity/src/EntityCompanionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export default class EntityCompanionProvider {
* @param cacheAdapterFlavors - Cache adapter configurations for this instance
*/
constructor(
private metricsAdapter: IEntityMetricsAdapter,
public readonly metricsAdapter: IEntityMetricsAdapter,
private databaseAdapterFlavors: ReadonlyMap<
DatabaseAdapterFlavor,
DatabaseAdapterFlavorDefinition
Expand Down
13 changes: 9 additions & 4 deletions packages/entity/src/EntityLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import ViewerContext from './ViewerContext';
import EntityInvalidFieldValueError from './errors/EntityInvalidFieldValueError';
import EntityNotFoundError from './errors/EntityNotFoundError';
import EntityDataManager from './internal/EntityDataManager';
import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';
import { mapMap, mapMapAsync } from './utils/collections/maps';

/**
Expand Down Expand Up @@ -49,7 +50,8 @@ export default class EntityLoader<
TSelectedFields
>,
private readonly privacyPolicy: TPrivacyPolicy,
private readonly dataManager: EntityDataManager<TFields>
private readonly dataManager: EntityDataManager<TFields>,
protected readonly metricsAdapter: IEntityMetricsAdapter
) {}

/**
Expand Down Expand Up @@ -216,7 +218,8 @@ export default class EntityLoader<
this.privacyPolicy.authorizeReadAsync(
this.viewerContext,
this.queryContext,
uncheckedEntityResult.value
uncheckedEntityResult.value,
this.metricsAdapter
)
);
})
Expand Down Expand Up @@ -269,7 +272,8 @@ export default class EntityLoader<
this.privacyPolicy.authorizeReadAsync(
this.viewerContext,
this.queryContext,
uncheckedEntityResult.value
uncheckedEntityResult.value,
this.metricsAdapter
)
);
})
Expand Down Expand Up @@ -328,7 +332,8 @@ export default class EntityLoader<
this.privacyPolicy.authorizeReadAsync(
this.viewerContext,
this.queryContext,
uncheckedEntityResult.value
uncheckedEntityResult.value,
this.metricsAdapter
)
);
})
Expand Down
7 changes: 5 additions & 2 deletions packages/entity/src/EntityLoaderFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { EntityQueryContext } from './EntityQueryContext';
import ReadonlyEntity from './ReadonlyEntity';
import ViewerContext from './ViewerContext';
import EntityDataManager from './internal/EntityDataManager';
import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';

/**
* The primary entry point for loading entities.
Expand Down Expand Up @@ -35,7 +36,8 @@ export default class EntityLoaderFactory<
TSelectedFields
>,
private readonly privacyPolicyClass: TPrivacyPolicy,
private readonly dataManager: EntityDataManager<TFields>
private readonly dataManager: EntityDataManager<TFields>,
protected readonly metricsAdapter: IEntityMetricsAdapter
) {}

/**
Expand All @@ -53,7 +55,8 @@ export default class EntityLoaderFactory<
this.entityConfiguration,
this.entityClass,
this.privacyPolicyClass,
this.dataManager
this.dataManager,
this.metricsAdapter
);
}
}
13 changes: 10 additions & 3 deletions packages/entity/src/EntityMutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ export class CreateMutator<
this.privacyPolicy.authorizeCreateAsync(
this.viewerContext,
queryContext,
temporaryEntityForPrivacyCheck
temporaryEntityForPrivacyCheck,
this.metricsAdapter
)
);
if (!authorizeCreateResult.ok) {
Expand Down Expand Up @@ -408,7 +409,8 @@ export class UpdateMutator<
this.privacyPolicy.authorizeUpdateAsync(
this.viewerContext,
queryContext,
entityAboutToBeUpdated
entityAboutToBeUpdated,
this.metricsAdapter
)
);
if (!authorizeUpdateResult.ok) {
Expand Down Expand Up @@ -592,7 +594,12 @@ export class DeleteMutator<
skipDatabaseDeletion: boolean
): Promise<Result<void>> {
const authorizeDeleteResult = await asyncResult(
this.privacyPolicy.authorizeDeleteAsync(this.viewerContext, queryContext, this.entity)
this.privacyPolicy.authorizeDeleteAsync(
this.viewerContext,
queryContext,
this.entity,
this.metricsAdapter
)
);
if (!authorizeDeleteResult.ok) {
return authorizeDeleteResult;
Expand Down
94 changes: 76 additions & 18 deletions packages/entity/src/EntityPrivacyPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { EntityQueryContext } from './EntityQueryContext';
import ReadonlyEntity from './ReadonlyEntity';
import ViewerContext from './ViewerContext';
import EntityNotAuthorizedError from './errors/EntityNotAuthorizedError';
import IEntityMetricsAdapter, {
EntityMetricsAuthorizationResult,
} from './metrics/IEntityMetricsAdapter';
import PrivacyPolicyRule, { RuleEvaluationResult } from './rules/PrivacyPolicyRule';

export enum EntityPrivacyPolicyEvaluationMode {
Expand Down Expand Up @@ -124,14 +127,16 @@ export default abstract class EntityPrivacyPolicy<
async authorizeCreateAsync(
viewerContext: TViewerContext,
queryContext: EntityQueryContext,
entity: TEntity
entity: TEntity,
metricsAdapter: IEntityMetricsAdapter
): Promise<TEntity> {
return await this.authorizeForRulesetAsync(
this.createRules,
viewerContext,
queryContext,
entity,
EntityAuthorizationAction.CREATE
EntityAuthorizationAction.CREATE,
metricsAdapter
);
}

Expand All @@ -146,14 +151,16 @@ export default abstract class EntityPrivacyPolicy<
async authorizeReadAsync(
viewerContext: TViewerContext,
queryContext: EntityQueryContext,
entity: TEntity
entity: TEntity,
metricsAdapter: IEntityMetricsAdapter
): Promise<TEntity> {
return await this.authorizeForRulesetAsync(
this.readRules,
viewerContext,
queryContext,
entity,
EntityAuthorizationAction.READ
EntityAuthorizationAction.READ,
metricsAdapter
);
}

Expand All @@ -168,14 +175,16 @@ export default abstract class EntityPrivacyPolicy<
async authorizeUpdateAsync(
viewerContext: TViewerContext,
queryContext: EntityQueryContext,
entity: TEntity
entity: TEntity,
metricsAdapter: IEntityMetricsAdapter
): Promise<TEntity> {
return await this.authorizeForRulesetAsync(
this.updateRules,
viewerContext,
queryContext,
entity,
EntityAuthorizationAction.UPDATE
EntityAuthorizationAction.UPDATE,
metricsAdapter
);
}

Expand All @@ -190,14 +199,16 @@ export default abstract class EntityPrivacyPolicy<
async authorizeDeleteAsync(
viewerContext: TViewerContext,
queryContext: EntityQueryContext,
entity: TEntity
entity: TEntity,
metricsAdapter: IEntityMetricsAdapter
): Promise<TEntity> {
return await this.authorizeForRulesetAsync(
this.deleteRules,
viewerContext,
queryContext,
entity,
EntityAuthorizationAction.DELETE
EntityAuthorizationAction.DELETE,
metricsAdapter
);
}

Expand All @@ -206,48 +217,95 @@ export default abstract class EntityPrivacyPolicy<
viewerContext: TViewerContext,
queryContext: EntityQueryContext,
entity: TEntity,
action: EntityAuthorizationAction
action: EntityAuthorizationAction,
metricsAdapter: IEntityMetricsAdapter
): Promise<TEntity> {
const privacyPolicyEvaluator = this.getPrivacyPolicyEvaluator(viewerContext);
switch (privacyPolicyEvaluator.mode) {
case EntityPrivacyPolicyEvaluationMode.ENFORCE:
return await this.authorizeForRulesetInnerAsync(
ruleset,
viewerContext,
queryContext,
entity,
action
);
try {
const result = await this.authorizeForRulesetInnerAsync(
ruleset,
viewerContext,
queryContext,
entity,
action
);
metricsAdapter.logAuthorizationEvent({
entityClassName: entity.constructor.name,
action,
evaluationResult: EntityMetricsAuthorizationResult.ALLOW,
privacyPolicyEvaluationMode: privacyPolicyEvaluator.mode,
});
return result;
} catch (e) {
if (!(e instanceof EntityNotAuthorizedError)) {
throw e;
}
metricsAdapter.logAuthorizationEvent({
entityClassName: entity.constructor.name,
action,
evaluationResult: EntityMetricsAuthorizationResult.DENY,
privacyPolicyEvaluationMode: privacyPolicyEvaluator.mode,
});
throw e;
}
case EntityPrivacyPolicyEvaluationMode.ENFORCE_AND_LOG:
try {
return await this.authorizeForRulesetInnerAsync(
const result = await this.authorizeForRulesetInnerAsync(
ruleset,
viewerContext,
queryContext,
entity,
action
);
metricsAdapter.logAuthorizationEvent({
entityClassName: entity.constructor.name,
action,
evaluationResult: EntityMetricsAuthorizationResult.ALLOW,
privacyPolicyEvaluationMode: privacyPolicyEvaluator.mode,
});
return result;
} catch (e) {
if (!(e instanceof EntityNotAuthorizedError)) {
throw e;
}
privacyPolicyEvaluator.denyHandler(e);
metricsAdapter.logAuthorizationEvent({
entityClassName: entity.constructor.name,
action,
evaluationResult: EntityMetricsAuthorizationResult.DENY,
privacyPolicyEvaluationMode: privacyPolicyEvaluator.mode,
});
throw e;
}
case EntityPrivacyPolicyEvaluationMode.DRY_RUN:
try {
return await this.authorizeForRulesetInnerAsync(
const result = await this.authorizeForRulesetInnerAsync(
ruleset,
viewerContext,
queryContext,
entity,
action
);
metricsAdapter.logAuthorizationEvent({
entityClassName: entity.constructor.name,
action,
evaluationResult: EntityMetricsAuthorizationResult.ALLOW,
privacyPolicyEvaluationMode: privacyPolicyEvaluator.mode,
});
return result;
} catch (e) {
if (!(e instanceof EntityNotAuthorizedError)) {
throw e;
}
privacyPolicyEvaluator.denyHandler(e);
metricsAdapter.logAuthorizationEvent({
entityClassName: entity.constructor.name,
action,
evaluationResult: EntityMetricsAuthorizationResult.DENY,
privacyPolicyEvaluationMode: privacyPolicyEvaluator.mode,
});
return entity;
}
}
Expand Down
8 changes: 8 additions & 0 deletions packages/entity/src/ViewerScopedEntityCompanion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ReadonlyEntity from './ReadonlyEntity';
import ViewerContext from './ViewerContext';
import ViewerScopedEntityLoaderFactory from './ViewerScopedEntityLoaderFactory';
import ViewerScopedEntityMutatorFactory from './ViewerScopedEntityMutatorFactory';
import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';

/**
* Provides a simpler API for loading and mutating entities by injecting the {@link ViewerContext}
Expand Down Expand Up @@ -76,4 +77,11 @@ export default class ViewerScopedEntityCompanion<
getQueryContextProvider(): EntityQueryContextProvider {
return this.entityCompanion.getQueryContextProvider();
}

/**
* Get the {@link IEntityMetricsAdapter} for this companion.
*/
getMetricsAdapter(): IEntityMetricsAdapter {
return this.entityCompanion.getMetricsAdapter();
}
}
Loading

0 comments on commit 7cf54d8

Please sign in to comment.