Skip to content

Commit

Permalink
Separate caching of dynamic user vars from permissions (#9957)
Browse files Browse the repository at this point in the history
* 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 <rijkvanzanten@me.com>
  • Loading branch information
licitdev and rijkvanzanten committed Dec 10, 2021
1 parent 99c8f59 commit 709bab1
Showing 1 changed file with 123 additions and 67 deletions.
190 changes: 123 additions & 67 deletions api/src/utils/get-permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];

Expand All @@ -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);
}
}
}

Expand All @@ -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<string, any> = {};
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<string, any> = {};

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<string, any>
) {
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;
});
}

0 comments on commit 709bab1

Please sign in to comment.