From 709bab178b94933f52f0c6877c1b1c33e6cb342c Mon Sep 17 00:00:00 2001 From: ian Date: Sat, 11 Dec 2021 03:17:47 +0800 Subject: [PATCH] Separate caching of dynamic user vars from permissions (#9957) * Separate caching of dynamic user vars from permissions * Cache filterContext only when permissions are cached * Reset merge * Reapply changes * Reduce nesting * Add missing assignment Co-authored-by: rijkvanzanten --- api/src/utils/get-permissions.ts | 190 ++++++++++++++++++++----------- 1 file changed, 123 insertions(+), 67 deletions(-) diff --git a/api/src/utils/get-permissions.ts b/api/src/utils/get-permissions.ts index 33c6b07a04d89..61b799e01eea2 100644 --- a/api/src/utils/get-permissions.ts +++ b/api/src/utils/get-permissions.ts @@ -13,7 +13,7 @@ import { SchemaOverview } from '../types'; export async function getPermissions(accountability: Accountability, schema: SchemaOverview) { const database = getDatabase(); - const { systemCache } = getCache(); + const { systemCache, cache } = getCache(); let permissions: Permission[] = []; @@ -24,7 +24,31 @@ export async function getPermissions(accountability: Accountability, schema: Sch const cachedPermissions = await systemCache.get(cacheKey); if (cachedPermissions) { - return cachedPermissions; + if (!cachedPermissions.containDynamicData) { + return processPermissions(accountability, cachedPermissions.permissions, {}); + } + + const cachedFilterContext = await cache?.get( + `filterContext-${hash({ user, role, permissions: cachedPermissions.permissions })}` + ); + + if (cachedFilterContext) { + return processPermissions(accountability, cachedPermissions.permissions, cachedFilterContext); + } else { + let requiredPermissionData, containDynamicData; + + ({ permissions, requiredPermissionData, containDynamicData } = parsePermissions(cachedPermissions.permissions)); + + const filterContext = containDynamicData + ? await getFilterContext(schema, accountability, requiredPermissionData) + : {}; + + if (containDynamicData && env.CACHE_ENABLED !== false) { + await cache?.set(`filterContext-${hash({ user, role, permissions })}`, filterContext); + } + + return processPermissions(accountability, permissions, filterContext); + } } } @@ -34,93 +58,125 @@ export async function getPermissions(accountability: Accountability, schema: Sch .from('directus_permissions') .where({ role: accountability.role }); - const requiredPermissionData = { - $CURRENT_USER: [] as string[], - $CURRENT_ROLE: [] as string[], - }; + let requiredPermissionData, containDynamicData; - permissions = permissionsForRole.map((permissionRaw) => { - const permission = cloneDeep(permissionRaw); + ({ permissions, requiredPermissionData, containDynamicData } = parsePermissions(permissionsForRole)); - if (permission.permissions && typeof permission.permissions === 'string') { - permission.permissions = JSON.parse(permission.permissions); - } else if (permission.permissions === null) { - permission.permissions = {}; - } + if (accountability.app === true) { + permissions = mergePermissions( + permissions, + appAccessMinimalPermissions.map((perm) => ({ ...perm, role: accountability!.role })) + ); + } - if (permission.validation && typeof permission.validation === 'string') { - permission.validation = JSON.parse(permission.validation); - } else if (permission.validation === null) { - permission.validation = {}; - } + const filterContext = containDynamicData + ? await getFilterContext(schema, accountability, requiredPermissionData) + : {}; - if (permission.presets && typeof permission.presets === 'string') { - permission.presets = JSON.parse(permission.presets); - } else if (permission.presets === null) { - permission.presets = {}; - } + if (env.CACHE_PERMISSIONS !== false) { + await systemCache.set(cacheKey, { permissions, containDynamicData }); - if (permission.fields && typeof permission.fields === 'string') { - permission.fields = permission.fields.split(','); - } else if (permission.fields === null) { - permission.fields = []; + if (containDynamicData && env.CACHE_ENABLED !== false) { + await cache?.set(`filterContext-${hash({ user, role, permissions })}`, filterContext); } + } - const extractPermissionData = (val: any) => { - if (typeof val === 'string' && val.startsWith('$CURRENT_USER.')) { - requiredPermissionData.$CURRENT_USER.push(val.replace('$CURRENT_USER.', '')); - } + return processPermissions(accountability, permissions, filterContext); + } - if (typeof val === 'string' && val.startsWith('$CURRENT_ROLE.')) { - requiredPermissionData.$CURRENT_ROLE.push(val.replace('$CURRENT_ROLE.', '')); - } + return permissions; +} - return val; - }; +function parsePermissions(permissions: any[]) { + const requiredPermissionData = { + $CURRENT_USER: [] as string[], + $CURRENT_ROLE: [] as string[], + }; - deepMap(permission.permissions, extractPermissionData); - deepMap(permission.validation, extractPermissionData); - deepMap(permission.presets, extractPermissionData); + let containDynamicData = false; - return permission; - }); + permissions = permissions.map((permissionRaw) => { + const permission = cloneDeep(permissionRaw); - if (accountability.app === true) { - permissions = mergePermissions( - permissions, - appAccessMinimalPermissions.map((perm) => ({ ...perm, role: accountability!.role })) - ); + if (permission.permissions && typeof permission.permissions === 'string') { + permission.permissions = JSON.parse(permission.permissions); + } else if (permission.permissions === null) { + permission.permissions = {}; } - const usersService = new UsersService({ schema }); - const rolesService = new RolesService({ schema }); - - const filterContext: Record = {}; + if (permission.validation && typeof permission.validation === 'string') { + permission.validation = JSON.parse(permission.validation); + } else if (permission.validation === null) { + permission.validation = {}; + } - if (accountability.user && requiredPermissionData.$CURRENT_USER.length > 0) { - filterContext.$CURRENT_USER = await usersService.readOne(accountability.user, { - fields: requiredPermissionData.$CURRENT_USER, - }); + if (permission.presets && typeof permission.presets === 'string') { + permission.presets = JSON.parse(permission.presets); + } else if (permission.presets === null) { + permission.presets = {}; } - if (accountability.role && requiredPermissionData.$CURRENT_ROLE.length > 0) { - filterContext.$CURRENT_ROLE = await rolesService.readOne(accountability.role, { - fields: requiredPermissionData.$CURRENT_ROLE, - }); + if (permission.fields && typeof permission.fields === 'string') { + permission.fields = permission.fields.split(','); + } else if (permission.fields === null) { + permission.fields = []; } - permissions = permissions.map((permission) => { - permission.permissions = parseFilter(permission.permissions, accountability!, filterContext); - permission.validation = parseFilter(permission.validation, accountability!, filterContext); - permission.presets = parseFilter(permission.presets, accountability!, filterContext); + const extractPermissionData = (val: any) => { + if (typeof val === 'string' && val.startsWith('$CURRENT_USER.')) { + requiredPermissionData.$CURRENT_USER.push(val.replace('$CURRENT_USER.', '')); + containDynamicData = true; + } + + if (typeof val === 'string' && val.startsWith('$CURRENT_ROLE.')) { + requiredPermissionData.$CURRENT_ROLE.push(val.replace('$CURRENT_ROLE.', '')); + containDynamicData = true; + } + + return val; + }; + + deepMap(permission.permissions, extractPermissionData); + deepMap(permission.validation, extractPermissionData); + deepMap(permission.presets, extractPermissionData); + + return permission; + }); + + return { permissions, requiredPermissionData, containDynamicData }; +} - return permission; +async function getFilterContext(schema: SchemaOverview, accountability: Accountability, requiredPermissionData: any) { + const usersService = new UsersService({ schema }); + const rolesService = new RolesService({ schema }); + + const filterContext: Record = {}; + + if (accountability.user && requiredPermissionData.$CURRENT_USER.length > 0) { + filterContext.$CURRENT_USER = await usersService.readOne(accountability.user, { + fields: requiredPermissionData.$CURRENT_USER, }); + } - if (env.CACHE_PERMISSIONS !== false) { - await systemCache.set(cacheKey, permissions); - } + if (accountability.role && requiredPermissionData.$CURRENT_ROLE.length > 0) { + filterContext.$CURRENT_ROLE = await rolesService.readOne(accountability.role, { + fields: requiredPermissionData.$CURRENT_ROLE, + }); } - return permissions; + return filterContext; +} + +function processPermissions( + accountability: Accountability, + permissions: Permission[], + filterContext: Record +) { + return permissions.map((permission) => { + permission.permissions = parseFilter(permission.permissions, accountability!, filterContext); + permission.validation = parseFilter(permission.validation, accountability!, filterContext); + permission.presets = parseFilter(permission.presets, accountability!, filterContext); + + return permission; + }); }