1
import { useEnv } from '@directus/env';
2
import type { Accountability, Permission, SchemaOverview } from '@directus/types';
3
import { deepMap, parseFilter, parseJSON, parsePreset } from '@directus/utils';
4
import { cloneDeep } from 'lodash-es';
5
import hash from 'object-hash';
6
import { getCache, getCacheValue, getSystemCache, setCacheValue, setSystemCache } from '../cache.js';
7
import getDatabase from '../database/index.js';
8
import { appAccessMinimalPermissions } from '@directus/system-data';
9
import { useLogger } from '../logger.js';
10
import { RolesService } from '../services/roles.js';
11
import { UsersService } from '../services/users.js';
12
import { mergePermissionsForShare } from './merge-permissions-for-share.js';
13
import { mergePermissions } from './merge-permissions.js';
15
export async function getPermissions(accountability: Accountability, schema: SchemaOverview) {
16
const database = getDatabase();
17
const { cache } = getCache();
19
const logger = useLogger();
21
let permissions: Permission[] = [];
23
const { user, role, app, admin, share_scope } = accountability;
24
const cacheKey = `permissions-${hash({ user, role, app, admin, share_scope })}`;
26
if (cache && env['CACHE_PERMISSIONS'] !== false) {
27
let cachedPermissions;
30
cachedPermissions = await getSystemCache(cacheKey);
32
logger.warn(err, `[cache] Couldn't read key ${cacheKey}. ${err.message}`);
35
if (cachedPermissions) {
36
if (!cachedPermissions['containDynamicData']) {
37
return processPermissions(accountability, cachedPermissions['permissions'], {});
40
const cachedFilterContext = await getCacheValue(
42
`filterContext-${hash({ user, role, permissions: cachedPermissions['permissions'] })}`,
45
if (cachedFilterContext) {
46
return processPermissions(accountability, cachedPermissions['permissions'], cachedFilterContext);
49
permissions: parsedPermissions,
50
requiredPermissionData,
52
} = parsePermissions(cachedPermissions['permissions']);
54
permissions = parsedPermissions;
56
const filterContext = containDynamicData
57
? await getFilterContext(schema, accountability, requiredPermissionData)
60
if (containDynamicData && env['CACHE_ENABLED'] !== false) {
61
await setCacheValue(cache, `filterContext-${hash({ user, role, permissions })}`, filterContext);
64
return processPermissions(accountability, permissions, filterContext);
69
if (accountability.admin !== true) {
70
const query = database.select('*').from('directus_permissions');
72
if (accountability.role) {
73
query.where({ role: accountability.role });
75
query.whereNull('role');
78
const permissionsForRole = await query;
81
permissions: parsedPermissions,
82
requiredPermissionData,
84
} = parsePermissions(permissionsForRole);
86
permissions = parsedPermissions;
88
if (accountability.app === true) {
89
permissions = mergePermissions(
92
appAccessMinimalPermissions.map((perm) => ({ ...perm, role: accountability.role })),
96
if (accountability.share_scope) {
97
permissions = mergePermissionsForShare(permissions, accountability, schema);
100
const filterContext = containDynamicData
101
? await getFilterContext(schema, accountability, requiredPermissionData)
104
if (cache && env['CACHE_PERMISSIONS'] !== false) {
105
await setSystemCache(cacheKey, { permissions, containDynamicData });
107
if (containDynamicData && env['CACHE_ENABLED'] !== false) {
108
await setCacheValue(cache, `filterContext-${hash({ user, role, permissions })}`, filterContext);
112
return processPermissions(accountability, permissions, filterContext);
118
function parsePermissions(permissions: any[]) {
119
const requiredPermissionData = {
120
$CURRENT_USER: [] as string[],
121
$CURRENT_ROLE: [] as string[],
124
let containDynamicData = false;
126
permissions = permissions.map((permissionRaw) => {
127
const permission = cloneDeep(permissionRaw);
129
if (permission.permissions && typeof permission.permissions === 'string') {
130
permission.permissions = parseJSON(permission.permissions);
133
if (permission.validation && typeof permission.validation === 'string') {
134
permission.validation = parseJSON(permission.validation);
135
} else if (permission.validation === null) {
136
permission.validation = {};
139
if (permission.presets && typeof permission.presets === 'string') {
140
permission.presets = parseJSON(permission.presets);
141
} else if (permission.presets === null) {
142
permission.presets = {};
145
if (permission.fields && typeof permission.fields === 'string') {
146
permission.fields = permission.fields.split(',');
147
} else if (permission.fields === null) {
148
permission.fields = [];
151
const extractPermissionData = (val: any) => {
152
if (typeof val === 'string' && val.startsWith('$CURRENT_USER.')) {
153
requiredPermissionData.$CURRENT_USER.push(val.replace('$CURRENT_USER.', ''));
154
containDynamicData = true;
157
if (typeof val === 'string' && val.startsWith('$CURRENT_ROLE.')) {
158
requiredPermissionData.$CURRENT_ROLE.push(val.replace('$CURRENT_ROLE.', ''));
159
containDynamicData = true;
165
deepMap(permission.permissions, extractPermissionData);
166
deepMap(permission.validation, extractPermissionData);
167
deepMap(permission.presets, extractPermissionData);
172
return { permissions, requiredPermissionData, containDynamicData };
175
async function getFilterContext(schema: SchemaOverview, accountability: Accountability, requiredPermissionData: any) {
176
const usersService = new UsersService({ schema });
177
const rolesService = new RolesService({ schema });
179
const filterContext: Record<string, any> = {};
181
if (accountability.user && requiredPermissionData.$CURRENT_USER.length > 0) {
182
filterContext['$CURRENT_USER'] = await usersService.readOne(accountability.user, {
183
fields: requiredPermissionData.$CURRENT_USER,
187
if (accountability.role && requiredPermissionData.$CURRENT_ROLE.length > 0) {
188
filterContext['$CURRENT_ROLE'] = await rolesService.readOne(accountability.role, {
189
fields: requiredPermissionData.$CURRENT_ROLE,
193
return filterContext;
196
function processPermissions(
197
accountability: Accountability,
198
permissions: Permission[],
199
filterContext: Record<string, any>,
201
return permissions.map((permission) => {
202
permission.permissions = parseFilter(permission.permissions, accountability!, filterContext);
203
permission.validation = parseFilter(permission.validation, accountability!, filterContext);
204
permission.presets = parsePreset(permission.presets, accountability!, filterContext);