From 2c089b11eaf44489c56ce5e0ea4838928a5d2608 Mon Sep 17 00:00:00 2001 From: getlarge Date: Mon, 23 Mar 2020 07:15:11 +0100 Subject: [PATCH] improve permission types --- src/engine/action/Action.ts | 24 +- src/engine/permission/Permission.ts | 336 +++++++++++++++++++--------- 2 files changed, 249 insertions(+), 111 deletions(-) diff --git a/src/engine/action/Action.ts b/src/engine/action/Action.ts index 50e7ebda..e60dc843 100644 --- a/src/engine/action/Action.ts +++ b/src/engine/action/Action.ts @@ -41,8 +41,8 @@ export class Action { type: string; permissions: Function | Permission | Permission[]; private _permissions: Function | Permission | Permission[]; - private _defaultPermissions: Function | Permission | Permission[]; - descriptionPermissions: string | false; + private _defaultPermissions: Permission | Permission[]; + descriptionPermissions: string | boolean; postProcessor: Function; constructor(setup: ActionSetup = {} as ActionSetup) { @@ -190,13 +190,15 @@ export class Action { return !!this.output; } - _processPermissions(): null | Function { + _processPermissions(): null | Permission | Permission[] { if (this._permissions) { if (isFunction(this._permissions)) { const permissionsFn = this._permissions as Function; - return processActionPermissions(this, permissionsFn); + const permissions: Permission | Permission[] = permissionsFn(); + return processActionPermissions(this, permissions); } - return processActionPermissions(this, this._permissions); + const permissions = this._permissions as Permission | Permission[]; + return processActionPermissions(this, permissions); // const permissions = isFunction(this._permissions) // ? this._permissions() @@ -211,9 +213,15 @@ export class Action { _generatePermissionDescriptions(): void { if (this.permissions) { - this.descriptionPermissions = generatePermissionDescription( - this.permissions, - ); + let permissions: Permission | Permission[]; + if (isFunction(this._permissions)) { + const permissionsFn = this._permissions as Function; + permissions = permissionsFn(); + } else { + permissions = this._permissions as Permission | Permission[]; + } + + this.descriptionPermissions = generatePermissionDescription(permissions); } } diff --git a/src/engine/permission/Permission.ts b/src/engine/permission/Permission.ts index 9f872a24..7406f85d 100644 --- a/src/engine/permission/Permission.ts +++ b/src/engine/permission/Permission.ts @@ -25,6 +25,12 @@ const compatibilityList = [ ['role', 'userAttribute', 'lookup', 'value', 'state'], ]; +export type PermissionMap = { + read?: Permission | Permission[]; + find?: Permission | Permission[]; + mutations?: {} | Permission | Permission[]; +}; + export class Permission { /* eslint-disable no-undef */ @@ -47,7 +53,7 @@ export class Permission { return this; } - _checkCompatibility(type) { + _checkCompatibility(type): void { this.types[type] = true; const found = compatibilityList.find(list => { @@ -68,21 +74,21 @@ export class Permission { ); } - everyone() { + everyone(): Permission { this.isEmpty = false; this._checkCompatibility('everyone'); this.everyoneCanAccess = true; return this; } - authenticated() { + authenticated(): Permission { this.isEmpty = false; this._checkCompatibility('authenticated'); this.authenticatedCanAccess = true; return this; } - role(name) { + role(name: string): Permission { this.isEmpty = false; this._checkCompatibility('role'); @@ -98,7 +104,7 @@ export class Permission { return this; } - userAttribute(attributeName) { + userAttribute(attributeName: string): Permission { this.isEmpty = false; this._checkCompatibility('userAttribute'); @@ -118,7 +124,7 @@ export class Permission { return this; } - lookup(entity, lookupMap) { + lookup(entity: Entity | ViewEntity, lookupMap: object): Permission { this.isEmpty = false; this._checkCompatibility('lookup'); @@ -139,7 +145,7 @@ export class Permission { return this; } - value(attributeName, value) { + value(attributeName: string, value: any): Permission { this.isEmpty = false; this._checkCompatibility('value'); @@ -160,7 +166,7 @@ export class Permission { return this; } - state(stateName) { + state(stateName: string): Permission { this.isEmpty = false; this._checkCompatibility('state'); @@ -174,16 +180,16 @@ export class Permission { return this; } - toString() { + toString(): string { return 'Permission Object'; } } -export const isPermission = (obj: any) => { +export const isPermission = (obj: any): boolean => { return obj instanceof Permission; }; -export const isPermissionsArray = (obj?: any) => { +export const isPermissionsArray = (obj?: any): boolean => { if (isArray(obj, true)) { return obj.reduce( (prev, permission) => prev && isPermission(permission), @@ -195,18 +201,20 @@ export const isPermissionsArray = (obj?: any) => { export const findInvalidPermissionAttributes = ( permission: Permission, - entity: Entity, + entity: Entity | ViewEntity, ) => { const attributes = entity.getAttributes(); permission.userAttributes.map(userAttribute => { const attribute = attributes[userAttribute]; const attributeTypeAsEntity = attribute.type as Entity; + const entityAsEntity = entity as Entity; passOrThrow( attribute && (isDataTypeUser(attribute.type) || - (entity.isUserEntity && entity.getPrimaryAttribute() === attribute) || + (entityAsEntity.isUserEntity && + entityAsEntity.getPrimaryAttribute() === attribute) || (isEntity(attribute.type) && attributeTypeAsEntity.isUserEntity)), () => `Cannot use attribute '${userAttribute}' in '${entity.name}.permissions' as 'userAttribute' as it is not a reference to the User entity`, @@ -216,9 +224,9 @@ export const findInvalidPermissionAttributes = ( export const findMissingPermissionAttributes = ( permission: Permission, - permissionEntity: Entity, + permissionEntity: Entity | ViewEntity, mutation?: Mutation, -) => { +): string | boolean => { const entityAttributeNames = Object.keys(permissionEntity.getAttributes()); const missinguserAttribute = permission.userAttributes.find( @@ -228,7 +236,7 @@ export const findMissingPermissionAttributes = ( return missinguserAttribute; } - let missingLookupAttribute; + let missingLookupAttribute: string; permission.lookups.map(lookup => { const { entity: _entity, lookupMap } = lookup; let entity = _entity; @@ -277,7 +285,10 @@ export const findMissingPermissionAttributes = ( return false; }; -export const findMissingPermissionStates = (permission, permissionEntity) => { +export const findMissingPermissionStates = ( + permission: Permission, + permissionEntity: Entity, +) => { const entityStates = permissionEntity.getStates(); if (entityStates) { @@ -291,9 +302,9 @@ export const findMissingPermissionStates = (permission, permissionEntity) => { }; export const validateActionLookupPermission = ( - permission, - permissionAction, -) => { + permission: Permission, + permissionAction: Action, +): void => { permission.lookups.map(lookup => { const { entity: _entity, lookupMap } = lookup; let entity = _entity; @@ -320,10 +331,14 @@ export const validateActionLookupPermission = ( }); }; -export const generatePermissionDescription = permissions => { +export const generatePermissionDescription = ( + permissions: Permission | Permission[], +): string | boolean => { const descriptions = []; - const permissionsArray = isArray(permissions) ? permissions : [permissions]; + const permissionsArray = isArray(permissions as Permission[]) + ? (permissions as Permission[]) + : ([permissions] as Permission[]); permissionsArray.map(permission => { const lines = []; @@ -402,10 +417,10 @@ export const generatePermissionDescription = permissions => { }; export const checkPermissionSimple = ( - permission, + permission: Permission, userId = null, userRoles = [], -) => { +): boolean => { passOrThrow( isPermission(permission), () => 'checkPermissionSimple needs a valid permission object', @@ -448,7 +463,7 @@ export const checkPermissionSimple = ( return false; }; -export const isPermissionSimple = permission => { +export const isPermissionSimple = (permission: any): boolean => { passOrThrow( isPermission(permission), () => 'isPermissionSimple needs a valid permission object', @@ -462,45 +477,58 @@ export const isPermissionSimple = permission => { ); }; +export type UserAttributesPermissionFilter = { + $or?: [{ [attributeName: number]: string | number }]; +}; + export const buildUserAttributesPermissionFilter = ({ permission, userId, }: { permission: Permission; userId: string | number; -}) => { - let where; +}): UserAttributesPermissionFilter | undefined => { + let where: UserAttributesPermissionFilter; if (permission.userAttributes.length > 0) { passOrThrow(userId, () => 'missing userId in permission object'); - - where = where || {}; - where.$or = where.$or || []; + where = {}; + // where = where || {}; + // where.$or = where.$or || []; permission.userAttributes.map(attributeName => { - where.$or.push({ + const userAttrFilter = { [attributeName]: userId, - }); + }; + if (!where.$or) { + where = { $or: [userAttrFilter] }; + } else { + where.$or.push(userAttrFilter); + } }); } return where; }; +export type StatesPermissionFilter = { + $or?: [{ state: { $in: string[] | number[] } }]; +}; + export const buildStatesPermissionFilter = ({ permission, entity, }: { permission: Permission; entity: Entity; -}) => { - let where; +}): StatesPermissionFilter | undefined => { + let where: StatesPermissionFilter; if (permission.states.length > 0) { passOrThrow(entity, () => 'missing entity in permission object'); - - where = where || {}; - where.$or = where.$or || []; + where = {}; + // where = where || {}; + // where.$or = where.$or || []; const states = entity.getStates(); const stateIds = permission.states.map(stateName => { @@ -514,37 +542,66 @@ export const buildStatesPermissionFilter = ({ return state; }); - where.$or.push({ - state: { - $in: stateIds, + where.$or = [ + { + state: { + $in: stateIds, + }, }, - }); + ]; } return where; }; +export type ValuesPermissionFilter = { + $or?: [{ [attributeName: number]: any }]; +}; + export const buildValuesPermissionFilter = ({ permission, }: { permission: Permission; -}) => { - let where; +}): ValuesPermissionFilter | undefined => { + let where: ValuesPermissionFilter; if (permission.values.length > 0) { - where = where || {}; - where.$or = where.$or || []; + where = {}; + // where = where || {}; + // where.$or = where.$or || []; permission.values.map(({ attributeName, value }) => { - where.$or.push({ + const filter = { [attributeName]: value, - }); + }; + if (!where.$or) { + where = { $or: [filter] }; + } else { + where.$or.push(filter); + } }); } return where; }; +// export type LookupsPermissionFilterCondition = { +// targetAttribute: string; +// operator: string; +// value: any; +// }; + +export type LookupsPermissionFilter = { + $or?: [ + { + $sub: { + entity: string; + condition: any[]; + }; + }, + ]; +}; + export const buildLookupsPermissionFilter = async ({ permission, userId, @@ -557,12 +614,13 @@ export const buildLookupsPermissionFilter = async ({ userRoles?: any; input?: any; context?: any; -}) => { - let where; +}): Promise => { + let where: LookupsPermissionFilter; if (permission.lookups.length > 0) { - where = where || {}; - where.$or = where.$or || []; + where = {}; + // where = where || {}; + // where.$or = where.$or || []; await Promise.all( permission.lookups.map(async ({ entity, lookupMap }) => { @@ -615,12 +673,17 @@ export const buildLookupsPermissionFilter = async ({ }), ); - where.$or.push({ + const filter = { $sub: { entity: entity.name, condition, }, - }); + }; + if (!where.$or) { + where.$or = [filter]; + } else { + where.$or.push(filter); + } }), ); } @@ -628,6 +691,15 @@ export const buildLookupsPermissionFilter = async ({ return where; }; +export type PermissionFilterSingle = { + $and?: [ + | UserAttributesPermissionFilter + | StatesPermissionFilter + | ValuesPermissionFilter + | LookupsPermissionFilter, + ]; +}; + export const buildPermissionFilterSingle = async ( permission: Permission, userId?: string | number, @@ -635,8 +707,8 @@ export const buildPermissionFilterSingle = async ( entity?: Entity, input?: any, context?: any, -) => { - let where; +): Promise => { + let where: PermissionFilterSingle; const params = { permission, userId, userRoles, entity, input, context }; @@ -650,14 +722,22 @@ export const buildPermissionFilterSingle = async ( permissionFilters.map(permissionFilter => { if (permissionFilter) { where = where || {}; - where.$and = where.$and || []; - where.$and.push(permissionFilter); + // where.$and = where.$and || []; + if (!where.$and) { + where.$and = [permissionFilter]; + } else { + where.$and.push(permissionFilter); + } } }); return where; }; +export type PermissionFilter = { + $or?: [PermissionFilterSingle]; +}; + export const buildPermissionFilter = async ( _permissions: Permission | Permission[], userId?: string | number, @@ -665,20 +745,24 @@ export const buildPermissionFilter = async ( entity?: Entity, input?: any, context?: any, -) => { - let where; +): Promise => { + let where: PermissionFilter; if (!_permissions) { return where; } - const permissions = isArray(_permissions as any[]) - ? _permissions - : [_permissions]; + const permissions = isArray(_permissions as Permission[]) + ? (_permissions as Permission[]) + : ([_permissions] as Permission[]); + + // const permissions = isArray(_permissions as any[]) + // ? _permissions + // : [_permissions]; let foundSimplePermission = false; - await asyncForEach(permissions as any[], async permission => { + await asyncForEach(permissions, async permission => { if (foundSimplePermission) { return; } @@ -704,8 +788,12 @@ export const buildPermissionFilter = async ( if (permissionFilter) { where = where || {}; - where.$or = where.$or || []; - where.$or.push(permissionFilter); + // where.$or = where.$or || []; + if (!where.$or) { + where.$or = [permissionFilter]; + } else { + where.$or.push(permissionFilter); + } } } }); @@ -713,22 +801,32 @@ export const buildPermissionFilter = async ( return where; }; +export type ActionPermissionFilter = { + $or?: [LookupsPermissionFilter]; +}; + export const buildActionPermissionFilter = async ( - _permissions, - userId, - userRoles, - action, - input, - context, -) => { - let where; - let lookupPermissionEntity; + _permissions: Permission | Permission[], + userId = null, + userRoles = [], + action: Action, + input?: any, + context?: any, +): Promise< +{ where: ActionPermissionFilter; lookupPermissionEntity: Entity } | undefined +> => { + let where: ActionPermissionFilter; + let lookupPermissionEntity: Entity; if (!_permissions) { - return where; + // return where; + return undefined; } - const permissions = isArray(_permissions) ? _permissions : [_permissions]; + // const permissions = isArray(_permissions) ? _permissions : [_permissions]; + const permissions = isArray(_permissions as Permission[]) + ? (_permissions as Permission[]) + : ([_permissions] as Permission[]); let foundSimplePermission = false; @@ -760,8 +858,12 @@ export const buildActionPermissionFilter = async ( if (permissionFilter) { where = where || {}; - where.$or = where.$or || []; - where.$or.push(permissionFilter); + // where.$or = where.$or || []; + if (!where.$or) { + where.$or = [permissionFilter]; + } else { + where.$or.push(permissionFilter); + } lookupPermissionEntity = permission.lookups[0].entity; } @@ -775,7 +877,11 @@ export const buildActionPermissionFilter = async ( }; }; -export const checkPermissionAdvanced = (data, permission, userId = null) => { +export const checkPermissionAdvanced = ( + data: any, + permission: Permission, + userId = null, +): boolean => { passOrThrow( isPermission(permission), () => 'checkPermissionAdvanced needs a valid permission object', @@ -797,16 +903,20 @@ export const checkPermissionAdvanced = (data, permission, userId = null) => { }; const validatePermissionAttributesAndStates = ( - entity, - permissions, - _mutation, -) => { - const permissionsArray = isArray(permissions) ? permissions : [permissions]; - - const mutation = isMutation(_mutation) ? _mutation : null; - + entity: Entity | ViewEntity, + permissions: Permission | Permission[], + _mutation: Mutation | string, +): void => { + // const permissionsArray = isArray(permissions) ? permissions : [permissions]; + const permissionsArray = isArray(permissions as Permission[]) + ? (permissions as Permission[]) + : ([permissions] as Permission[]); + + const mutation = isMutation(_mutation) ? (_mutation as Mutation) : null; + + const mutationAsMutation = _mutation as Mutation; const mutationName = isMutation(_mutation) - ? _mutation.name + ? mutationAsMutation.name : String(_mutation); permissionsArray.map(permission => { @@ -824,8 +934,12 @@ const validatePermissionAttributesAndStates = ( findInvalidPermissionAttributes(permission, entity); - if (entity.getStates) { - const invalidState = findMissingPermissionStates(permission, entity); + const entityAsEntity = entity as Entity; + if (entityAsEntity.getStates) { + const invalidState = findMissingPermissionStates( + permission, + entity as Entity, + ); passOrThrow( !invalidState, @@ -836,9 +950,16 @@ const validatePermissionAttributesAndStates = ( }); }; -const validatePermissionMutationTypes = (entity, permissions, mutation) => { +const validatePermissionMutationTypes = ( + entity: Entity, + permissions: Permission | Permission[], + mutation: Mutation, +): void => { if (mutation.type === MUTATION_TYPE_CREATE) { - const permissionsArray = isArray(permissions) ? permissions : [permissions]; + // const permissionsArray = isArray(permissions) ? permissions : [permissions]; + const permissionsArray = isArray(permissions as Permission[]) + ? (permissions as Permission[]) + : ([permissions] as Permission[]); permissionsArray.map(permission => { passOrThrow( @@ -852,16 +973,19 @@ const validatePermissionMutationTypes = (entity, permissions, mutation) => { } }; -export const hasEmptyPermissions = permissions => { - if (isPermissionsArray(permissions)) { +export const hasEmptyPermissions = ( + _permissions: Permission | Permission[], +): boolean => { + if (isPermissionsArray(_permissions)) { + const permissions = _permissions as Permission[]; const foundEmpty = permissions.find(({ isEmpty }) => isEmpty); return !!foundEmpty; } - - return permissions.isEmpty; + const permission = _permissions as Permission; + return permission.isEmpty; }; -export const findEmptyEntityPermissions = permissions => { +export const findEmptyEntityPermissions = (permissions): string[] => { const emptyPermissionsIn = []; if (permissions.read && hasEmptyPermissions(permissions.read)) { @@ -889,9 +1013,9 @@ export const findEmptyEntityPermissions = permissions => { export const processEntityPermissions = ( entity: Entity, - permissions, + permissions: PermissionMap, defaultPermissions?, -) => { +): PermissionMap => { passOrThrow( isMap(permissions), () => @@ -1000,9 +1124,9 @@ export const processEntityPermissions = ( export const processViewEntityPermissions = ( entity: ViewEntity, - permissions, + permissions: PermissionMap, defaultPermissions?, -) => { +): PermissionMap => { passOrThrow( isMap(permissions), () => @@ -1050,13 +1174,19 @@ export const processViewEntityPermissions = ( return permissions; }; -export const processActionPermissions = (action: Action, permissions) => { +export const processActionPermissions = ( + action: Action, + permissions: Permission | Permission[], +): Permission | Permission[] => { passOrThrow( isPermission(permissions) || isPermissionsArray(permissions), () => `Invalid permission definition for action '${action.name}'`, ); - const permissionsArray = isArray(permissions) ? permissions : [permissions]; + // const permissionsArray = isArray(permissions) ? permissions : [permissions]; + const permissionsArray = isArray(permissions as Permission[]) + ? (permissions as Permission[]) + : ([permissions] as Permission[]); permissionsArray.map(permission => { passOrThrow(