directus 9.7.0 → 9.9.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 (112) hide show
  1. package/dist/__mocks__/cache.d.ts +5 -0
  2. package/dist/__mocks__/cache.js +7 -0
  3. package/dist/auth/drivers/ldap.js +10 -11
  4. package/dist/auth/drivers/oauth2.js +9 -4
  5. package/dist/auth/drivers/openid.js +7 -4
  6. package/dist/cache.js +2 -2
  7. package/dist/cli/commands/schema/apply.js +9 -3
  8. package/dist/controllers/assets.js +5 -0
  9. package/dist/controllers/files.d.ts +2 -0
  10. package/dist/controllers/files.js +10 -5
  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/mssql.d.ts +1 -9
  14. package/dist/database/helpers/date/dialects/mssql.js +4 -23
  15. package/dist/database/helpers/date/dialects/mysql.d.ts +2 -9
  16. package/dist/database/helpers/date/dialects/mysql.js +7 -22
  17. package/dist/database/helpers/date/dialects/oracle.d.ts +1 -9
  18. package/dist/database/helpers/date/dialects/oracle.js +7 -23
  19. package/dist/database/helpers/date/dialects/sqlite.d.ts +1 -9
  20. package/dist/database/helpers/date/dialects/sqlite.js +8 -24
  21. package/dist/database/helpers/date/index.d.ts +4 -4
  22. package/dist/database/helpers/date/index.js +9 -9
  23. package/dist/database/helpers/date/types.d.ts +3 -9
  24. package/dist/database/helpers/date/types.js +10 -0
  25. package/dist/database/helpers/fn/dialects/mssql.d.ts +13 -0
  26. package/dist/database/helpers/fn/dialects/mssql.js +42 -0
  27. package/dist/database/helpers/{date/dialects/postgres.d.ts → fn/dialects/mysql.d.ts} +3 -2
  28. package/dist/database/helpers/fn/dialects/mysql.js +42 -0
  29. package/dist/database/helpers/fn/dialects/oracle.d.ts +13 -0
  30. package/dist/database/helpers/fn/dialects/oracle.js +42 -0
  31. package/dist/database/helpers/fn/dialects/postgres.d.ts +13 -0
  32. package/dist/database/helpers/{date → fn}/dialects/postgres.js +14 -3
  33. package/dist/database/helpers/fn/dialects/sqlite.d.ts +13 -0
  34. package/dist/database/helpers/fn/dialects/sqlite.js +42 -0
  35. package/dist/database/helpers/fn/index.d.ts +7 -0
  36. package/dist/database/helpers/fn/index.js +17 -0
  37. package/dist/database/helpers/fn/types.d.ts +18 -0
  38. package/dist/database/helpers/fn/types.js +27 -0
  39. package/dist/database/helpers/index.d.ts +4 -1
  40. package/dist/database/helpers/index.js +7 -1
  41. package/dist/database/index.js +0 -3
  42. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.d.ts +3 -0
  43. package/dist/database/migrations/20220308A-add-bookmark-icon-and-color.js +17 -0
  44. package/dist/database/migrations/20220314A-add-translation-strings.d.ts +3 -0
  45. package/dist/database/migrations/20220314A-add-translation-strings.js +15 -0
  46. package/dist/database/migrations/20220322A-rename-field-typecast-flags.d.ts +3 -0
  47. package/dist/database/migrations/20220322A-rename-field-typecast-flags.js +77 -0
  48. package/dist/database/migrations/20220323A-add-field-validation.d.ts +3 -0
  49. package/dist/database/migrations/20220323A-add-field-validation.js +17 -0
  50. package/dist/database/migrations/20220325A-fix-typecast-flags.d.ts +3 -0
  51. package/dist/database/migrations/20220325A-fix-typecast-flags.js +49 -0
  52. package/dist/database/migrations/20220325B-add-default-language.d.ts +3 -0
  53. package/dist/database/migrations/20220325B-add-default-language.js +28 -0
  54. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.d.ts +3 -0
  55. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.js +22 -0
  56. package/dist/database/migrations/run.js +1 -1
  57. package/dist/database/run-ast.js +4 -2
  58. package/dist/database/system-data/fields/activity.yaml +4 -4
  59. package/dist/database/system-data/fields/collections.yaml +4 -4
  60. package/dist/database/system-data/fields/fields.yaml +17 -8
  61. package/dist/database/system-data/fields/files.yaml +2 -2
  62. package/dist/database/system-data/fields/panels.yaml +2 -2
  63. package/dist/database/system-data/fields/permissions.yaml +4 -4
  64. package/dist/database/system-data/fields/presets.yaml +17 -3
  65. package/dist/database/system-data/fields/relations.yaml +1 -1
  66. package/dist/database/system-data/fields/revisions.yaml +2 -2
  67. package/dist/database/system-data/fields/roles.yaml +4 -4
  68. package/dist/database/system-data/fields/settings.yaml +18 -4
  69. package/dist/database/system-data/fields/users.yaml +5 -2
  70. package/dist/database/system-data/fields/webhooks.yaml +4 -4
  71. package/dist/env.js +4 -1
  72. package/dist/exceptions/index.d.ts +1 -0
  73. package/dist/exceptions/index.js +1 -0
  74. package/dist/exceptions/token-expired.d.ts +4 -0
  75. package/dist/exceptions/token-expired.js +10 -0
  76. package/dist/logger.js +2 -1
  77. package/dist/middleware/cache.js +10 -0
  78. package/dist/services/activity.js +4 -1
  79. package/dist/services/authorization.d.ts +1 -1
  80. package/dist/services/authorization.js +198 -14
  81. package/dist/services/collections.d.ts +2 -0
  82. package/dist/services/collections.js +230 -198
  83. package/dist/services/fields.js +208 -174
  84. package/dist/services/files.d.ts +5 -1
  85. package/dist/services/files.js +59 -40
  86. package/dist/services/graphql.d.ts +2 -3
  87. package/dist/services/graphql.js +39 -9
  88. package/dist/services/items.js +1 -0
  89. package/dist/services/payload.d.ts +1 -0
  90. package/dist/services/payload.js +29 -24
  91. package/dist/services/relations.js +93 -81
  92. package/dist/services/server.js +1 -0
  93. package/dist/services/shares.js +2 -1
  94. package/dist/services/users.js +3 -1
  95. package/dist/types/files.d.ts +8 -0
  96. package/dist/utils/apply-query.js +38 -10
  97. package/dist/utils/apply-snapshot.d.ts +3 -1
  98. package/dist/utils/apply-snapshot.js +34 -5
  99. package/dist/utils/get-column.d.ts +6 -5
  100. package/dist/utils/get-column.js +16 -8
  101. package/dist/utils/get-graphql-type.js +1 -0
  102. package/dist/utils/get-local-type.js +7 -2
  103. package/dist/utils/get-schema.d.ts +1 -1
  104. package/dist/utils/get-schema.js +15 -10
  105. package/dist/utils/jwt.js +1 -1
  106. package/dist/utils/track.js +3 -2
  107. package/dist/utils/url.d.ts +1 -1
  108. package/dist/utils/url.js +1 -1
  109. package/dist/utils/validate-query.js +19 -15
  110. package/dist/utils/validate-storage.js +3 -1
  111. package/example.env +4 -0
  112. package/package.json +14 -13
@@ -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, action, this.accountability);
38
39
  applyFilters(ast, this.accountability);
39
40
  return ast;
40
41
  /**
@@ -86,8 +87,10 @@ class AuthorizationService {
86
87
  if (!aliasMap)
87
88
  continue;
88
89
  for (const column of Object.values(aliasMap)) {
90
+ if (column === '*')
91
+ continue;
89
92
  if (allowedFields.includes(column) === false)
90
- throw new exceptions_1.ForbiddenException();
93
+ throw new exceptions_2.ForbiddenException();
91
94
  }
92
95
  }
93
96
  }
@@ -100,7 +103,176 @@ class AuthorizationService {
100
103
  continue;
101
104
  const fieldKey = (0, strip_function_1.stripFunction)(childNode.name);
102
105
  if (allowedFields.includes(fieldKey) === false) {
103
- throw new exceptions_1.ForbiddenException();
106
+ throw new exceptions_2.ForbiddenException();
107
+ }
108
+ }
109
+ }
110
+ }
111
+ function validateFilterPermissions(ast, schema, action, accountability) {
112
+ var _a, _b, _c, _d, _e;
113
+ let requiredFieldPermissions = {};
114
+ if (ast.type !== 'field') {
115
+ if (ast.type === 'a2o') {
116
+ for (const collection of Object.keys(ast.children)) {
117
+ 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 : {}));
118
+ for (const child of ast.children[collection]) {
119
+ const childPermissions = validateFilterPermissions(child, schema, action, accountability);
120
+ if (Object.keys(childPermissions).length > 0) {
121
+ //Only add relational field if deep child has a filter
122
+ if (child.type !== 'field') {
123
+ (requiredFieldPermissions[collection] || (requiredFieldPermissions[collection] = new Set())).add(child.fieldKey);
124
+ }
125
+ requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, childPermissions);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ else {
131
+ requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, extractRequiredFieldPermissions(ast.name, (_e = (_d = ast.query) === null || _d === void 0 ? void 0 : _d.filter) !== null && _e !== void 0 ? _e : {}));
132
+ for (const child of ast.children) {
133
+ const childPermissions = validateFilterPermissions(child, schema, action, accountability);
134
+ if (Object.keys(childPermissions).length > 0) {
135
+ // Only add relational field if deep child has a filter
136
+ if (child.type !== 'field') {
137
+ (requiredFieldPermissions[ast.name] || (requiredFieldPermissions[ast.name] = new Set())).add(child.fieldKey);
138
+ }
139
+ requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, childPermissions);
140
+ }
141
+ }
142
+ }
143
+ }
144
+ if (ast.type === 'root') {
145
+ // Validate all required permissions once at the root level
146
+ checkFieldPermissions(ast.name, schema, action, requiredFieldPermissions);
147
+ }
148
+ return requiredFieldPermissions;
149
+ function extractRequiredFieldPermissions(collection, filter, parentCollection, parentField) {
150
+ return (0, lodash_1.reduce)(filter, function (result, filterValue, filterKey) {
151
+ if (filterKey.startsWith('_')) {
152
+ if (filterKey === '_and' || filterKey === '_or') {
153
+ if ((0, lodash_1.isArray)(filterValue)) {
154
+ for (const filter of filterValue) {
155
+ const requiredPermissions = extractRequiredFieldPermissions(collection, filter, parentCollection, parentField);
156
+ result = mergeRequiredFieldPermissions(result, requiredPermissions);
157
+ }
158
+ }
159
+ return result;
160
+ }
161
+ // Filter value is not a filter, so we should skip it
162
+ return result;
163
+ }
164
+ // m2a filter in the form of `item:collection`
165
+ else if (filterKey.includes(':')) {
166
+ const [field, collectionScope] = filterKey.split(':');
167
+ if (collection) {
168
+ // Add the `item` field to the required permissions
169
+ (result[collection] || (result[collection] = new Set())).add(field);
170
+ // Add the `collection` field to the required permissions
171
+ result[collection].add('collection');
172
+ }
173
+ else {
174
+ const relation = schema.relations.find((relation) => {
175
+ var _a;
176
+ return ((relation.collection === parentCollection && relation.field === parentField) ||
177
+ (relation.related_collection === parentCollection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === parentField));
178
+ });
179
+ // Filter key not found in parent collection
180
+ if (!relation)
181
+ throw new exceptions_2.ForbiddenException();
182
+ const relatedCollectionName = relation.related_collection === parentCollection ? relation.collection : relation.related_collection;
183
+ // Add the `item` field to the required permissions
184
+ (result[relatedCollectionName] || (result[relatedCollectionName] = new Set())).add(field);
185
+ // Add the `collection` field to the required permissions
186
+ result[relatedCollectionName].add('collection');
187
+ }
188
+ // Continue to parse the filter for nested `collection` afresh
189
+ const requiredPermissions = extractRequiredFieldPermissions(collectionScope, filterValue);
190
+ result = mergeRequiredFieldPermissions(result, requiredPermissions);
191
+ }
192
+ else {
193
+ if (collection) {
194
+ (result[collection] || (result[collection] = new Set())).add(filterKey);
195
+ }
196
+ else {
197
+ const relation = schema.relations.find((relation) => {
198
+ var _a;
199
+ return ((relation.collection === parentCollection && relation.field === parentField) ||
200
+ (relation.related_collection === parentCollection && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === parentField));
201
+ });
202
+ // Filter key not found in parent collection
203
+ if (!relation)
204
+ throw new exceptions_2.ForbiddenException();
205
+ parentCollection =
206
+ relation.related_collection === parentCollection ? relation.collection : relation.related_collection;
207
+ (result[parentCollection] || (result[parentCollection] = new Set())).add(filterKey);
208
+ }
209
+ if (typeof filterValue === 'object') {
210
+ // Parent collection is undefined when we process the top level filter
211
+ if (!parentCollection)
212
+ parentCollection = collection;
213
+ for (const [childFilterKey, childFilterValue] of Object.entries(filterValue)) {
214
+ if (childFilterKey.startsWith('_')) {
215
+ if (childFilterKey === '_and' || childFilterKey === '_or') {
216
+ if ((0, lodash_1.isArray)(childFilterValue)) {
217
+ for (const filter of childFilterValue) {
218
+ const requiredPermissions = extractRequiredFieldPermissions('', filter, parentCollection, filterKey);
219
+ result = mergeRequiredFieldPermissions(result, requiredPermissions);
220
+ }
221
+ }
222
+ }
223
+ }
224
+ else {
225
+ const requiredPermissions = extractRequiredFieldPermissions('', filterValue, parentCollection, filterKey);
226
+ result = mergeRequiredFieldPermissions(result, requiredPermissions);
227
+ }
228
+ }
229
+ }
230
+ }
231
+ return result;
232
+ }, {});
233
+ }
234
+ function mergeRequiredFieldPermissions(current, child) {
235
+ for (const collection of Object.keys(child)) {
236
+ if (!current[collection]) {
237
+ current[collection] = child[collection];
238
+ }
239
+ else {
240
+ current[collection] = new Set([...current[collection], ...child[collection]]);
241
+ }
242
+ }
243
+ return current;
244
+ }
245
+ function checkFieldPermissions(rootCollection, schema, action, requiredPermissions) {
246
+ var _a, _b;
247
+ if ((accountability === null || accountability === void 0 ? void 0 : accountability.admin) === true)
248
+ return;
249
+ for (const collection of Object.keys(requiredPermissions)) {
250
+ 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');
251
+ let allowedFields;
252
+ // Allow the filtering of top level ID for actions such as update and delete
253
+ if (action !== 'read' && collection === rootCollection) {
254
+ const actionPermission = (_b = accountability === null || accountability === void 0 ? void 0 : accountability.permissions) === null || _b === void 0 ? void 0 : _b.find((permission) => permission.collection === collection && permission.action === action);
255
+ if (!actionPermission || !actionPermission.fields) {
256
+ throw new exceptions_2.ForbiddenException();
257
+ }
258
+ allowedFields = (permission === null || permission === void 0 ? void 0 : permission.fields)
259
+ ? [...permission.fields, schema.collections[collection].primary]
260
+ : [schema.collections[collection].primary];
261
+ }
262
+ else if (!permission || !permission.fields) {
263
+ throw new exceptions_2.ForbiddenException();
264
+ }
265
+ else {
266
+ allowedFields = permission.fields;
267
+ }
268
+ if (allowedFields.includes('*'))
269
+ continue;
270
+ // Allow legacy permissions with an empty fields array, where id can be accessed
271
+ if (allowedFields.length === 0)
272
+ allowedFields.push(schema.collections[collection].primary);
273
+ for (const field of requiredPermissions[collection]) {
274
+ if (!allowedFields.includes(field))
275
+ throw new exceptions_2.ForbiddenException();
104
276
  }
105
277
  }
106
278
  }
@@ -164,20 +336,24 @@ class AuthorizationService {
164
336
  return permission.collection === collection && permission.action === action;
165
337
  });
166
338
  if (!permission)
167
- throw new exceptions_1.ForbiddenException();
339
+ throw new exceptions_2.ForbiddenException();
168
340
  // Check if you have permission to access the fields you're trying to access
169
341
  const allowedFields = permission.fields || [];
170
342
  if (allowedFields.includes('*') === false) {
171
343
  const keysInData = Object.keys(payload);
172
344
  const invalidKeys = keysInData.filter((fieldKey) => allowedFields.includes(fieldKey) === false);
173
345
  if (invalidKeys.length > 0) {
174
- throw new exceptions_1.ForbiddenException();
346
+ throw new exceptions_2.ForbiddenException();
175
347
  }
176
348
  }
177
349
  }
178
350
  const preset = (_e = permission.presets) !== null && _e !== void 0 ? _e : {};
179
351
  const payloadWithPresets = (0, lodash_1.merge)({}, preset, payload);
352
+ const fieldValidationRules = Object.values(this.schema.collections[collection].fields)
353
+ .map((field) => field.validation)
354
+ .filter((v) => v);
180
355
  const hasValidationRules = (0, lodash_1.isNil)(permission.validation) === false && Object.keys((_f = permission.validation) !== null && _f !== void 0 ? _f : {}).length > 0;
356
+ const hasFieldValidationRules = fieldValidationRules && fieldValidationRules.length > 0;
181
357
  const requiredColumns = [];
182
358
  for (const field of Object.values(this.schema.collections[collection].fields)) {
183
359
  const specials = (_g = field === null || field === void 0 ? void 0 : field.special) !== null && _g !== void 0 ? _g : [];
@@ -187,7 +363,7 @@ class AuthorizationService {
187
363
  requiredColumns.push(field);
188
364
  }
189
365
  }
190
- if (hasValidationRules === false && requiredColumns.length === 0) {
366
+ if (hasValidationRules === false && hasFieldValidationRules === false && requiredColumns.length === 0) {
191
367
  return payloadWithPresets;
192
368
  }
193
369
  if (requiredColumns.length > 0) {
@@ -207,8 +383,16 @@ class AuthorizationService {
207
383
  });
208
384
  }
209
385
  }
386
+ if (hasFieldValidationRules) {
387
+ if (permission.validation && Object.keys(permission.validation).length > 0) {
388
+ permission.validation = { _and: [permission.validation, ...fieldValidationRules] };
389
+ }
390
+ else {
391
+ permission.validation = { _and: fieldValidationRules };
392
+ }
393
+ }
210
394
  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)))));
395
+ 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
396
  if (validationErrors.length > 0)
213
397
  throw validationErrors;
214
398
  return payloadWithPresets;
@@ -228,14 +412,14 @@ class AuthorizationService {
228
412
  if (Array.isArray(pk)) {
229
413
  const result = await itemsService.readMany(pk, { ...query, limit: pk.length }, { permissionsAction: action });
230
414
  if (!result)
231
- throw new exceptions_1.ForbiddenException();
415
+ throw new exceptions_2.ForbiddenException();
232
416
  if (result.length !== pk.length)
233
- throw new exceptions_1.ForbiddenException();
417
+ throw new exceptions_2.ForbiddenException();
234
418
  }
235
419
  else {
236
420
  const result = await itemsService.readOne(pk, query, { permissionsAction: action });
237
421
  if (!result)
238
- throw new exceptions_1.ForbiddenException();
422
+ throw new exceptions_2.ForbiddenException();
239
423
  }
240
424
  }
241
425
  }
@@ -4,6 +4,7 @@ import Keyv from 'keyv';
4
4
  import { AbstractServiceOptions, Collection, CollectionMeta, MutationOptions } from '../types';
5
5
  import { Accountability, RawField, SchemaOverview } from '@directus/shared/types';
6
6
  import { Table } from 'knex-schema-inspector/dist/types/table';
7
+ import { Helpers } from '../database/helpers';
7
8
  export declare type RawCollection = {
8
9
  collection: string;
9
10
  fields?: RawField[];
@@ -12,6 +13,7 @@ export declare type RawCollection = {
12
13
  };
13
14
  export declare class CollectionsService {
14
15
  knex: Knex;
16
+ helpers: Helpers;
15
17
  accountability: Accountability | null;
16
18
  schemaInspector: ReturnType<typeof SchemaInspector>;
17
19
  schema: SchemaOverview;