mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
* Utils to compress/decompress data Gzip was chosen because we want smaller data but quick algorithm since this will be ran for every request * Compress system cache * Decompress system cache * Set/Get compressed cache for individual requests * Switch from gzip to snappy, use json compression too * Fix cache exp set/get * Remove unused import Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
201 lines
6.4 KiB
TypeScript
201 lines
6.4 KiB
TypeScript
import { Accountability, Permission, SchemaOverview } from '@directus/shared/types';
|
|
import { deepMap, parseFilter, parseJSON, parsePreset } from '@directus/shared/utils';
|
|
import { cloneDeep } from 'lodash';
|
|
import hash from 'object-hash';
|
|
import { getCache, getSystemCache, setSystemCache } from '../cache';
|
|
import getDatabase from '../database';
|
|
import { appAccessMinimalPermissions } from '../database/system-data/app-access-permissions';
|
|
import env from '../env';
|
|
import { RolesService } from '../services/roles';
|
|
import { UsersService } from '../services/users';
|
|
import { mergePermissions } from '../utils/merge-permissions';
|
|
import { mergePermissionsForShare } from './merge-permissions-for-share';
|
|
|
|
export async function getPermissions(accountability: Accountability, schema: SchemaOverview) {
|
|
const database = getDatabase();
|
|
const { cache } = getCache();
|
|
|
|
let permissions: Permission[] = [];
|
|
|
|
const { user, role, app, admin, share_scope } = accountability;
|
|
const cacheKey = `permissions-${hash({ user, role, app, admin, share_scope })}`;
|
|
|
|
if (env.CACHE_PERMISSIONS !== false) {
|
|
const cachedPermissions = await getSystemCache(cacheKey);
|
|
|
|
if (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 {
|
|
const {
|
|
permissions: parsedPermissions,
|
|
requiredPermissionData,
|
|
containDynamicData,
|
|
} = parsePermissions(cachedPermissions.permissions);
|
|
|
|
permissions = parsedPermissions;
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (accountability.admin !== true) {
|
|
const query = database.select('*').from('directus_permissions');
|
|
|
|
if (accountability.role) {
|
|
query.where({ role: accountability.role });
|
|
} else {
|
|
query.whereNull('role');
|
|
}
|
|
|
|
const permissionsForRole = await query;
|
|
|
|
const {
|
|
permissions: parsedPermissions,
|
|
requiredPermissionData,
|
|
containDynamicData,
|
|
} = parsePermissions(permissionsForRole);
|
|
|
|
permissions = parsedPermissions;
|
|
|
|
if (accountability.app === true) {
|
|
permissions = mergePermissions(
|
|
'or',
|
|
permissions,
|
|
appAccessMinimalPermissions.map((perm) => ({ ...perm, role: accountability.role }))
|
|
);
|
|
}
|
|
|
|
if (accountability.share_scope) {
|
|
permissions = mergePermissionsForShare(permissions, accountability, schema);
|
|
}
|
|
|
|
const filterContext = containDynamicData
|
|
? await getFilterContext(schema, accountability, requiredPermissionData)
|
|
: {};
|
|
|
|
if (env.CACHE_PERMISSIONS !== false) {
|
|
await setSystemCache(cacheKey, { permissions, containDynamicData });
|
|
|
|
if (containDynamicData && env.CACHE_ENABLED !== false) {
|
|
await cache?.set(`filterContext-${hash({ user, role, permissions })}`, filterContext);
|
|
}
|
|
}
|
|
|
|
return processPermissions(accountability, permissions, filterContext);
|
|
}
|
|
|
|
return permissions;
|
|
}
|
|
|
|
function parsePermissions(permissions: any[]) {
|
|
const requiredPermissionData = {
|
|
$CURRENT_USER: [] as string[],
|
|
$CURRENT_ROLE: [] as string[],
|
|
};
|
|
|
|
let containDynamicData = false;
|
|
|
|
permissions = permissions.map((permissionRaw) => {
|
|
const permission = cloneDeep(permissionRaw);
|
|
|
|
if (permission.permissions && typeof permission.permissions === 'string') {
|
|
permission.permissions = parseJSON(permission.permissions);
|
|
} else if (permission.permissions === null) {
|
|
permission.permissions = {};
|
|
}
|
|
|
|
if (permission.validation && typeof permission.validation === 'string') {
|
|
permission.validation = parseJSON(permission.validation);
|
|
} else if (permission.validation === null) {
|
|
permission.validation = {};
|
|
}
|
|
|
|
if (permission.presets && typeof permission.presets === 'string') {
|
|
permission.presets = parseJSON(permission.presets);
|
|
} else if (permission.presets === null) {
|
|
permission.presets = {};
|
|
}
|
|
|
|
if (permission.fields && typeof permission.fields === 'string') {
|
|
permission.fields = permission.fields.split(',');
|
|
} else if (permission.fields === null) {
|
|
permission.fields = [];
|
|
}
|
|
|
|
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 };
|
|
}
|
|
|
|
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 (accountability.role && requiredPermissionData.$CURRENT_ROLE.length > 0) {
|
|
filterContext.$CURRENT_ROLE = await rolesService.readOne(accountability.role, {
|
|
fields: requiredPermissionData.$CURRENT_ROLE,
|
|
});
|
|
}
|
|
|
|
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 = parsePreset(permission.presets, accountability!, filterContext);
|
|
|
|
return permission;
|
|
});
|
|
}
|