directus 9.6.0 → 9.8.0
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 +3 -1
- package/dist/auth/drivers/oauth2.js +3 -0
- package/dist/auth/drivers/openid.js +3 -0
- package/dist/cache.d.ts +4 -1
- package/dist/cache.js +29 -7
- package/dist/cli/commands/schema/apply.d.ts +1 -0
- package/dist/cli/commands/schema/apply.js +9 -5
- package/dist/cli/index.js +1 -0
- package/dist/controllers/assets.js +9 -1
- package/dist/controllers/utils.js +18 -1
- package/dist/database/helpers/date/dialects/default.d.ts +3 -0
- package/dist/database/helpers/date/dialects/default.js +7 -0
- package/dist/database/helpers/date/dialects/sqlite.d.ts +0 -9
- package/dist/database/helpers/date/dialects/sqlite.js +0 -24
- package/dist/database/helpers/date/index.d.ts +6 -6
- package/dist/database/helpers/date/index.js +13 -13
- package/dist/database/helpers/date/types.d.ts +0 -9
- package/dist/database/helpers/{date → fn}/dialects/mssql.d.ts +3 -2
- package/dist/database/helpers/{date → fn}/dialects/mssql.js +14 -3
- package/dist/database/helpers/{date → fn}/dialects/mysql.d.ts +3 -2
- package/dist/database/helpers/{date → fn}/dialects/mysql.js +14 -3
- package/dist/database/helpers/{date → fn}/dialects/oracle.d.ts +3 -2
- package/dist/database/helpers/{date → fn}/dialects/oracle.js +14 -3
- package/dist/database/helpers/{date → fn}/dialects/postgres.d.ts +3 -2
- package/dist/database/helpers/{date → fn}/dialects/postgres.js +14 -3
- package/dist/database/helpers/fn/dialects/sqlite.d.ts +13 -0
- package/dist/database/helpers/fn/dialects/sqlite.js +42 -0
- package/dist/database/helpers/fn/index.d.ts +7 -0
- package/dist/database/helpers/fn/index.js +17 -0
- package/dist/database/helpers/fn/types.d.ts +18 -0
- package/dist/database/helpers/fn/types.js +27 -0
- package/dist/database/helpers/index.d.ts +4 -1
- package/dist/database/helpers/index.js +7 -1
- package/dist/database/index.js +0 -3
- package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.d.ts +3 -0
- package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.js +17 -0
- package/dist/database/migrations/20220314A-add-translation-strings.d.ts +3 -0
- package/dist/database/migrations/20220314A-add-translation-strings.js +15 -0
- package/dist/database/migrations/20220322A-rename-field-typecast-flags.d.ts +3 -0
- package/dist/database/migrations/20220322A-rename-field-typecast-flags.js +77 -0
- package/dist/database/migrations/20220323A-add-field-validation.d.ts +3 -0
- package/dist/database/migrations/20220323A-add-field-validation.js +17 -0
- package/dist/database/migrations/20220325A-fix-typecast-flags.d.ts +3 -0
- package/dist/database/migrations/20220325A-fix-typecast-flags.js +49 -0
- package/dist/database/migrations/20220325B-add-default-language.d.ts +3 -0
- package/dist/database/migrations/20220325B-add-default-language.js +28 -0
- package/dist/database/migrations/run.js +1 -1
- package/dist/database/run-ast.js +11 -3
- package/dist/database/system-data/fields/activity.yaml +4 -4
- package/dist/database/system-data/fields/collections.yaml +4 -4
- package/dist/database/system-data/fields/fields.yaml +17 -8
- package/dist/database/system-data/fields/files.yaml +2 -2
- package/dist/database/system-data/fields/panels.yaml +2 -2
- package/dist/database/system-data/fields/permissions.yaml +4 -4
- package/dist/database/system-data/fields/presets.yaml +17 -3
- package/dist/database/system-data/fields/relations.yaml +1 -1
- package/dist/database/system-data/fields/revisions.yaml +2 -2
- package/dist/database/system-data/fields/roles.yaml +4 -4
- package/dist/database/system-data/fields/settings.yaml +19 -4
- package/dist/database/system-data/fields/users.yaml +2 -2
- package/dist/database/system-data/fields/webhooks.yaml +4 -4
- package/dist/env.js +6 -2
- package/dist/exceptions/database/dialects/mysql.js +23 -17
- package/dist/logger.js +2 -1
- package/dist/middleware/respond.js +7 -28
- package/dist/services/activity.js +4 -1
- package/dist/services/authorization.d.ts +1 -1
- package/dist/services/authorization.js +156 -14
- package/dist/services/collections.js +222 -198
- package/dist/services/fields.js +184 -173
- package/dist/services/files.js +69 -3
- package/dist/services/graphql.js +2 -2
- package/dist/services/import-export.d.ts +34 -0
- package/dist/services/import-export.js +270 -0
- package/dist/services/index.d.ts +2 -1
- package/dist/services/index.js +2 -1
- package/dist/services/items.js +1 -0
- package/dist/services/payload.js +11 -9
- package/dist/services/permissions.js +10 -10
- package/dist/services/relations.js +93 -78
- package/dist/services/server.js +1 -0
- package/dist/services/shares.js +2 -1
- package/dist/services/users.js +3 -1
- package/dist/utils/apply-query.js +21 -3
- package/dist/utils/get-column.d.ts +6 -5
- package/dist/utils/get-column.js +16 -8
- package/dist/utils/get-date-formatted.d.ts +1 -0
- package/dist/utils/get-date-formatted.js +14 -0
- package/dist/utils/get-local-type.js +2 -2
- package/dist/utils/get-permissions.js +1 -1
- package/dist/utils/get-schema.d.ts +1 -1
- package/dist/utils/get-schema.js +16 -11
- package/dist/utils/track.js +3 -2
- package/dist/utils/url.d.ts +1 -1
- package/dist/utils/url.js +1 -1
- package/dist/utils/validate-storage.js +3 -1
- package/package.json +13 -12
- package/dist/services/import.d.ts +0 -13
- package/dist/services/import.js +0 -118
|
@@ -4,14 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.AuthorizationService = void 0;
|
|
7
|
+
const exceptions_1 = require("@directus/shared/exceptions");
|
|
8
|
+
const utils_1 = require("@directus/shared/utils");
|
|
7
9
|
const lodash_1 = require("lodash");
|
|
8
10
|
const database_1 = __importDefault(require("../database"));
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const utils_1 = require("@directus/shared/utils");
|
|
11
|
+
const exceptions_2 = require("../exceptions");
|
|
12
|
+
const strip_function_1 = require("../utils/strip-function");
|
|
12
13
|
const items_1 = require("./items");
|
|
13
14
|
const payload_1 = require("./payload");
|
|
14
|
-
const strip_function_1 = require("../utils/strip-function");
|
|
15
15
|
class AuthorizationService {
|
|
16
16
|
constructor(options) {
|
|
17
17
|
this.knex = options.knex || (0, database_1.default)();
|
|
@@ -32,9 +32,10 @@ class AuthorizationService {
|
|
|
32
32
|
// If the permissions don't match the collections, you don't have permission to read all of them
|
|
33
33
|
const uniqueCollectionsRequestedCount = (0, lodash_1.uniq)(collectionsRequested.map(({ collection }) => collection)).length;
|
|
34
34
|
if (uniqueCollectionsRequestedCount !== permissionsForCollections.length) {
|
|
35
|
-
throw new
|
|
35
|
+
throw new exceptions_2.ForbiddenException();
|
|
36
36
|
}
|
|
37
37
|
validateFields(ast);
|
|
38
|
+
validateFilterPermissions(ast, this.schema, this.accountability);
|
|
38
39
|
applyFilters(ast, this.accountability);
|
|
39
40
|
return ast;
|
|
40
41
|
/**
|
|
@@ -87,7 +88,7 @@ class AuthorizationService {
|
|
|
87
88
|
continue;
|
|
88
89
|
for (const column of Object.values(aliasMap)) {
|
|
89
90
|
if (allowedFields.includes(column) === false)
|
|
90
|
-
throw new
|
|
91
|
+
throw new exceptions_2.ForbiddenException();
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
}
|
|
@@ -100,7 +101,136 @@ class AuthorizationService {
|
|
|
100
101
|
continue;
|
|
101
102
|
const fieldKey = (0, strip_function_1.stripFunction)(childNode.name);
|
|
102
103
|
if (allowedFields.includes(fieldKey) === false) {
|
|
103
|
-
throw new
|
|
104
|
+
throw new exceptions_2.ForbiddenException();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function validateFilterPermissions(ast, schema, accountability) {
|
|
110
|
+
var _a, _b, _c, _d, _e;
|
|
111
|
+
let requiredFieldPermissions = {};
|
|
112
|
+
if (ast.type !== 'field') {
|
|
113
|
+
if (ast.type === 'a2o') {
|
|
114
|
+
for (const collection of Object.keys(ast.children)) {
|
|
115
|
+
requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, extractRequiredFieldPermissions(collection, (_c = (_b = (_a = ast.query) === null || _a === void 0 ? void 0 : _a[collection]) === null || _b === void 0 ? void 0 : _b.filter) !== null && _c !== void 0 ? _c : {}));
|
|
116
|
+
for (const child of ast.children[collection]) {
|
|
117
|
+
// Always add relational field as a deep child may have a filter
|
|
118
|
+
if (child.type !== 'field') {
|
|
119
|
+
(requiredFieldPermissions[collection] || (requiredFieldPermissions[collection] = new Set())).add(child.fieldKey);
|
|
120
|
+
}
|
|
121
|
+
requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, validateFilterPermissions(child, schema, accountability));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, extractRequiredFieldPermissions(ast.name, (_e = (_d = ast.query) === null || _d === void 0 ? void 0 : _d.filter) !== null && _e !== void 0 ? _e : {}));
|
|
127
|
+
for (const child of ast.children) {
|
|
128
|
+
// Always add relational field as a deep child may have a filter
|
|
129
|
+
if (child.type !== 'field') {
|
|
130
|
+
(requiredFieldPermissions[ast.name] || (requiredFieldPermissions[ast.name] = new Set())).add(child.fieldKey);
|
|
131
|
+
}
|
|
132
|
+
requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, validateFilterPermissions(child, schema, accountability));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (ast.type === 'root') {
|
|
137
|
+
// Validate all required permissions once at the root level
|
|
138
|
+
checkFieldPermissions(requiredFieldPermissions);
|
|
139
|
+
}
|
|
140
|
+
return requiredFieldPermissions;
|
|
141
|
+
function extractRequiredFieldPermissions(collection, filter, parentCollection, parentField) {
|
|
142
|
+
return (0, lodash_1.reduce)(filter, function (result, filterValue, filterKey) {
|
|
143
|
+
if (filterKey.startsWith('_')) {
|
|
144
|
+
if (filterKey === '_and' || filterKey === '_or') {
|
|
145
|
+
if ((0, lodash_1.isArray)(filterValue)) {
|
|
146
|
+
for (const filter of filterValue) {
|
|
147
|
+
const requiredPermissions = extractRequiredFieldPermissions(collection, filter, parentCollection, parentField);
|
|
148
|
+
result = mergeRequiredFieldPermissions(result, requiredPermissions);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
// Filter value is not a filter, so we should skip it
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
if (collection) {
|
|
158
|
+
(result[collection] || (result[collection] = new Set())).add(filterKey);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const relation = schema.relations.find((relation) => {
|
|
162
|
+
var _a;
|
|
163
|
+
return ((relation.collection === parentCollection && relation.field === parentField) ||
|
|
164
|
+
(relation.related_collection === parentCollection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === parentField));
|
|
165
|
+
});
|
|
166
|
+
if (relation) {
|
|
167
|
+
if (relation.related_collection === parentCollection) {
|
|
168
|
+
(result[relation.collection] || (result[relation.collection] = new Set())).add(filterKey);
|
|
169
|
+
parentCollection = relation.collection;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
(result[relation.related_collection] || (result[relation.related_collection] = new Set())).add(filterKey);
|
|
173
|
+
parentCollection = relation.related_collection;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// Filter key not found in parent collection
|
|
178
|
+
throw new exceptions_2.ForbiddenException();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (typeof filterValue === 'object') {
|
|
182
|
+
// Parent collection is undefined when we process the top level filter
|
|
183
|
+
if (!parentCollection)
|
|
184
|
+
parentCollection = collection;
|
|
185
|
+
for (const [childFilterKey, childFilterValue] of Object.entries(filterValue)) {
|
|
186
|
+
if (childFilterKey.startsWith('_')) {
|
|
187
|
+
if (childFilterKey === '_and' || childFilterKey === '_or') {
|
|
188
|
+
if ((0, lodash_1.isArray)(childFilterValue)) {
|
|
189
|
+
for (const filter of childFilterValue) {
|
|
190
|
+
const requiredPermissions = extractRequiredFieldPermissions('', filter, parentCollection, filterKey);
|
|
191
|
+
result = mergeRequiredFieldPermissions(result, requiredPermissions);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const requiredPermissions = extractRequiredFieldPermissions('', filterValue, parentCollection, filterKey);
|
|
198
|
+
result = mergeRequiredFieldPermissions(result, requiredPermissions);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}, {});
|
|
205
|
+
}
|
|
206
|
+
function mergeRequiredFieldPermissions(current, child) {
|
|
207
|
+
for (const collection of Object.keys(child)) {
|
|
208
|
+
if (!current[collection]) {
|
|
209
|
+
current[collection] = child[collection];
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
current[collection] = new Set([...current[collection], ...child[collection]]);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return current;
|
|
216
|
+
}
|
|
217
|
+
function checkFieldPermissions(requiredPermissions) {
|
|
218
|
+
var _a;
|
|
219
|
+
if ((accountability === null || accountability === void 0 ? void 0 : accountability.admin) === true)
|
|
220
|
+
return;
|
|
221
|
+
for (const collection of Object.keys(requiredPermissions)) {
|
|
222
|
+
const permission = (_a = accountability === null || accountability === void 0 ? void 0 : accountability.permissions) === null || _a === void 0 ? void 0 : _a.find((permission) => permission.collection === collection && permission.action === 'read');
|
|
223
|
+
if (!permission || !permission.fields)
|
|
224
|
+
throw new exceptions_2.ForbiddenException();
|
|
225
|
+
const allowedFields = permission.fields;
|
|
226
|
+
if (allowedFields.includes('*'))
|
|
227
|
+
continue;
|
|
228
|
+
// Allow legacy permissions with an empty fields array, where id can be accessed
|
|
229
|
+
if (allowedFields.length === 0)
|
|
230
|
+
allowedFields.push('id');
|
|
231
|
+
for (const field of requiredPermissions[collection]) {
|
|
232
|
+
if (!allowedFields.includes(field))
|
|
233
|
+
throw new exceptions_2.ForbiddenException();
|
|
104
234
|
}
|
|
105
235
|
}
|
|
106
236
|
}
|
|
@@ -164,20 +294,24 @@ class AuthorizationService {
|
|
|
164
294
|
return permission.collection === collection && permission.action === action;
|
|
165
295
|
});
|
|
166
296
|
if (!permission)
|
|
167
|
-
throw new
|
|
297
|
+
throw new exceptions_2.ForbiddenException();
|
|
168
298
|
// Check if you have permission to access the fields you're trying to access
|
|
169
299
|
const allowedFields = permission.fields || [];
|
|
170
300
|
if (allowedFields.includes('*') === false) {
|
|
171
301
|
const keysInData = Object.keys(payload);
|
|
172
302
|
const invalidKeys = keysInData.filter((fieldKey) => allowedFields.includes(fieldKey) === false);
|
|
173
303
|
if (invalidKeys.length > 0) {
|
|
174
|
-
throw new
|
|
304
|
+
throw new exceptions_2.ForbiddenException();
|
|
175
305
|
}
|
|
176
306
|
}
|
|
177
307
|
}
|
|
178
308
|
const preset = (_e = permission.presets) !== null && _e !== void 0 ? _e : {};
|
|
179
309
|
const payloadWithPresets = (0, lodash_1.merge)({}, preset, payload);
|
|
310
|
+
const fieldValidationRules = Object.values(this.schema.collections[collection].fields)
|
|
311
|
+
.map((field) => field.validation)
|
|
312
|
+
.filter((v) => v);
|
|
180
313
|
const hasValidationRules = (0, lodash_1.isNil)(permission.validation) === false && Object.keys((_f = permission.validation) !== null && _f !== void 0 ? _f : {}).length > 0;
|
|
314
|
+
const hasFieldValidationRules = fieldValidationRules && fieldValidationRules.length > 0;
|
|
181
315
|
const requiredColumns = [];
|
|
182
316
|
for (const field of Object.values(this.schema.collections[collection].fields)) {
|
|
183
317
|
const specials = (_g = field === null || field === void 0 ? void 0 : field.special) !== null && _g !== void 0 ? _g : [];
|
|
@@ -187,7 +321,7 @@ class AuthorizationService {
|
|
|
187
321
|
requiredColumns.push(field);
|
|
188
322
|
}
|
|
189
323
|
}
|
|
190
|
-
if (hasValidationRules === false && requiredColumns.length === 0) {
|
|
324
|
+
if (hasValidationRules === false && hasFieldValidationRules === false && requiredColumns.length === 0) {
|
|
191
325
|
return payloadWithPresets;
|
|
192
326
|
}
|
|
193
327
|
if (requiredColumns.length > 0) {
|
|
@@ -207,8 +341,16 @@ class AuthorizationService {
|
|
|
207
341
|
});
|
|
208
342
|
}
|
|
209
343
|
}
|
|
344
|
+
if (hasFieldValidationRules) {
|
|
345
|
+
if (permission.validation) {
|
|
346
|
+
permission.validation = { _and: [permission.validation, ...fieldValidationRules] };
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
permission.validation = { _and: fieldValidationRules };
|
|
350
|
+
}
|
|
351
|
+
}
|
|
210
352
|
const validationErrors = [];
|
|
211
|
-
validationErrors.push(...(0, lodash_1.flatten)((0, utils_1.validatePayload)(permission.validation, payloadWithPresets).map((error) => error.details.map((details) => new
|
|
353
|
+
validationErrors.push(...(0, lodash_1.flatten)((0, utils_1.validatePayload)(permission.validation, payloadWithPresets).map((error) => error.details.map((details) => new exceptions_1.FailedValidationException(details)))));
|
|
212
354
|
if (validationErrors.length > 0)
|
|
213
355
|
throw validationErrors;
|
|
214
356
|
return payloadWithPresets;
|
|
@@ -228,14 +370,14 @@ class AuthorizationService {
|
|
|
228
370
|
if (Array.isArray(pk)) {
|
|
229
371
|
const result = await itemsService.readMany(pk, { ...query, limit: pk.length }, { permissionsAction: action });
|
|
230
372
|
if (!result)
|
|
231
|
-
throw new
|
|
373
|
+
throw new exceptions_2.ForbiddenException();
|
|
232
374
|
if (result.length !== pk.length)
|
|
233
|
-
throw new
|
|
375
|
+
throw new exceptions_2.ForbiddenException();
|
|
234
376
|
}
|
|
235
377
|
else {
|
|
236
378
|
const result = await itemsService.readOne(pk, query, { permissionsAction: action });
|
|
237
379
|
if (!result)
|
|
238
|
-
throw new
|
|
380
|
+
throw new exceptions_2.ForbiddenException();
|
|
239
381
|
}
|
|
240
382
|
}
|
|
241
383
|
}
|