directus 9.2.0 → 9.4.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 (90) hide show
  1. package/dist/app.js +5 -3
  2. package/dist/auth/auth.d.ts +4 -6
  3. package/dist/auth/auth.js +5 -9
  4. package/dist/auth/drivers/ldap.d.ts +3 -3
  5. package/dist/auth/drivers/ldap.js +11 -4
  6. package/dist/auth/drivers/local.d.ts +2 -2
  7. package/dist/auth/drivers/local.js +5 -12
  8. package/dist/auth/drivers/oauth2.d.ts +4 -4
  9. package/dist/auth/drivers/oauth2.js +47 -21
  10. package/dist/auth/drivers/openid.d.ts +4 -4
  11. package/dist/auth/drivers/openid.js +35 -19
  12. package/dist/cli/commands/bootstrap/index.js +3 -2
  13. package/dist/cli/commands/init/index.js +3 -7
  14. package/dist/cli/commands/schema/apply.js +1 -1
  15. package/dist/cli/utils/defaults.d.ts +11 -0
  16. package/dist/cli/utils/defaults.js +14 -0
  17. package/dist/constants.d.ts +8 -0
  18. package/dist/constants.js +16 -2
  19. package/dist/controllers/shares.d.ts +2 -0
  20. package/dist/controllers/shares.js +212 -0
  21. package/dist/controllers/users.js +21 -9
  22. package/dist/database/migrations/20211211A-add-shares.d.ts +3 -0
  23. package/dist/database/migrations/20211211A-add-shares.js +37 -0
  24. package/dist/database/run-ast.js +5 -5
  25. package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -15
  26. package/dist/database/system-data/app-access-permissions/index.d.ts +1 -0
  27. package/dist/database/system-data/app-access-permissions/index.js +4 -2
  28. package/dist/database/system-data/app-access-permissions/schema-access-permissions.yaml +17 -0
  29. package/dist/database/system-data/collections/collections.yaml +3 -0
  30. package/dist/database/system-data/fields/sessions.yaml +1 -1
  31. package/dist/database/system-data/fields/shares.yaml +73 -0
  32. package/dist/database/system-data/fields/users.yaml +1 -1
  33. package/dist/database/system-data/relations/relations.yaml +15 -0
  34. package/dist/emitter.d.ts +3 -2
  35. package/dist/emitter.js +13 -6
  36. package/dist/exceptions/index.d.ts +2 -0
  37. package/dist/exceptions/index.js +2 -0
  38. package/dist/exceptions/invalid-token.d.ts +4 -0
  39. package/dist/exceptions/invalid-token.js +10 -0
  40. package/dist/exceptions/unexpected-response.d.ts +4 -0
  41. package/dist/exceptions/unexpected-response.js +10 -0
  42. package/dist/extensions.d.ts +1 -0
  43. package/dist/extensions.js +10 -4
  44. package/dist/middleware/authenticate.js +5 -15
  45. package/dist/middleware/check-ip.js +9 -6
  46. package/dist/middleware/respond.js +4 -1
  47. package/dist/services/activity.d.ts +2 -1
  48. package/dist/services/activity.js +2 -2
  49. package/dist/services/authentication.d.ts +2 -7
  50. package/dist/services/authentication.js +81 -41
  51. package/dist/services/authorization.js +3 -3
  52. package/dist/services/collections.d.ts +1 -2
  53. package/dist/services/collections.js +2 -2
  54. package/dist/services/files.d.ts +2 -2
  55. package/dist/services/files.js +14 -8
  56. package/dist/services/graphql.js +16 -5
  57. package/dist/services/index.d.ts +1 -0
  58. package/dist/services/index.js +1 -0
  59. package/dist/services/items.d.ts +1 -15
  60. package/dist/services/notifications.d.ts +2 -2
  61. package/dist/services/permissions.d.ts +2 -2
  62. package/dist/services/roles.d.ts +2 -2
  63. package/dist/services/shares.d.ts +17 -0
  64. package/dist/services/shares.js +135 -0
  65. package/dist/services/specifications.js +1 -1
  66. package/dist/services/users.d.ts +2 -2
  67. package/dist/services/users.js +8 -6
  68. package/dist/services/webhooks.d.ts +2 -2
  69. package/dist/tests/database/migrations/run.test.d.ts +1 -0
  70. package/dist/tests/database/migrations/run.test.js +29 -0
  71. package/dist/types/ast.d.ts +3 -3
  72. package/dist/types/auth.d.ts +31 -0
  73. package/dist/types/extensions.d.ts +2 -0
  74. package/dist/types/items.d.ts +14 -0
  75. package/dist/utils/apply-query.d.ts +0 -38
  76. package/dist/utils/apply-query.js +67 -69
  77. package/dist/utils/apply-snapshot.js +69 -14
  78. package/dist/utils/get-ast-from-query.js +3 -3
  79. package/dist/utils/get-permissions.d.ts +2 -2
  80. package/dist/utils/get-permissions.js +117 -72
  81. package/dist/utils/get-relation-type.d.ts +1 -1
  82. package/dist/utils/get-relation-type.js +1 -1
  83. package/dist/utils/merge-permissions-for-share.d.ts +5 -0
  84. package/dist/utils/merge-permissions-for-share.js +116 -0
  85. package/dist/utils/merge-permissions.d.ts +13 -1
  86. package/dist/utils/merge-permissions.js +29 -21
  87. package/dist/utils/reduce-schema.d.ts +2 -2
  88. package/dist/utils/reduce-schema.js +7 -7
  89. package/dist/utils/user-name.js +3 -0
  90. package/package.json +14 -13
@@ -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 ? mergePerm(current, val) : val);
11
+ acc.set(key, current ? mergePermission(strategy, current, val) : val);
12
12
  return acc;
13
13
  }, new Map())
14
14
  .values();
15
- const result = Array.from(mergedPermissions).map((perm) => {
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 mergePerm(currentPerm, newPerm) {
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
- if (newPerm.permissions && !(0, lodash_1.isEmpty)(newPerm.permissions)) {
27
- if (currentPerm.permissions && Object.keys(currentPerm.permissions)[0] === '_or') {
24
+ if (newPerm.permissions) {
25
+ if (currentPerm.permissions && Object.keys(currentPerm.permissions)[0] === logicalKey) {
28
26
  permissions = {
29
- _or: [...currentPerm.permissions._or, newPerm.permissions],
27
+ [logicalKey]: [
28
+ ...currentPerm.permissions[logicalKey],
29
+ newPerm.permissions,
30
+ ],
30
31
  };
31
32
  }
32
- else if (currentPerm.permissions && !(0, lodash_1.isEmpty)(currentPerm.permissions)) {
33
+ else if (currentPerm.permissions) {
33
34
  permissions = {
34
- _or: [currentPerm.permissions, newPerm.permissions],
35
+ [logicalKey]: [currentPerm.permissions, newPerm.permissions],
35
36
  };
36
37
  }
37
38
  else {
38
39
  permissions = {
39
- _or: [newPerm.permissions],
40
+ [logicalKey]: [newPerm.permissions],
40
41
  };
41
42
  }
42
43
  }
43
44
  if (newPerm.validation) {
44
- if (currentPerm.validation && Object.keys(currentPerm.validation)[0] === '_or') {
45
+ if (currentPerm.validation && Object.keys(currentPerm.validation)[0] === logicalKey) {
45
46
  validation = {
46
- _or: [...currentPerm.validation._or, newPerm.validation],
47
+ [logicalKey]: [
48
+ ...currentPerm.validation[logicalKey],
49
+ newPerm.validation,
50
+ ],
47
51
  };
48
52
  }
49
53
  else if (currentPerm.validation) {
50
54
  validation = {
51
- _or: [currentPerm.validation, newPerm.validation],
55
+ [logicalKey]: [currentPerm.validation, newPerm.validation],
52
56
  };
53
57
  }
54
58
  else {
55
59
  validation = {
56
- _or: [newPerm.validation],
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 { Accountability, PermissionsAction } from '@directus/shared/types';
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, accountability: Accountability | null, actions?: PermissionsAction[]): 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, accountability, actions = ['create', 'read', 'update', 'delete']) {
13
- var _a, _b, _c, _d, _e;
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 = (_b = (_a = accountability === null || accountability === void 0 ? void 0 : accountability.permissions) === null || _a === void 0 ? void 0 : _a.filter((permission) => actions.includes(permission.action)).reduce((acc, permission) => {
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 && _b !== void 0 ? _b : {};
26
+ }, {})) !== null && _a !== void 0 ? _a : {};
27
27
  for (const [collectionName, collection] of Object.entries(schema.collections)) {
28
- if ((_c = accountability === null || accountability === void 0 ? void 0 : accountability.permissions) === null || _c === void 0 ? void 0 : _c.some((permission) => permission.collection === collectionName && actions.includes(permission.action))) {
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 (((_d = allowedFieldsInCollection[collectionName]) === null || _d === void 0 ? void 0 : _d.includes('*')) ||
32
- ((_e = allowedFieldsInCollection[collectionName]) === null || _e === void 0 ? void 0 : _e.includes(fieldName))) {
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
  }
@@ -2,6 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.userName = void 0;
4
4
  function userName(user) {
5
+ if (!user) {
6
+ return 'Unknown User';
7
+ }
5
8
  if (user.first_name && user.last_name) {
6
9
  return `${user.first_name} ${user.last_name}`;
7
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directus",
3
- "version": "9.2.0",
3
+ "version": "9.4.0",
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.",
@@ -76,16 +76,16 @@
76
76
  ],
77
77
  "dependencies": {
78
78
  "@aws-sdk/client-ses": "^3.40.0",
79
- "@directus/app": "9.2.0",
80
- "@directus/drive": "9.2.0",
81
- "@directus/drive-azure": "9.2.0",
82
- "@directus/drive-gcs": "9.2.0",
83
- "@directus/drive-s3": "9.2.0",
84
- "@directus/extensions-sdk": "9.2.0",
85
- "@directus/format-title": "9.2.0",
86
- "@directus/schema": "9.2.0",
87
- "@directus/shared": "9.2.0",
88
- "@directus/specs": "9.2.0",
79
+ "@directus/app": "9.4.0",
80
+ "@directus/drive": "9.4.0",
81
+ "@directus/drive-azure": "9.4.0",
82
+ "@directus/drive-gcs": "9.4.0",
83
+ "@directus/drive-s3": "9.4.0",
84
+ "@directus/extensions-sdk": "9.4.0",
85
+ "@directus/format-title": "9.4.0",
86
+ "@directus/schema": "9.4.0",
87
+ "@directus/shared": "9.4.0",
88
+ "@directus/specs": "9.4.0",
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": "776c105aacfda559a1ebf49cb2220599652f6c48",
172
+ "gitHead": "a47b9cec0f24a7585108744b3c816bfc620297c4",
173
173
  "devDependencies": {
174
174
  "@types/async": "3.2.10",
175
175
  "@types/atob": "2.1.2",
@@ -199,7 +199,7 @@
199
199
  "@types/nodemailer": "6.4.4",
200
200
  "@types/object-hash": "2.2.1",
201
201
  "@types/qs": "6.9.7",
202
- "@types/sanitize-html": "^2.5.0",
202
+ "@types/sanitize-html": "2.5.0",
203
203
  "@types/sharp": "0.29.4",
204
204
  "@types/stream-json": "1.7.1",
205
205
  "@types/supertest": "2.0.11",
@@ -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"