directus 9.2.1 → 9.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.js +5 -3
- package/dist/auth/auth.d.ts +4 -6
- package/dist/auth/auth.js +5 -9
- package/dist/auth/drivers/ldap.d.ts +3 -3
- package/dist/auth/drivers/ldap.js +6 -2
- package/dist/auth/drivers/local.d.ts +2 -2
- package/dist/auth/drivers/local.js +5 -12
- package/dist/auth/drivers/oauth2.d.ts +3 -3
- package/dist/auth/drivers/oauth2.js +2 -3
- package/dist/auth/drivers/openid.d.ts +3 -3
- package/dist/auth/drivers/openid.js +2 -3
- package/dist/cli/commands/bootstrap/index.js +3 -2
- package/dist/cli/commands/init/index.js +3 -7
- package/dist/cli/commands/schema/apply.js +1 -1
- package/dist/cli/utils/defaults.d.ts +11 -0
- package/dist/cli/utils/defaults.js +14 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +16 -2
- package/dist/controllers/shares.d.ts +2 -0
- package/dist/controllers/shares.js +212 -0
- package/dist/controllers/users.js +21 -9
- package/dist/database/migrations/20211211A-add-shares.d.ts +3 -0
- package/dist/database/migrations/20211211A-add-shares.js +38 -0
- package/dist/database/run-ast.js +5 -5
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -15
- package/dist/database/system-data/app-access-permissions/index.d.ts +1 -0
- package/dist/database/system-data/app-access-permissions/index.js +4 -2
- package/dist/database/system-data/app-access-permissions/schema-access-permissions.yaml +17 -0
- package/dist/database/system-data/collections/collections.yaml +3 -0
- package/dist/database/system-data/fields/_defaults.yaml +2 -0
- package/dist/database/system-data/fields/sessions.yaml +1 -1
- package/dist/database/system-data/fields/settings.yaml +9 -0
- package/dist/database/system-data/fields/shares.yaml +77 -0
- package/dist/database/system-data/fields/users.yaml +1 -1
- package/dist/database/system-data/relations/relations.yaml +15 -0
- package/dist/emitter.d.ts +3 -2
- package/dist/emitter.js +13 -6
- package/dist/env.js +1 -1
- package/dist/exceptions/index.d.ts +1 -0
- package/dist/exceptions/index.js +1 -0
- package/dist/exceptions/unexpected-response.d.ts +4 -0
- package/dist/exceptions/unexpected-response.js +10 -0
- package/dist/extensions.d.ts +1 -0
- package/dist/extensions.js +10 -4
- package/dist/middleware/authenticate.js +5 -15
- package/dist/middleware/check-ip.js +9 -6
- package/dist/middleware/respond.js +4 -1
- package/dist/services/activity.d.ts +2 -1
- package/dist/services/activity.js +2 -2
- package/dist/services/authentication.d.ts +2 -7
- package/dist/services/authentication.js +81 -41
- package/dist/services/authorization.js +3 -3
- package/dist/services/collections.d.ts +1 -2
- package/dist/services/collections.js +2 -2
- package/dist/services/files.d.ts +2 -2
- package/dist/services/files.js +14 -8
- package/dist/services/graphql.js +20 -5
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/items.d.ts +1 -15
- package/dist/services/notifications.d.ts +2 -2
- package/dist/services/permissions.d.ts +2 -2
- package/dist/services/roles.d.ts +2 -2
- package/dist/services/shares.d.ts +17 -0
- package/dist/services/shares.js +135 -0
- package/dist/services/specifications.js +1 -1
- package/dist/services/users.d.ts +2 -2
- package/dist/services/users.js +8 -6
- package/dist/services/webhooks.d.ts +2 -2
- package/dist/tests/database/migrations/run.test.d.ts +1 -0
- package/dist/tests/database/migrations/run.test.js +29 -0
- package/dist/types/ast.d.ts +3 -3
- package/dist/types/auth.d.ts +31 -0
- package/dist/types/extensions.d.ts +2 -0
- package/dist/types/items.d.ts +14 -0
- package/dist/utils/apply-query.d.ts +0 -38
- package/dist/utils/apply-query.js +66 -67
- package/dist/utils/apply-snapshot.js +69 -14
- package/dist/utils/get-ast-from-query.js +3 -3
- package/dist/utils/get-default-value.js +3 -1
- package/dist/utils/get-permissions.d.ts +2 -2
- package/dist/utils/get-permissions.js +117 -72
- package/dist/utils/get-relation-type.d.ts +1 -1
- package/dist/utils/get-relation-type.js +1 -1
- package/dist/utils/merge-permissions-for-share.d.ts +5 -0
- package/dist/utils/merge-permissions-for-share.js +116 -0
- package/dist/utils/merge-permissions.d.ts +13 -1
- package/dist/utils/merge-permissions.js +27 -19
- package/dist/utils/reduce-schema.d.ts +2 -2
- package/dist/utils/reduce-schema.js +7 -7
- package/dist/utils/user-name.js +3 -0
- package/example.env +1 -1
- package/package.json +14 -13
|
@@ -9,6 +9,7 @@ const lodash_1 = require("lodash");
|
|
|
9
9
|
const database_1 = __importDefault(require("../database"));
|
|
10
10
|
const app_access_permissions_1 = require("../database/system-data/app-access-permissions");
|
|
11
11
|
const merge_permissions_1 = require("../utils/merge-permissions");
|
|
12
|
+
const merge_permissions_for_share_1 = require("./merge-permissions-for-share");
|
|
12
13
|
const users_1 = require("../services/users");
|
|
13
14
|
const roles_1 = require("../services/roles");
|
|
14
15
|
const cache_1 = require("../cache");
|
|
@@ -16,91 +17,135 @@ const object_hash_1 = __importDefault(require("object-hash"));
|
|
|
16
17
|
const env_1 = __importDefault(require("../env"));
|
|
17
18
|
async function getPermissions(accountability, schema) {
|
|
18
19
|
const database = (0, database_1.default)();
|
|
19
|
-
const { systemCache } = (0, cache_1.getCache)();
|
|
20
|
+
const { systemCache, cache } = (0, cache_1.getCache)();
|
|
20
21
|
let permissions = [];
|
|
21
|
-
const { user, role, app, admin } = accountability;
|
|
22
|
-
const cacheKey = `permissions-${(0, object_hash_1.default)({ user, role, app, admin })}`;
|
|
22
|
+
const { user, role, app, admin, share_scope } = accountability;
|
|
23
|
+
const cacheKey = `permissions-${(0, object_hash_1.default)({ user, role, app, admin, share_scope })}`;
|
|
23
24
|
if (env_1.default.CACHE_PERMISSIONS !== false) {
|
|
24
25
|
const cachedPermissions = await systemCache.get(cacheKey);
|
|
25
26
|
if (cachedPermissions) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
if (accountability.admin !== true) {
|
|
30
|
-
const permissionsForRole = await database
|
|
31
|
-
.select('*')
|
|
32
|
-
.from('directus_permissions')
|
|
33
|
-
.where({ role: accountability.role });
|
|
34
|
-
const requiredPermissionData = {
|
|
35
|
-
$CURRENT_USER: [],
|
|
36
|
-
$CURRENT_ROLE: [],
|
|
37
|
-
};
|
|
38
|
-
permissions = permissionsForRole.map((permissionRaw) => {
|
|
39
|
-
const permission = (0, lodash_1.cloneDeep)(permissionRaw);
|
|
40
|
-
if (permission.permissions && typeof permission.permissions === 'string') {
|
|
41
|
-
permission.permissions = JSON.parse(permission.permissions);
|
|
42
|
-
}
|
|
43
|
-
else if (permission.permissions === null) {
|
|
44
|
-
permission.permissions = {};
|
|
45
|
-
}
|
|
46
|
-
if (permission.validation && typeof permission.validation === 'string') {
|
|
47
|
-
permission.validation = JSON.parse(permission.validation);
|
|
48
|
-
}
|
|
49
|
-
else if (permission.validation === null) {
|
|
50
|
-
permission.validation = {};
|
|
51
|
-
}
|
|
52
|
-
if (permission.presets && typeof permission.presets === 'string') {
|
|
53
|
-
permission.presets = JSON.parse(permission.presets);
|
|
54
|
-
}
|
|
55
|
-
else if (permission.presets === null) {
|
|
56
|
-
permission.presets = {};
|
|
57
|
-
}
|
|
58
|
-
if (permission.fields && typeof permission.fields === 'string') {
|
|
59
|
-
permission.fields = permission.fields.split(',');
|
|
27
|
+
if (!cachedPermissions.containDynamicData) {
|
|
28
|
+
return processPermissions(accountability, cachedPermissions.permissions, {});
|
|
60
29
|
}
|
|
61
|
-
|
|
62
|
-
|
|
30
|
+
const cachedFilterContext = await (cache === null || cache === void 0 ? void 0 : cache.get(`filterContext-${(0, object_hash_1.default)({ user, role, permissions: cachedPermissions.permissions })}`));
|
|
31
|
+
if (cachedFilterContext) {
|
|
32
|
+
return processPermissions(accountability, cachedPermissions.permissions, cachedFilterContext);
|
|
63
33
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
34
|
+
else {
|
|
35
|
+
const { permissions: parsedPermissions, requiredPermissionData, containDynamicData, } = parsePermissions(cachedPermissions.permissions);
|
|
36
|
+
permissions = parsedPermissions;
|
|
37
|
+
const filterContext = containDynamicData
|
|
38
|
+
? await getFilterContext(schema, accountability, requiredPermissionData)
|
|
39
|
+
: {};
|
|
40
|
+
if (containDynamicData && env_1.default.CACHE_ENABLED !== false) {
|
|
41
|
+
await (cache === null || cache === void 0 ? void 0 : cache.set(`filterContext-${(0, object_hash_1.default)({ user, role, permissions })}`, filterContext));
|
|
67
42
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
(
|
|
76
|
-
return permission;
|
|
77
|
-
});
|
|
78
|
-
if (accountability.app === true) {
|
|
79
|
-
permissions = (0, merge_permissions_1.mergePermissions)(permissions, app_access_permissions_1.appAccessMinimalPermissions.map((perm) => ({ ...perm, role: accountability.role })));
|
|
43
|
+
return processPermissions(accountability, permissions, filterContext);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (accountability.admin !== true) {
|
|
48
|
+
const query = database.select('*').from('directus_permissions');
|
|
49
|
+
if (accountability.role) {
|
|
50
|
+
query.where({ role: accountability.role });
|
|
80
51
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const filterContext = {};
|
|
84
|
-
if (accountability.user && requiredPermissionData.$CURRENT_USER.length > 0) {
|
|
85
|
-
filterContext.$CURRENT_USER = await usersService.readOne(accountability.user, {
|
|
86
|
-
fields: requiredPermissionData.$CURRENT_USER,
|
|
87
|
-
});
|
|
52
|
+
else {
|
|
53
|
+
query.whereNull('role');
|
|
88
54
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
55
|
+
const permissionsForRole = await query;
|
|
56
|
+
const { permissions: parsedPermissions, requiredPermissionData, containDynamicData, } = parsePermissions(permissionsForRole);
|
|
57
|
+
permissions = parsedPermissions;
|
|
58
|
+
if (accountability.app === true) {
|
|
59
|
+
permissions = (0, merge_permissions_1.mergePermissions)('or', permissions, app_access_permissions_1.appAccessMinimalPermissions.map((perm) => ({ ...perm, role: accountability.role })));
|
|
93
60
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
61
|
+
if (accountability.share_scope) {
|
|
62
|
+
permissions = (0, merge_permissions_for_share_1.mergePermissionsForShare)(permissions, accountability, schema);
|
|
63
|
+
}
|
|
64
|
+
const filterContext = containDynamicData
|
|
65
|
+
? await getFilterContext(schema, accountability, requiredPermissionData)
|
|
66
|
+
: {};
|
|
100
67
|
if (env_1.default.CACHE_PERMISSIONS !== false) {
|
|
101
|
-
await systemCache.set(cacheKey, permissions);
|
|
68
|
+
await systemCache.set(cacheKey, { permissions, containDynamicData });
|
|
69
|
+
if (containDynamicData && env_1.default.CACHE_ENABLED !== false) {
|
|
70
|
+
await (cache === null || cache === void 0 ? void 0 : cache.set(`filterContext-${(0, object_hash_1.default)({ user, role, permissions })}`, filterContext));
|
|
71
|
+
}
|
|
102
72
|
}
|
|
73
|
+
return processPermissions(accountability, permissions, filterContext);
|
|
103
74
|
}
|
|
104
75
|
return permissions;
|
|
105
76
|
}
|
|
106
77
|
exports.getPermissions = getPermissions;
|
|
78
|
+
function parsePermissions(permissions) {
|
|
79
|
+
const requiredPermissionData = {
|
|
80
|
+
$CURRENT_USER: [],
|
|
81
|
+
$CURRENT_ROLE: [],
|
|
82
|
+
};
|
|
83
|
+
let containDynamicData = false;
|
|
84
|
+
permissions = permissions.map((permissionRaw) => {
|
|
85
|
+
const permission = (0, lodash_1.cloneDeep)(permissionRaw);
|
|
86
|
+
if (permission.permissions && typeof permission.permissions === 'string') {
|
|
87
|
+
permission.permissions = JSON.parse(permission.permissions);
|
|
88
|
+
}
|
|
89
|
+
else if (permission.permissions === null) {
|
|
90
|
+
permission.permissions = {};
|
|
91
|
+
}
|
|
92
|
+
if (permission.validation && typeof permission.validation === 'string') {
|
|
93
|
+
permission.validation = JSON.parse(permission.validation);
|
|
94
|
+
}
|
|
95
|
+
else if (permission.validation === null) {
|
|
96
|
+
permission.validation = {};
|
|
97
|
+
}
|
|
98
|
+
if (permission.presets && typeof permission.presets === 'string') {
|
|
99
|
+
permission.presets = JSON.parse(permission.presets);
|
|
100
|
+
}
|
|
101
|
+
else if (permission.presets === null) {
|
|
102
|
+
permission.presets = {};
|
|
103
|
+
}
|
|
104
|
+
if (permission.fields && typeof permission.fields === 'string') {
|
|
105
|
+
permission.fields = permission.fields.split(',');
|
|
106
|
+
}
|
|
107
|
+
else if (permission.fields === null) {
|
|
108
|
+
permission.fields = [];
|
|
109
|
+
}
|
|
110
|
+
const extractPermissionData = (val) => {
|
|
111
|
+
if (typeof val === 'string' && val.startsWith('$CURRENT_USER.')) {
|
|
112
|
+
requiredPermissionData.$CURRENT_USER.push(val.replace('$CURRENT_USER.', ''));
|
|
113
|
+
containDynamicData = true;
|
|
114
|
+
}
|
|
115
|
+
if (typeof val === 'string' && val.startsWith('$CURRENT_ROLE.')) {
|
|
116
|
+
requiredPermissionData.$CURRENT_ROLE.push(val.replace('$CURRENT_ROLE.', ''));
|
|
117
|
+
containDynamicData = true;
|
|
118
|
+
}
|
|
119
|
+
return val;
|
|
120
|
+
};
|
|
121
|
+
(0, utils_1.deepMap)(permission.permissions, extractPermissionData);
|
|
122
|
+
(0, utils_1.deepMap)(permission.validation, extractPermissionData);
|
|
123
|
+
(0, utils_1.deepMap)(permission.presets, extractPermissionData);
|
|
124
|
+
return permission;
|
|
125
|
+
});
|
|
126
|
+
return { permissions, requiredPermissionData, containDynamicData };
|
|
127
|
+
}
|
|
128
|
+
async function getFilterContext(schema, accountability, requiredPermissionData) {
|
|
129
|
+
const usersService = new users_1.UsersService({ schema });
|
|
130
|
+
const rolesService = new roles_1.RolesService({ schema });
|
|
131
|
+
const filterContext = {};
|
|
132
|
+
if (accountability.user && requiredPermissionData.$CURRENT_USER.length > 0) {
|
|
133
|
+
filterContext.$CURRENT_USER = await usersService.readOne(accountability.user, {
|
|
134
|
+
fields: requiredPermissionData.$CURRENT_USER,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (accountability.role && requiredPermissionData.$CURRENT_ROLE.length > 0) {
|
|
138
|
+
filterContext.$CURRENT_ROLE = await rolesService.readOne(accountability.role, {
|
|
139
|
+
fields: requiredPermissionData.$CURRENT_ROLE,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return filterContext;
|
|
143
|
+
}
|
|
144
|
+
function processPermissions(accountability, permissions, filterContext) {
|
|
145
|
+
return permissions.map((permission) => {
|
|
146
|
+
permission.permissions = (0, utils_1.parseFilter)(permission.permissions, accountability, filterContext);
|
|
147
|
+
permission.validation = (0, utils_1.parseFilter)(permission.validation, accountability, filterContext);
|
|
148
|
+
permission.presets = (0, utils_1.parseFilter)(permission.presets, accountability, filterContext);
|
|
149
|
+
return permission;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -10,7 +10,7 @@ function getRelationType(getRelationOptions) {
|
|
|
10
10
|
relation.field === field &&
|
|
11
11
|
((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_collection_field) &&
|
|
12
12
|
((_b = relation.meta) === null || _b === void 0 ? void 0 : _b.one_allowed_collections)) {
|
|
13
|
-
return '
|
|
13
|
+
return 'a2o';
|
|
14
14
|
}
|
|
15
15
|
if (relation.collection === collection && relation.field === field) {
|
|
16
16
|
return 'm2o';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Permission, Accountability, Filter } from '@directus/shared/types';
|
|
2
|
+
import { SchemaOverview } from '../types';
|
|
3
|
+
export declare function mergePermissionsForShare(currentPermissions: Permission[], accountability: Accountability, schema: SchemaOverview): Permission[];
|
|
4
|
+
export declare function traverse(schema: SchemaOverview, rootItemPrimaryKeyField: string, rootItemPrimaryKey: string, currentCollection: string, parentCollections?: string[], path?: string[]): Partial<Permission>[];
|
|
5
|
+
export declare function getFilterForPath(type: 'o2m' | 'm2o' | 'a2o', path: string[], rootPrimaryKeyField: string, rootPrimaryKey: string): Filter;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getFilterForPath = exports.traverse = exports.mergePermissionsForShare = void 0;
|
|
4
|
+
const lodash_1 = require("lodash");
|
|
5
|
+
const merge_permissions_1 = require("./merge-permissions");
|
|
6
|
+
const app_access_permissions_1 = require("../database/system-data/app-access-permissions");
|
|
7
|
+
const reduce_schema_1 = require("./reduce-schema");
|
|
8
|
+
function mergePermissionsForShare(currentPermissions, accountability, schema) {
|
|
9
|
+
const defaults = {
|
|
10
|
+
action: 'read',
|
|
11
|
+
role: accountability.role,
|
|
12
|
+
collection: '',
|
|
13
|
+
permissions: {},
|
|
14
|
+
validation: null,
|
|
15
|
+
presets: null,
|
|
16
|
+
fields: null,
|
|
17
|
+
};
|
|
18
|
+
const { collection, item } = accountability.share_scope;
|
|
19
|
+
const parentPrimaryKeyField = schema.collections[collection].primary;
|
|
20
|
+
const reducedSchema = (0, reduce_schema_1.reduceSchema)(schema, currentPermissions, ['read']);
|
|
21
|
+
const relationalPermissions = traverse(reducedSchema, parentPrimaryKeyField, item, collection);
|
|
22
|
+
const parentCollectionPermission = (0, lodash_1.assign)({}, defaults, {
|
|
23
|
+
collection,
|
|
24
|
+
permissions: {
|
|
25
|
+
[parentPrimaryKeyField]: {
|
|
26
|
+
_eq: item,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
// All permissions that will be merged into the original permissions set
|
|
31
|
+
const allGeneratedPermissions = [
|
|
32
|
+
parentCollectionPermission,
|
|
33
|
+
...relationalPermissions.map((generated) => (0, lodash_1.assign)({}, defaults, generated)),
|
|
34
|
+
...app_access_permissions_1.schemaPermissions,
|
|
35
|
+
];
|
|
36
|
+
// All the collections that are touched through the relational tree from the current root collection, and the schema collections
|
|
37
|
+
const allowedCollections = (0, lodash_1.uniq)(allGeneratedPermissions.map(({ collection }) => collection));
|
|
38
|
+
const generatedPermissions = [];
|
|
39
|
+
// Merge all the permissions that relate to the same collection with an _or (this allows you to properly retrieve)
|
|
40
|
+
// the items of a collection if you entered that collection from multiple angles
|
|
41
|
+
for (const collection of allowedCollections) {
|
|
42
|
+
const permissionsForCollection = allGeneratedPermissions.filter((permission) => permission.collection === collection);
|
|
43
|
+
if (permissionsForCollection.length > 0) {
|
|
44
|
+
generatedPermissions.push(...(0, merge_permissions_1.mergePermissions)('or', permissionsForCollection));
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
generatedPermissions.push(...permissionsForCollection);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Explicitly filter out permissions to collections unrelated to the root parent item.
|
|
51
|
+
const limitedPermissions = currentPermissions.filter(({ collection }) => allowedCollections.includes(collection));
|
|
52
|
+
return (0, merge_permissions_1.mergePermissions)('and', limitedPermissions, generatedPermissions);
|
|
53
|
+
}
|
|
54
|
+
exports.mergePermissionsForShare = mergePermissionsForShare;
|
|
55
|
+
function traverse(schema, rootItemPrimaryKeyField, rootItemPrimaryKey, currentCollection, parentCollections = [], path = []) {
|
|
56
|
+
var _a, _b, _c;
|
|
57
|
+
const permissions = [];
|
|
58
|
+
// If there's already a permissions rule for the collection we're currently checking, we'll shortcircuit.
|
|
59
|
+
// This prevents infinite loop in recursive relationships, like articles->related_articles->articles, or
|
|
60
|
+
// articles.author->users.avatar->files.created_by->users.avatar->files.created_by->🔁
|
|
61
|
+
if (parentCollections.includes(currentCollection)) {
|
|
62
|
+
return permissions;
|
|
63
|
+
}
|
|
64
|
+
const relationsInCollection = schema.relations.filter((relation) => {
|
|
65
|
+
return relation.collection === currentCollection || relation.related_collection === currentCollection;
|
|
66
|
+
});
|
|
67
|
+
for (const relation of relationsInCollection) {
|
|
68
|
+
let type;
|
|
69
|
+
if (relation.related_collection === currentCollection) {
|
|
70
|
+
type = 'o2m';
|
|
71
|
+
}
|
|
72
|
+
else if (!relation.related_collection) {
|
|
73
|
+
type = 'a2o';
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
type = 'm2o';
|
|
77
|
+
}
|
|
78
|
+
if (type === 'o2m') {
|
|
79
|
+
permissions.push({
|
|
80
|
+
collection: relation.collection,
|
|
81
|
+
permissions: getFilterForPath(type, [...path, relation.field], rootItemPrimaryKeyField, rootItemPrimaryKey),
|
|
82
|
+
});
|
|
83
|
+
permissions.push(...traverse(schema, rootItemPrimaryKeyField, rootItemPrimaryKey, relation.collection, [...parentCollections, currentCollection], [...path, relation.field]));
|
|
84
|
+
}
|
|
85
|
+
if (type === 'a2o' && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_allowed_collections)) {
|
|
86
|
+
for (const collection of relation.meta.one_allowed_collections) {
|
|
87
|
+
permissions.push({
|
|
88
|
+
collection,
|
|
89
|
+
permissions: getFilterForPath(type, [...path, `$FOLLOW(${relation.collection},${relation.field},${relation.meta.one_collection_field})`], rootItemPrimaryKeyField, rootItemPrimaryKey),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (type === 'm2o') {
|
|
94
|
+
permissions.push({
|
|
95
|
+
collection: relation.related_collection,
|
|
96
|
+
permissions: getFilterForPath(type, [...path, `$FOLLOW(${relation.collection},${relation.field})`], rootItemPrimaryKeyField, rootItemPrimaryKey),
|
|
97
|
+
});
|
|
98
|
+
if ((_b = relation.meta) === null || _b === void 0 ? void 0 : _b.one_field) {
|
|
99
|
+
permissions.push(...traverse(schema, rootItemPrimaryKeyField, rootItemPrimaryKey, relation.related_collection, [...parentCollections, currentCollection], [...path, (_c = relation.meta) === null || _c === void 0 ? void 0 : _c.one_field]));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return permissions;
|
|
104
|
+
}
|
|
105
|
+
exports.traverse = traverse;
|
|
106
|
+
function getFilterForPath(type, path, rootPrimaryKeyField, rootPrimaryKey) {
|
|
107
|
+
const filter = {};
|
|
108
|
+
if (type === 'm2o' || type === 'a2o') {
|
|
109
|
+
(0, lodash_1.set)(filter, path.reverse(), { [rootPrimaryKeyField]: { _eq: rootPrimaryKey } });
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
(0, lodash_1.set)(filter, path.reverse(), { _eq: rootPrimaryKey });
|
|
113
|
+
}
|
|
114
|
+
return filter;
|
|
115
|
+
}
|
|
116
|
+
exports.getFilterForPath = getFilterForPath;
|
|
@@ -1,2 +1,14 @@
|
|
|
1
|
+
/// <reference types="lodash" />
|
|
1
2
|
import { Permission } from '@directus/shared/types';
|
|
2
|
-
export declare function mergePermissions(...permissions: Permission[][]): Permission[];
|
|
3
|
+
export declare function mergePermissions(strategy: 'and' | 'or', ...permissions: Permission[][]): Permission[];
|
|
4
|
+
export declare function mergePermission(strategy: 'and' | 'or', currentPerm: Permission, newPerm: Permission): import("lodash").Omit<{
|
|
5
|
+
permissions: import("@directus/shared/types").Filter | null;
|
|
6
|
+
validation: import("@directus/shared/types").Filter | null;
|
|
7
|
+
fields: string[] | null;
|
|
8
|
+
presets: Record<string, any> | null;
|
|
9
|
+
id?: number | undefined;
|
|
10
|
+
role: string | null;
|
|
11
|
+
collection: string;
|
|
12
|
+
action: import("@directus/shared/types").PermissionsAction;
|
|
13
|
+
system?: true | undefined;
|
|
14
|
+
}, "id" | "system">;
|
|
@@ -1,66 +1,73 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.mergePermissions = void 0;
|
|
3
|
+
exports.mergePermission = exports.mergePermissions = void 0;
|
|
4
4
|
const lodash_1 = require("lodash");
|
|
5
|
-
function mergePermissions(...permissions) {
|
|
5
|
+
function mergePermissions(strategy, ...permissions) {
|
|
6
6
|
const allPermissions = (0, lodash_1.flatten)(permissions);
|
|
7
7
|
const mergedPermissions = allPermissions
|
|
8
8
|
.reduce((acc, val) => {
|
|
9
9
|
const key = `${val.collection}__${val.action}__${val.role || '$PUBLIC'}`;
|
|
10
10
|
const current = acc.get(key);
|
|
11
|
-
acc.set(key, current ?
|
|
11
|
+
acc.set(key, current ? mergePermission(strategy, current, val) : val);
|
|
12
12
|
return acc;
|
|
13
13
|
}, new Map())
|
|
14
14
|
.values();
|
|
15
|
-
|
|
16
|
-
return (0, lodash_1.omit)(perm, ['id', 'system']);
|
|
17
|
-
});
|
|
18
|
-
return result;
|
|
15
|
+
return Array.from(mergedPermissions);
|
|
19
16
|
}
|
|
20
17
|
exports.mergePermissions = mergePermissions;
|
|
21
|
-
function
|
|
18
|
+
function mergePermission(strategy, currentPerm, newPerm) {
|
|
19
|
+
const logicalKey = `_${strategy}`;
|
|
22
20
|
let permissions = currentPerm.permissions;
|
|
23
21
|
let validation = currentPerm.validation;
|
|
24
22
|
let fields = currentPerm.fields;
|
|
25
23
|
let presets = currentPerm.presets;
|
|
26
24
|
if (newPerm.permissions) {
|
|
27
|
-
if (currentPerm.permissions && Object.keys(currentPerm.permissions)[0] ===
|
|
25
|
+
if (currentPerm.permissions && Object.keys(currentPerm.permissions)[0] === logicalKey) {
|
|
28
26
|
permissions = {
|
|
29
|
-
|
|
27
|
+
[logicalKey]: [
|
|
28
|
+
...currentPerm.permissions[logicalKey],
|
|
29
|
+
newPerm.permissions,
|
|
30
|
+
],
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
else if (currentPerm.permissions) {
|
|
33
34
|
permissions = {
|
|
34
|
-
|
|
35
|
+
[logicalKey]: [currentPerm.permissions, newPerm.permissions],
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
else {
|
|
38
39
|
permissions = {
|
|
39
|
-
|
|
40
|
+
[logicalKey]: [newPerm.permissions],
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
if (newPerm.validation) {
|
|
44
|
-
if (currentPerm.validation && Object.keys(currentPerm.validation)[0] ===
|
|
45
|
+
if (currentPerm.validation && Object.keys(currentPerm.validation)[0] === logicalKey) {
|
|
45
46
|
validation = {
|
|
46
|
-
|
|
47
|
+
[logicalKey]: [
|
|
48
|
+
...currentPerm.validation[logicalKey],
|
|
49
|
+
newPerm.validation,
|
|
50
|
+
],
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
53
|
else if (currentPerm.validation) {
|
|
50
54
|
validation = {
|
|
51
|
-
|
|
55
|
+
[logicalKey]: [currentPerm.validation, newPerm.validation],
|
|
52
56
|
};
|
|
53
57
|
}
|
|
54
58
|
else {
|
|
55
59
|
validation = {
|
|
56
|
-
|
|
60
|
+
[logicalKey]: [newPerm.validation],
|
|
57
61
|
};
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
if (newPerm.fields) {
|
|
61
|
-
if (Array.isArray(currentPerm.fields)) {
|
|
65
|
+
if (Array.isArray(currentPerm.fields) && strategy === 'or') {
|
|
62
66
|
fields = [...new Set([...currentPerm.fields, ...newPerm.fields])];
|
|
63
67
|
}
|
|
68
|
+
else if (Array.isArray(currentPerm.fields) && strategy === 'and') {
|
|
69
|
+
fields = (0, lodash_1.intersection)(currentPerm.fields, newPerm.fields);
|
|
70
|
+
}
|
|
64
71
|
else {
|
|
65
72
|
fields = newPerm.fields;
|
|
66
73
|
}
|
|
@@ -70,11 +77,12 @@ function mergePerm(currentPerm, newPerm) {
|
|
|
70
77
|
if (newPerm.presets) {
|
|
71
78
|
presets = (0, lodash_1.merge)({}, presets, newPerm.presets);
|
|
72
79
|
}
|
|
73
|
-
return {
|
|
80
|
+
return (0, lodash_1.omit)({
|
|
74
81
|
...currentPerm,
|
|
75
82
|
permissions,
|
|
76
83
|
validation,
|
|
77
84
|
fields,
|
|
78
85
|
presets,
|
|
79
|
-
};
|
|
86
|
+
}, ['id', 'system']);
|
|
80
87
|
}
|
|
88
|
+
exports.mergePermission = mergePermission;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SchemaOverview } from '../types';
|
|
2
|
-
import {
|
|
2
|
+
import { Permission, PermissionsAction } from '@directus/shared/types';
|
|
3
3
|
/**
|
|
4
4
|
* Reduces the schema based on the included permissions. The resulting object is the schema structure, but with only
|
|
5
5
|
* the allowed collections/fields/relations included based on the permissions.
|
|
@@ -7,4 +7,4 @@ import { Accountability, PermissionsAction } from '@directus/shared/types';
|
|
|
7
7
|
* @param actions Array of permissions actions (crud)
|
|
8
8
|
* @returns Reduced schema
|
|
9
9
|
*/
|
|
10
|
-
export declare function reduceSchema(schema: SchemaOverview,
|
|
10
|
+
export declare function reduceSchema(schema: SchemaOverview, permissions: Permission[] | null, actions?: PermissionsAction[]): SchemaOverview;
|
|
@@ -9,13 +9,13 @@ const lodash_1 = require("lodash");
|
|
|
9
9
|
* @param actions Array of permissions actions (crud)
|
|
10
10
|
* @returns Reduced schema
|
|
11
11
|
*/
|
|
12
|
-
function reduceSchema(schema,
|
|
13
|
-
var _a, _b, _c
|
|
12
|
+
function reduceSchema(schema, permissions, actions = ['create', 'read', 'update', 'delete']) {
|
|
13
|
+
var _a, _b, _c;
|
|
14
14
|
const reduced = {
|
|
15
15
|
collections: {},
|
|
16
16
|
relations: [],
|
|
17
17
|
};
|
|
18
|
-
const allowedFieldsInCollection = (
|
|
18
|
+
const allowedFieldsInCollection = (_a = permissions === null || permissions === void 0 ? void 0 : permissions.filter((permission) => actions.includes(permission.action)).reduce((acc, permission) => {
|
|
19
19
|
if (!acc[permission.collection]) {
|
|
20
20
|
acc[permission.collection] = [];
|
|
21
21
|
}
|
|
@@ -23,13 +23,13 @@ function reduceSchema(schema, accountability, actions = ['create', 'read', 'upda
|
|
|
23
23
|
acc[permission.collection] = (0, lodash_1.uniq)([...acc[permission.collection], ...permission.fields]);
|
|
24
24
|
}
|
|
25
25
|
return acc;
|
|
26
|
-
}, {})) !== null &&
|
|
26
|
+
}, {})) !== null && _a !== void 0 ? _a : {};
|
|
27
27
|
for (const [collectionName, collection] of Object.entries(schema.collections)) {
|
|
28
|
-
if (
|
|
28
|
+
if (permissions === null || permissions === void 0 ? void 0 : permissions.some((permission) => permission.collection === collectionName && actions.includes(permission.action))) {
|
|
29
29
|
const fields = {};
|
|
30
30
|
for (const [fieldName, field] of Object.entries(schema.collections[collectionName].fields)) {
|
|
31
|
-
if (((
|
|
32
|
-
((
|
|
31
|
+
if (((_b = allowedFieldsInCollection[collectionName]) === null || _b === void 0 ? void 0 : _b.includes('*')) ||
|
|
32
|
+
((_c = allowedFieldsInCollection[collectionName]) === null || _c === void 0 ? void 0 : _c.includes(fieldName))) {
|
|
33
33
|
fields[fieldName] = field;
|
|
34
34
|
}
|
|
35
35
|
}
|
package/dist/utils/user-name.js
CHANGED
package/example.env
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "directus",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.4.1",
|
|
4
4
|
"license": "GPL-3.0-only",
|
|
5
5
|
"homepage": "https://github.com/directus/directus#readme",
|
|
6
6
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content.",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"prebuild": "npm run cleanup",
|
|
61
61
|
"build": "tsc --build && copyfiles \"src/**/*.*\" -e \"src/**/*.ts\" -u 1 dist",
|
|
62
62
|
"cleanup": "rimraf dist",
|
|
63
|
-
"dev": "cross-env NODE_ENV=development SERVE_APP=false ts-node-dev --files --transpile-only --respawn --watch \".env\" --inspect --exit-child -- src/start.ts",
|
|
63
|
+
"dev": "cross-env NODE_ENV=development SERVE_APP=false ts-node-dev --files --transpile-only --respawn --watch \".env\" --inspect=0 --exit-child -- src/start.ts",
|
|
64
64
|
"cli": "cross-env NODE_ENV=development SERVE_APP=false ts-node --script-mode --transpile-only src/cli/run.ts",
|
|
65
65
|
"test": "jest --coverage",
|
|
66
66
|
"test:watch": "jest --watchAll"
|
|
@@ -76,16 +76,16 @@
|
|
|
76
76
|
],
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@aws-sdk/client-ses": "^3.40.0",
|
|
79
|
-
"@directus/app": "9.
|
|
80
|
-
"@directus/drive": "9.
|
|
81
|
-
"@directus/drive-azure": "9.
|
|
82
|
-
"@directus/drive-gcs": "9.
|
|
83
|
-
"@directus/drive-s3": "9.
|
|
84
|
-
"@directus/extensions-sdk": "9.
|
|
85
|
-
"@directus/format-title": "9.
|
|
86
|
-
"@directus/schema": "9.
|
|
87
|
-
"@directus/shared": "9.
|
|
88
|
-
"@directus/specs": "9.
|
|
79
|
+
"@directus/app": "9.4.1",
|
|
80
|
+
"@directus/drive": "9.4.1",
|
|
81
|
+
"@directus/drive-azure": "9.4.1",
|
|
82
|
+
"@directus/drive-gcs": "9.4.1",
|
|
83
|
+
"@directus/drive-s3": "9.4.1",
|
|
84
|
+
"@directus/extensions-sdk": "9.4.1",
|
|
85
|
+
"@directus/format-title": "9.4.1",
|
|
86
|
+
"@directus/schema": "9.4.1",
|
|
87
|
+
"@directus/shared": "9.4.1",
|
|
88
|
+
"@directus/specs": "9.4.1",
|
|
89
89
|
"@godaddy/terminus": "^4.9.0",
|
|
90
90
|
"@rollup/plugin-alias": "^3.1.2",
|
|
91
91
|
"@rollup/plugin-virtual": "^2.0.3",
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
"sqlite3": "^5.0.2",
|
|
170
170
|
"tedious": "^13.0.0"
|
|
171
171
|
},
|
|
172
|
-
"gitHead": "
|
|
172
|
+
"gitHead": "4991ba858bdde8bdf03aee475d77a218da6e46ab",
|
|
173
173
|
"devDependencies": {
|
|
174
174
|
"@types/async": "3.2.10",
|
|
175
175
|
"@types/atob": "2.1.2",
|
|
@@ -209,6 +209,7 @@
|
|
|
209
209
|
"copyfiles": "2.4.1",
|
|
210
210
|
"cross-env": "7.0.3",
|
|
211
211
|
"jest": "27.3.1",
|
|
212
|
+
"knex-mock-client": "1.6.1",
|
|
212
213
|
"ts-jest": "27.0.7",
|
|
213
214
|
"ts-node-dev": "1.1.8",
|
|
214
215
|
"typescript": "4.5.2"
|