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.
Files changed (99) hide show
  1. package/dist/app.js +3 -1
  2. package/dist/auth/drivers/oauth2.js +3 -0
  3. package/dist/auth/drivers/openid.js +3 -0
  4. package/dist/cache.d.ts +4 -1
  5. package/dist/cache.js +29 -7
  6. package/dist/cli/commands/schema/apply.d.ts +1 -0
  7. package/dist/cli/commands/schema/apply.js +9 -5
  8. package/dist/cli/index.js +1 -0
  9. package/dist/controllers/assets.js +9 -1
  10. package/dist/controllers/utils.js +18 -1
  11. package/dist/database/helpers/date/dialects/default.d.ts +3 -0
  12. package/dist/database/helpers/date/dialects/default.js +7 -0
  13. package/dist/database/helpers/date/dialects/sqlite.d.ts +0 -9
  14. package/dist/database/helpers/date/dialects/sqlite.js +0 -24
  15. package/dist/database/helpers/date/index.d.ts +6 -6
  16. package/dist/database/helpers/date/index.js +13 -13
  17. package/dist/database/helpers/date/types.d.ts +0 -9
  18. package/dist/database/helpers/{date → fn}/dialects/mssql.d.ts +3 -2
  19. package/dist/database/helpers/{date → fn}/dialects/mssql.js +14 -3
  20. package/dist/database/helpers/{date → fn}/dialects/mysql.d.ts +3 -2
  21. package/dist/database/helpers/{date → fn}/dialects/mysql.js +14 -3
  22. package/dist/database/helpers/{date → fn}/dialects/oracle.d.ts +3 -2
  23. package/dist/database/helpers/{date → fn}/dialects/oracle.js +14 -3
  24. package/dist/database/helpers/{date → fn}/dialects/postgres.d.ts +3 -2
  25. package/dist/database/helpers/{date → fn}/dialects/postgres.js +14 -3
  26. package/dist/database/helpers/fn/dialects/sqlite.d.ts +13 -0
  27. package/dist/database/helpers/fn/dialects/sqlite.js +42 -0
  28. package/dist/database/helpers/fn/index.d.ts +7 -0
  29. package/dist/database/helpers/fn/index.js +17 -0
  30. package/dist/database/helpers/fn/types.d.ts +18 -0
  31. package/dist/database/helpers/fn/types.js +27 -0
  32. package/dist/database/helpers/index.d.ts +4 -1
  33. package/dist/database/helpers/index.js +7 -1
  34. package/dist/database/index.js +0 -3
  35. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.d.ts +3 -0
  36. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.js +17 -0
  37. package/dist/database/migrations/20220314A-add-translation-strings.d.ts +3 -0
  38. package/dist/database/migrations/20220314A-add-translation-strings.js +15 -0
  39. package/dist/database/migrations/20220322A-rename-field-typecast-flags.d.ts +3 -0
  40. package/dist/database/migrations/20220322A-rename-field-typecast-flags.js +77 -0
  41. package/dist/database/migrations/20220323A-add-field-validation.d.ts +3 -0
  42. package/dist/database/migrations/20220323A-add-field-validation.js +17 -0
  43. package/dist/database/migrations/20220325A-fix-typecast-flags.d.ts +3 -0
  44. package/dist/database/migrations/20220325A-fix-typecast-flags.js +49 -0
  45. package/dist/database/migrations/20220325B-add-default-language.d.ts +3 -0
  46. package/dist/database/migrations/20220325B-add-default-language.js +28 -0
  47. package/dist/database/migrations/run.js +1 -1
  48. package/dist/database/run-ast.js +11 -3
  49. package/dist/database/system-data/fields/activity.yaml +4 -4
  50. package/dist/database/system-data/fields/collections.yaml +4 -4
  51. package/dist/database/system-data/fields/fields.yaml +17 -8
  52. package/dist/database/system-data/fields/files.yaml +2 -2
  53. package/dist/database/system-data/fields/panels.yaml +2 -2
  54. package/dist/database/system-data/fields/permissions.yaml +4 -4
  55. package/dist/database/system-data/fields/presets.yaml +17 -3
  56. package/dist/database/system-data/fields/relations.yaml +1 -1
  57. package/dist/database/system-data/fields/revisions.yaml +2 -2
  58. package/dist/database/system-data/fields/roles.yaml +4 -4
  59. package/dist/database/system-data/fields/settings.yaml +19 -4
  60. package/dist/database/system-data/fields/users.yaml +2 -2
  61. package/dist/database/system-data/fields/webhooks.yaml +4 -4
  62. package/dist/env.js +6 -2
  63. package/dist/exceptions/database/dialects/mysql.js +23 -17
  64. package/dist/logger.js +2 -1
  65. package/dist/middleware/respond.js +7 -28
  66. package/dist/services/activity.js +4 -1
  67. package/dist/services/authorization.d.ts +1 -1
  68. package/dist/services/authorization.js +156 -14
  69. package/dist/services/collections.js +222 -198
  70. package/dist/services/fields.js +184 -173
  71. package/dist/services/files.js +69 -3
  72. package/dist/services/graphql.js +2 -2
  73. package/dist/services/import-export.d.ts +34 -0
  74. package/dist/services/import-export.js +270 -0
  75. package/dist/services/index.d.ts +2 -1
  76. package/dist/services/index.js +2 -1
  77. package/dist/services/items.js +1 -0
  78. package/dist/services/payload.js +11 -9
  79. package/dist/services/permissions.js +10 -10
  80. package/dist/services/relations.js +93 -78
  81. package/dist/services/server.js +1 -0
  82. package/dist/services/shares.js +2 -1
  83. package/dist/services/users.js +3 -1
  84. package/dist/utils/apply-query.js +21 -3
  85. package/dist/utils/get-column.d.ts +6 -5
  86. package/dist/utils/get-column.js +16 -8
  87. package/dist/utils/get-date-formatted.d.ts +1 -0
  88. package/dist/utils/get-date-formatted.js +14 -0
  89. package/dist/utils/get-local-type.js +2 -2
  90. package/dist/utils/get-permissions.js +1 -1
  91. package/dist/utils/get-schema.d.ts +1 -1
  92. package/dist/utils/get-schema.js +16 -11
  93. package/dist/utils/track.js +3 -2
  94. package/dist/utils/url.d.ts +1 -1
  95. package/dist/utils/url.js +1 -1
  96. package/dist/utils/validate-storage.js +3 -1
  97. package/package.json +13 -12
  98. package/dist/services/import.d.ts +0 -13
  99. 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 exceptions_1 = require("../exceptions");
10
- const exceptions_2 = require("@directus/shared/exceptions");
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 exceptions_1.ForbiddenException();
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 exceptions_1.ForbiddenException();
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 exceptions_1.ForbiddenException();
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 exceptions_1.ForbiddenException();
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 exceptions_1.ForbiddenException();
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 exceptions_2.FailedValidationException(details)))));
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 exceptions_1.ForbiddenException();
373
+ throw new exceptions_2.ForbiddenException();
232
374
  if (result.length !== pk.length)
233
- throw new exceptions_1.ForbiddenException();
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 exceptions_1.ForbiddenException();
380
+ throw new exceptions_2.ForbiddenException();
239
381
  }
240
382
  }
241
383
  }