@directus/api 21.0.0-rc.0 → 21.0.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 (285) hide show
  1. package/dist/app.js +5 -5
  2. package/dist/auth/drivers/ldap.js +4 -4
  3. package/dist/auth/drivers/local.js +4 -4
  4. package/dist/auth/drivers/oauth2.js +4 -4
  5. package/dist/auth/drivers/openid.js +4 -2
  6. package/dist/cache.d.ts +0 -1
  7. package/dist/cache.js +7 -25
  8. package/dist/cli/commands/bootstrap/index.js +2 -8
  9. package/dist/cli/commands/init/index.js +10 -9
  10. package/dist/cli/utils/defaults.d.ts +11 -4
  11. package/dist/cli/utils/defaults.js +1 -7
  12. package/dist/constants.d.ts +1 -1
  13. package/dist/controllers/auth.js +16 -5
  14. package/dist/controllers/permissions.js +2 -14
  15. package/dist/controllers/roles.js +1 -22
  16. package/dist/controllers/tus.js +27 -13
  17. package/dist/controllers/users.js +55 -0
  18. package/dist/database/helpers/fn/types.d.ts +1 -2
  19. package/dist/database/helpers/fn/types.js +1 -1
  20. package/dist/database/helpers/geometry/dialects/mssql.d.ts +1 -1
  21. package/dist/database/helpers/geometry/dialects/mssql.js +2 -4
  22. package/dist/database/helpers/geometry/dialects/mysql.js +1 -1
  23. package/dist/database/helpers/geometry/dialects/oracle.d.ts +1 -1
  24. package/dist/database/helpers/geometry/dialects/oracle.js +3 -5
  25. package/dist/database/helpers/geometry/types.d.ts +1 -1
  26. package/dist/database/helpers/geometry/types.js +2 -4
  27. package/dist/database/index.js +11 -8
  28. package/dist/database/migrations/20240305A-change-useragent-type.js +1 -1
  29. package/dist/database/migrations/20240716A-update-files-date-fields.js +33 -0
  30. package/dist/database/{run-ast/types.d.ts → run-ast.d.ts} +9 -3
  31. package/dist/database/run-ast.js +458 -0
  32. package/dist/flows.js +4 -3
  33. package/dist/logger/index.js +1 -1
  34. package/dist/middleware/authenticate.js +7 -2
  35. package/dist/middleware/cache.js +1 -1
  36. package/dist/middleware/check-ip.d.ts +2 -0
  37. package/dist/middleware/check-ip.js +37 -0
  38. package/dist/middleware/error-handler.d.ts +2 -2
  39. package/dist/middleware/error-handler.js +54 -51
  40. package/dist/middleware/get-permissions.d.ts +3 -0
  41. package/dist/middleware/get-permissions.js +10 -0
  42. package/dist/middleware/respond.js +1 -1
  43. package/dist/services/activity.js +10 -22
  44. package/dist/services/assets.d.ts +3 -2
  45. package/dist/services/assets.js +7 -15
  46. package/dist/services/authentication.js +18 -18
  47. package/dist/services/authorization.d.ts +17 -0
  48. package/dist/services/authorization.js +456 -0
  49. package/dist/services/collections.js +17 -18
  50. package/dist/services/fields.d.ts +4 -0
  51. package/dist/services/fields.js +53 -58
  52. package/dist/services/files/lib/get-sharp-instance.d.ts +2 -0
  53. package/dist/services/files/lib/get-sharp-instance.js +10 -0
  54. package/dist/services/files/utils/get-metadata.js +7 -6
  55. package/dist/services/files.js +8 -10
  56. package/dist/services/graphql/index.d.ts +3 -3
  57. package/dist/services/graphql/index.js +22 -126
  58. package/dist/services/graphql/subscription.js +4 -2
  59. package/dist/services/import-export.js +4 -18
  60. package/dist/services/index.d.ts +2 -3
  61. package/dist/services/index.js +2 -3
  62. package/dist/services/items.js +44 -115
  63. package/dist/services/mail/index.d.ts +1 -1
  64. package/dist/services/mail/index.js +9 -1
  65. package/dist/services/meta.js +23 -60
  66. package/dist/services/notifications.js +6 -14
  67. package/dist/services/payload.d.ts +10 -9
  68. package/dist/services/payload.js +3 -18
  69. package/dist/services/{permissions.d.ts → permissions/index.d.ts} +7 -5
  70. package/dist/services/{permissions.js → permissions/index.js} +54 -30
  71. package/dist/{permissions → services/permissions}/lib/with-app-minimal-permissions.d.ts +1 -1
  72. package/dist/services/permissions/lib/with-app-minimal-permissions.js +13 -0
  73. package/dist/services/relations.d.ts +9 -1
  74. package/dist/services/relations.js +56 -31
  75. package/dist/services/roles.d.ts +12 -4
  76. package/dist/services/roles.js +424 -57
  77. package/dist/services/shares.d.ts +2 -0
  78. package/dist/services/shares.js +8 -12
  79. package/dist/services/specifications.d.ts +2 -2
  80. package/dist/services/specifications.js +27 -39
  81. package/dist/services/tus/data-store.js +4 -5
  82. package/dist/services/tus/server.d.ts +1 -1
  83. package/dist/services/tus/server.js +9 -2
  84. package/dist/services/users.d.ts +5 -1
  85. package/dist/services/users.js +161 -78
  86. package/dist/services/utils.js +7 -11
  87. package/dist/services/versions.d.ts +2 -0
  88. package/dist/services/versions.js +10 -34
  89. package/dist/telemetry/lib/get-report.js +2 -2
  90. package/dist/telemetry/utils/check-increased-user-limits.d.ts +7 -0
  91. package/dist/telemetry/utils/check-increased-user-limits.js +25 -0
  92. package/dist/telemetry/utils/get-role-counts-by-roles.d.ts +6 -0
  93. package/dist/telemetry/utils/get-role-counts-by-roles.js +27 -0
  94. package/dist/telemetry/utils/get-role-counts-by-users.d.ts +11 -0
  95. package/dist/telemetry/utils/get-role-counts-by-users.js +34 -0
  96. package/dist/telemetry/utils/get-user-count.d.ts +8 -0
  97. package/dist/telemetry/utils/get-user-count.js +33 -0
  98. package/dist/telemetry/utils/get-user-counts-by-roles.d.ts +7 -0
  99. package/dist/telemetry/utils/get-user-counts-by-roles.js +35 -0
  100. package/dist/types/ast.d.ts +1 -43
  101. package/dist/types/items.d.ts +0 -11
  102. package/dist/utils/apply-query.d.ts +3 -4
  103. package/dist/utils/apply-query.js +16 -39
  104. package/dist/utils/get-accountability-for-role.js +25 -16
  105. package/dist/utils/get-accountability-for-token.js +16 -17
  106. package/dist/utils/get-ast-from-query.d.ts +13 -0
  107. package/dist/utils/get-ast-from-query.js +297 -0
  108. package/dist/utils/get-cache-key.d.ts +1 -1
  109. package/dist/utils/get-cache-key.js +1 -12
  110. package/dist/utils/get-column.d.ts +1 -2
  111. package/dist/utils/get-column.js +0 -1
  112. package/dist/utils/get-permissions.d.ts +2 -0
  113. package/dist/utils/get-permissions.js +150 -0
  114. package/dist/utils/get-schema.js +3 -3
  115. package/dist/utils/get-service.js +1 -5
  116. package/dist/utils/merge-permissions-for-share.d.ts +4 -0
  117. package/dist/utils/merge-permissions-for-share.js +109 -0
  118. package/dist/utils/merge-permissions.d.ts +3 -0
  119. package/dist/utils/merge-permissions.js +95 -0
  120. package/dist/utils/reduce-schema.d.ts +6 -4
  121. package/dist/utils/reduce-schema.js +32 -16
  122. package/dist/websocket/authenticate.d.ts +2 -0
  123. package/dist/websocket/authenticate.js +12 -0
  124. package/dist/websocket/controllers/graphql.js +4 -1
  125. package/dist/websocket/controllers/hooks.js +0 -4
  126. package/dist/websocket/controllers/rest.js +2 -0
  127. package/dist/websocket/handlers/subscribe.js +2 -0
  128. package/dist/websocket/utils/items.d.ts +1 -1
  129. package/package.json +36 -37
  130. package/dist/controllers/access.d.ts +0 -2
  131. package/dist/controllers/access.js +0 -148
  132. package/dist/controllers/policies.d.ts +0 -2
  133. package/dist/controllers/policies.js +0 -169
  134. package/dist/database/get-ast-from-query/get-ast-from-query.d.ts +0 -16
  135. package/dist/database/get-ast-from-query/get-ast-from-query.js +0 -82
  136. package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +0 -13
  137. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +0 -69
  138. package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +0 -15
  139. package/dist/database/get-ast-from-query/lib/parse-fields.js +0 -190
  140. package/dist/database/get-ast-from-query/utils/get-deep-query.d.ts +0 -14
  141. package/dist/database/get-ast-from-query/utils/get-deep-query.js +0 -17
  142. package/dist/database/get-ast-from-query/utils/get-related-collection.d.ts +0 -2
  143. package/dist/database/get-ast-from-query/utils/get-related-collection.js +0 -13
  144. package/dist/database/get-ast-from-query/utils/get-relation.d.ts +0 -2
  145. package/dist/database/get-ast-from-query/utils/get-relation.js +0 -7
  146. package/dist/database/migrations/20240710A-permissions-policies.js +0 -169
  147. package/dist/database/run-ast/lib/get-db-query.d.ts +0 -4
  148. package/dist/database/run-ast/lib/get-db-query.js +0 -208
  149. package/dist/database/run-ast/lib/parse-current-level.d.ts +0 -7
  150. package/dist/database/run-ast/lib/parse-current-level.js +0 -41
  151. package/dist/database/run-ast/run-ast.d.ts +0 -7
  152. package/dist/database/run-ast/run-ast.js +0 -107
  153. package/dist/database/run-ast/types.js +0 -1
  154. package/dist/database/run-ast/utils/apply-case-when.d.ts +0 -16
  155. package/dist/database/run-ast/utils/apply-case-when.js +0 -26
  156. package/dist/database/run-ast/utils/apply-parent-filters.d.ts +0 -3
  157. package/dist/database/run-ast/utils/apply-parent-filters.js +0 -55
  158. package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +0 -10
  159. package/dist/database/run-ast/utils/get-column-pre-processor.js +0 -57
  160. package/dist/database/run-ast/utils/get-field-alias.d.ts +0 -2
  161. package/dist/database/run-ast/utils/get-field-alias.js +0 -4
  162. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.d.ts +0 -5
  163. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.js +0 -23
  164. package/dist/database/run-ast/utils/merge-with-parent-items.d.ts +0 -3
  165. package/dist/database/run-ast/utils/merge-with-parent-items.js +0 -87
  166. package/dist/database/run-ast/utils/remove-temporary-fields.d.ts +0 -3
  167. package/dist/database/run-ast/utils/remove-temporary-fields.js +0 -73
  168. package/dist/permissions/cache.d.ts +0 -2
  169. package/dist/permissions/cache.js +0 -23
  170. package/dist/permissions/lib/fetch-permissions.d.ts +0 -10
  171. package/dist/permissions/lib/fetch-permissions.js +0 -55
  172. package/dist/permissions/lib/fetch-policies.d.ts +0 -7
  173. package/dist/permissions/lib/fetch-policies.js +0 -28
  174. package/dist/permissions/lib/fetch-roles-tree.d.ts +0 -3
  175. package/dist/permissions/lib/fetch-roles-tree.js +0 -28
  176. package/dist/permissions/lib/with-app-minimal-permissions.js +0 -10
  177. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.d.ts +0 -7
  178. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js +0 -56
  179. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.d.ts +0 -3
  180. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js +0 -16
  181. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.d.ts +0 -8
  182. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +0 -24
  183. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.d.ts +0 -9
  184. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +0 -31
  185. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.d.ts +0 -16
  186. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js +0 -27
  187. package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +0 -10
  188. package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +0 -23
  189. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +0 -5
  190. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +0 -7
  191. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +0 -5
  192. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +0 -10
  193. package/dist/permissions/modules/fetch-global-access/types.d.ts +0 -4
  194. package/dist/permissions/modules/fetch-global-access/types.js +0 -1
  195. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +0 -4
  196. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +0 -27
  197. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts +0 -12
  198. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +0 -32
  199. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +0 -4
  200. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js +0 -29
  201. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.d.ts +0 -4
  202. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.js +0 -49
  203. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.d.ts +0 -3
  204. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.js +0 -56
  205. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.d.ts +0 -4
  206. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.js +0 -8
  207. package/dist/permissions/modules/process-ast/lib/inject-cases.d.ts +0 -9
  208. package/dist/permissions/modules/process-ast/lib/inject-cases.js +0 -93
  209. package/dist/permissions/modules/process-ast/process-ast.d.ts +0 -9
  210. package/dist/permissions/modules/process-ast/process-ast.js +0 -39
  211. package/dist/permissions/modules/process-ast/types.d.ts +0 -24
  212. package/dist/permissions/modules/process-ast/types.js +0 -1
  213. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.d.ts +0 -2
  214. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.js +0 -7
  215. package/dist/permissions/modules/process-ast/utils/dedupe-access.d.ts +0 -12
  216. package/dist/permissions/modules/process-ast/utils/dedupe-access.js +0 -30
  217. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.d.ts +0 -15
  218. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +0 -50
  219. package/dist/permissions/modules/process-ast/utils/find-related-collection.d.ts +0 -3
  220. package/dist/permissions/modules/process-ast/utils/find-related-collection.js +0 -9
  221. package/dist/permissions/modules/process-ast/utils/flatten-filter.d.ts +0 -3
  222. package/dist/permissions/modules/process-ast/utils/flatten-filter.js +0 -34
  223. package/dist/permissions/modules/process-ast/utils/format-a2o-key.d.ts +0 -1
  224. package/dist/permissions/modules/process-ast/utils/format-a2o-key.js +0 -3
  225. package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +0 -5
  226. package/dist/permissions/modules/process-ast/utils/get-info-for-path.js +0 -7
  227. package/dist/permissions/modules/process-ast/utils/has-item-permissions.d.ts +0 -2
  228. package/dist/permissions/modules/process-ast/utils/has-item-permissions.js +0 -3
  229. package/dist/permissions/modules/process-ast/utils/stringify-query-path.d.ts +0 -2
  230. package/dist/permissions/modules/process-ast/utils/stringify-query-path.js +0 -3
  231. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.d.ts +0 -3
  232. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.js +0 -16
  233. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.d.ts +0 -2
  234. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.js +0 -12
  235. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.d.ts +0 -2
  236. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.js +0 -28
  237. package/dist/permissions/modules/process-payload/lib/is-field-nullable.d.ts +0 -5
  238. package/dist/permissions/modules/process-payload/lib/is-field-nullable.js +0 -12
  239. package/dist/permissions/modules/process-payload/process-payload.d.ts +0 -13
  240. package/dist/permissions/modules/process-payload/process-payload.js +0 -77
  241. package/dist/permissions/modules/validate-access/lib/validate-collection-access.d.ts +0 -12
  242. package/dist/permissions/modules/validate-access/lib/validate-collection-access.js +0 -11
  243. package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +0 -9
  244. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +0 -33
  245. package/dist/permissions/modules/validate-access/validate-access.d.ts +0 -14
  246. package/dist/permissions/modules/validate-access/validate-access.js +0 -28
  247. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.d.ts +0 -1
  248. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.js +0 -8
  249. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +0 -5
  250. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js +0 -10
  251. package/dist/permissions/types.d.ts +0 -6
  252. package/dist/permissions/types.js +0 -1
  253. package/dist/permissions/utils/create-default-accountability.d.ts +0 -2
  254. package/dist/permissions/utils/create-default-accountability.js +0 -11
  255. package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +0 -8
  256. package/dist/permissions/utils/extract-required-dynamic-variable-context.js +0 -27
  257. package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +0 -9
  258. package/dist/permissions/utils/fetch-dynamic-variable-context.js +0 -43
  259. package/dist/permissions/utils/filter-policies-by-ip.d.ts +0 -2
  260. package/dist/permissions/utils/filter-policies-by-ip.js +0 -15
  261. package/dist/permissions/utils/get-unaliased-field-key.d.ts +0 -5
  262. package/dist/permissions/utils/get-unaliased-field-key.js +0 -17
  263. package/dist/permissions/utils/process-permissions.d.ts +0 -7
  264. package/dist/permissions/utils/process-permissions.js +0 -9
  265. package/dist/permissions/utils/with-cache.d.ts +0 -10
  266. package/dist/permissions/utils/with-cache.js +0 -25
  267. package/dist/services/access.d.ts +0 -10
  268. package/dist/services/access.js +0 -43
  269. package/dist/services/policies.d.ts +0 -12
  270. package/dist/services/policies.js +0 -87
  271. package/dist/telemetry/utils/check-user-limits.d.ts +0 -5
  272. package/dist/telemetry/utils/check-user-limits.js +0 -19
  273. package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +0 -17
  274. package/dist/utils/fetch-user-count/fetch-access-lookup.js +0 -22
  275. package/dist/utils/fetch-user-count/fetch-access-roles.d.ts +0 -16
  276. package/dist/utils/fetch-user-count/fetch-access-roles.js +0 -37
  277. package/dist/utils/fetch-user-count/fetch-active-users.d.ts +0 -6
  278. package/dist/utils/fetch-user-count/fetch-active-users.js +0 -3
  279. package/dist/utils/fetch-user-count/fetch-user-count.d.ts +0 -12
  280. package/dist/utils/fetch-user-count/fetch-user-count.js +0 -57
  281. package/dist/utils/fetch-user-count/get-user-count-query.d.ts +0 -20
  282. package/dist/utils/fetch-user-count/get-user-count-query.js +0 -17
  283. package/dist/utils/validate-user-count-integrity.d.ts +0 -13
  284. package/dist/utils/validate-user-count-integrity.js +0 -29
  285. /package/dist/database/migrations/{20240710A-permissions-policies.d.ts → 20240716A-update-files-date-fields.d.ts} +0 -0
@@ -1,9 +0,0 @@
1
- import type { Permission } from '@directus/types';
2
- import type { AST } from '../../../../types/ast.js';
3
- /**
4
- * Mutates passed AST
5
- *
6
- * @param ast - Read query AST
7
- * @param permissions - Expected to be filtered down for the policies and action already
8
- */
9
- export declare function injectCases(ast: AST, permissions: Permission[]): void;
@@ -1,93 +0,0 @@
1
- import { getUnaliasedFieldKey } from '../../../utils/get-unaliased-field-key.js';
2
- import { dedupeAccess } from '../utils/dedupe-access.js';
3
- import { hasItemPermissions } from '../utils/has-item-permissions.js';
4
- import { uniq } from 'lodash-es';
5
- /**
6
- * Mutates passed AST
7
- *
8
- * @param ast - Read query AST
9
- * @param permissions - Expected to be filtered down for the policies and action already
10
- */
11
- export function injectCases(ast, permissions) {
12
- ast.cases = processChildren(ast.name, ast.children, permissions);
13
- }
14
- function processChildren(collection, children, permissions) {
15
- // Use uniq here, since there might be multiple duplications due to aliases or functions
16
- const requestedKeys = uniq(children.map(getUnaliasedFieldKey));
17
- const { cases, caseMap, allowedFields } = getCases(collection, permissions, requestedKeys);
18
- // TODO this can be optimized if there is only one rule to skip the whole case/where system,
19
- // since fields that are not allowed at all are already filtered out
20
- // TODO this can be optimized if all cases are the same for all requested keys, as those should be
21
- //
22
- for (const child of children) {
23
- // If there's one or more permissions that allow full access to this field, we can safe some
24
- // query perf overhead by ignoring the whole case/where system
25
- const fieldKey = getUnaliasedFieldKey(child);
26
- if (allowedFields.has('*') || allowedFields.has(fieldKey))
27
- continue;
28
- const globalWhenCase = caseMap['*'];
29
- const fieldWhenCase = caseMap[fieldKey];
30
- // Validation should catch any fields that are attempted to be read that don't have any access control configured.
31
- // When there are no access rules for this field, and no rules for "all" fields `*`, we missed something in the validation
32
- // and should abort.
33
- if (!globalWhenCase && !fieldWhenCase) {
34
- throw new Error(`Cannot extract access permissions for field "${fieldKey}" in collection "${collection}"`);
35
- }
36
- // Global and field can't both be undefined as per the error check prior
37
- child.whenCase = [...(globalWhenCase ?? []), ...(fieldWhenCase ?? [])];
38
- if (child.type === 'm2o') {
39
- child.cases = processChildren(child.relation.related_collection, child.children, permissions);
40
- }
41
- if (child.type === 'o2m') {
42
- child.cases = processChildren(child.relation.collection, child.children, permissions);
43
- }
44
- if (child.type === 'a2o') {
45
- for (const collection of child.names) {
46
- child.cases[collection] = processChildren(collection, child.children[collection] ?? [], permissions);
47
- }
48
- }
49
- if (child.type === 'functionField') {
50
- const { cases } = getCases(child.relatedCollection, permissions, []);
51
- child.cases = cases;
52
- }
53
- }
54
- return cases;
55
- }
56
- function getCases(collection, permissions, requestedKeys) {
57
- const permissionsForCollection = permissions.filter((permission) => permission.collection === collection);
58
- const rules = dedupeAccess(permissionsForCollection);
59
- const cases = [];
60
- const caseMap = {};
61
- // TODO this can be optimized if there is only one rule to skip the whole case/where system,
62
- // since fields that are not allowed at all are already filtered out
63
- // TODO this can be optimized if all cases are the same for all requested keys, as those should be
64
- //
65
- let index = 0;
66
- for (const { rule, fields } of rules) {
67
- // If none of the fields in the current permissions rule overlap with the actually requested
68
- // fields in the AST, we can ignore this case altogether
69
- if (requestedKeys.length > 0 &&
70
- fields.has('*') === false &&
71
- Array.from(fields).every((field) => requestedKeys.includes(field) === false)) {
72
- continue;
73
- }
74
- if (rule === null)
75
- continue;
76
- cases.push(rule);
77
- for (const field of fields) {
78
- caseMap[field] = [...(caseMap[field] ?? []), index];
79
- }
80
- index++;
81
- }
82
- // Field that are allowed no matter what conditions exist for the item. These come from
83
- // permissions where the item read access is "everything"
84
- const allowedFields = new Set(permissionsForCollection
85
- .filter((permission) => hasItemPermissions(permission) === false)
86
- .map((permission) => permission.fields ?? [])
87
- .flat());
88
- return {
89
- cases,
90
- caseMap,
91
- allowedFields,
92
- };
93
- }
@@ -1,9 +0,0 @@
1
- import type { Accountability, PermissionsAction } from '@directus/types';
2
- import type { AST } from '../../../types/ast.js';
3
- import type { Context } from '../../types.js';
4
- export interface ProcessAstOptions {
5
- ast: AST;
6
- action: PermissionsAction;
7
- accountability: Accountability | null;
8
- }
9
- export declare function processAst(options: ProcessAstOptions, context: Context): Promise<AST>;
@@ -1,39 +0,0 @@
1
- import { fetchPermissions } from '../../lib/fetch-permissions.js';
2
- import { fetchPolicies } from '../../lib/fetch-policies.js';
3
- import { fieldMapFromAst } from './lib/field-map-from-ast.js';
4
- import { injectCases } from './lib/inject-cases.js';
5
- import { collectionsInFieldMap } from './utils/collections-in-field-map.js';
6
- import { validatePathPermissions } from './utils/validate-path/validate-path-permissions.js';
7
- import { validatePathExistence } from './utils/validate-path/validate-path-existence.js';
8
- export async function processAst(options, context) {
9
- // FieldMap is a Map of paths in the AST, with each path containing the collection and fields in
10
- // that collection that the AST path tries to access
11
- const fieldMap = fieldMapFromAst(options.ast, context.schema);
12
- const collections = collectionsInFieldMap(fieldMap);
13
- if (!options.accountability || options.accountability.admin) {
14
- // Validate the field existence, even if no permissions apply to the current accountability
15
- for (const [path, { collection, fields }] of [...fieldMap.read.entries(), ...fieldMap.other.entries()]) {
16
- validatePathExistence(path, collection, fields, context.schema);
17
- }
18
- return options.ast;
19
- }
20
- const policies = await fetchPolicies(options.accountability, context);
21
- const permissions = await fetchPermissions({ action: options.action, policies, collections, accountability: options.accountability }, context);
22
- const readPermissions = options.action === 'read'
23
- ? permissions
24
- : await fetchPermissions({ action: 'read', policies, collections, accountability: options.accountability }, context);
25
- // Validate field existence first
26
- for (const [path, { collection, fields }] of [...fieldMap.read.entries(), ...fieldMap.other.entries()]) {
27
- validatePathExistence(path, collection, fields, context.schema);
28
- }
29
- // Validate permissions for the fields
30
- for (const [path, { collection, fields }] of fieldMap.other.entries()) {
31
- validatePathPermissions(path, permissions, collection, fields);
32
- }
33
- // Validate permission for read only fields
34
- for (const [path, { collection, fields }] of fieldMap.read.entries()) {
35
- validatePathPermissions(path, readPermissions, collection, fields);
36
- }
37
- injectCases(options.ast, permissions);
38
- return options.ast;
39
- }
@@ -1,24 +0,0 @@
1
- export type CollectionKey = string;
2
- export type FieldKey = string;
3
- export type QueryPath = string[];
4
- /**
5
- * Key is dot-notation QueryPath, f.e. `category.created_by`.
6
- * Value contains collection context for that path, and fields fetched within
7
- */
8
- export type FieldMapEntries = Map<string, {
9
- collection: CollectionKey;
10
- fields: Set<FieldKey>;
11
- }>;
12
- /**
13
- * FieldMapEntries that require only read permissions and those that require action specific permissions
14
- */
15
- export type FieldMap = {
16
- read: FieldMapEntries;
17
- other: FieldMapEntries;
18
- };
19
- export interface AccessRow {
20
- policy: {
21
- id: string;
22
- ip_access: string[] | null;
23
- };
24
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- import type { CollectionKey, FieldMap } from '../types.js';
2
- export declare function collectionsInFieldMap(fieldMap: FieldMap): CollectionKey[];
@@ -1,7 +0,0 @@
1
- export function collectionsInFieldMap(fieldMap) {
2
- const collections = new Set();
3
- for (const { collection } of [...fieldMap.other.values(), ...fieldMap.read.values()]) {
4
- collections.add(collection);
5
- }
6
- return Array.from(collections);
7
- }
@@ -1,12 +0,0 @@
1
- import type { Filter, Permission } from '@directus/types';
2
- /**
3
- * Deduplicate the permissions sets by merging the field sets based on the access control rules
4
- * (`permissions` in Permission rows)
5
- *
6
- * This allows the cases injection to be more efficient by not having to generate duplicate
7
- * case/when clauses for permission sets where the rule access is identical
8
- */
9
- export declare function dedupeAccess(permissions: Permission[]): {
10
- rule: Filter;
11
- fields: Set<string>;
12
- }[];
@@ -1,30 +0,0 @@
1
- import hash from 'object-hash';
2
- /**
3
- * Deduplicate the permissions sets by merging the field sets based on the access control rules
4
- * (`permissions` in Permission rows)
5
- *
6
- * This allows the cases injection to be more efficient by not having to generate duplicate
7
- * case/when clauses for permission sets where the rule access is identical
8
- */
9
- export function dedupeAccess(permissions) {
10
- // Map of `ruleHash: fields[]`
11
- const map = new Map();
12
- for (const permission of permissions) {
13
- const rule = permission.permissions ?? {};
14
- // Two JS objects can't be equality checked. Object-hash will resort any nested arrays
15
- // deterministically meaning that this can be used to compare two rule sets where the array
16
- // order does not matter
17
- const ruleHash = hash(rule, {
18
- algorithm: 'passthrough',
19
- unorderedArrays: true,
20
- });
21
- if (map.has(ruleHash) === false) {
22
- map.set(ruleHash, { rule, fields: new Set() });
23
- }
24
- const info = map.get(ruleHash);
25
- for (const field of permission.fields ?? []) {
26
- info.fields.add(field);
27
- }
28
- }
29
- return Array.from(map.values());
30
- }
@@ -1,15 +0,0 @@
1
- import type { Query } from '@directus/types';
2
- /**
3
- * Converts the passed Query object into a unique list of path arrays, for example:
4
- *
5
- * ```
6
- * [
7
- * ['author', 'age'],
8
- * ['category']
9
- * ]
10
- * ```
11
- */
12
- export declare function extractPathsFromQuery(query: Query): {
13
- paths: string[][];
14
- readOnlyPaths: string[][];
15
- };
@@ -1,50 +0,0 @@
1
- import { isEqual, uniqWith } from 'lodash-es';
2
- import { flattenFilter } from './flatten-filter.js';
3
- /**
4
- * Converts the passed Query object into a unique list of path arrays, for example:
5
- *
6
- * ```
7
- * [
8
- * ['author', 'age'],
9
- * ['category']
10
- * ]
11
- * ```
12
- */
13
- export function extractPathsFromQuery(query) {
14
- /**
15
- * All nested paths used in the current query scope.
16
- * This is generated by flattening the filters and adding in the used sort/aggregate fields.
17
- */
18
- const paths = [];
19
- const readOnlyPaths = [];
20
- if (query.filter) {
21
- flattenFilter(readOnlyPaths, query.filter);
22
- }
23
- if (query.sort) {
24
- for (const field of query.sort) {
25
- // Sort can have dot notation fields for sorting on m2o values Sort fields can start with
26
- // `-` to indicate descending order, which should be dropped for permissions checks
27
- readOnlyPaths.push(field.split('.').map((field) => (field.startsWith('-') ? field.substring(1) : field)));
28
- }
29
- }
30
- if (query.aggregate) {
31
- for (const fields of Object.values(query.aggregate)) {
32
- for (const field of fields) {
33
- // Aggregate doesn't currently support aggregating on nested fields, but it doesn't hurt
34
- // to standardize it in the validation layer
35
- paths.push(field.split('.'));
36
- }
37
- }
38
- }
39
- if (query.group) {
40
- for (const field of query.group) {
41
- // Grouping doesn't currently support grouping on nested fields, but it doesn't hurt to
42
- // standardize it in the validation layer
43
- paths.push(field.split('.'));
44
- }
45
- }
46
- return {
47
- paths: uniqWith(paths, isEqual),
48
- readOnlyPaths: uniqWith(readOnlyPaths, isEqual),
49
- };
50
- }
@@ -1,3 +0,0 @@
1
- import type { SchemaOverview } from '@directus/types';
2
- import type { CollectionKey, FieldKey } from '../types.js';
3
- export declare function findRelatedCollection(collection: CollectionKey, field: FieldKey, schema: SchemaOverview): CollectionKey | null;
@@ -1,9 +0,0 @@
1
- import { getRelationInfo } from '../../../../utils/get-relation-info.js';
2
- export function findRelatedCollection(collection, field, schema) {
3
- const { relation } = getRelationInfo(schema.relations, collection, field);
4
- if (!relation)
5
- return null;
6
- const isO2m = relation.related_collection === collection;
7
- const relatedCollectionName = isO2m ? relation.collection : relation.related_collection;
8
- return relatedCollectionName;
9
- }
@@ -1,3 +0,0 @@
1
- import type { Query } from '@directus/types';
2
- import type { FieldKey } from '../types.js';
3
- export declare function flattenFilter(paths: FieldKey[][], filter: Query['filter']): void;
@@ -1,34 +0,0 @@
1
- export function flattenFilter(paths, filter) {
2
- if (!filter)
3
- return;
4
- const stack = [{ current: filter, path: [] }];
5
- while (stack.length > 0) {
6
- const { current, path } = stack.pop();
7
- if (typeof current === 'object' && current !== null) {
8
- // If the current nested value is an array, we ignore the array order and flatten all
9
- // nested objects
10
- const isArray = Array.isArray(current);
11
- for (const key in current) {
12
- if (!key.startsWith('_') || key === '_and' || key === '_or' || key === '_some' || key === '_none') {
13
- // Only deepen the path if the current value can contain more keys
14
- stack.push({
15
- current: current[key],
16
- path: isArray ? path : [...path, key],
17
- });
18
- }
19
- else {
20
- // Ignore all operators and logical grouping in the field paths
21
- const parts = path.filter((part) => part.startsWith('_') === false);
22
- if (parts.length > 0)
23
- paths.push(parts);
24
- }
25
- }
26
- }
27
- else {
28
- // Ignore all operators and logical grouping in the field paths
29
- const parts = path.filter((part) => part.startsWith('_') === false);
30
- if (parts.length > 0)
31
- paths.push(parts);
32
- }
33
- }
34
- }
@@ -1 +0,0 @@
1
- export declare function formatA2oKey(fieldKey: string, collection: string): string;
@@ -1,3 +0,0 @@
1
- export function formatA2oKey(fieldKey, collection) {
2
- return `${fieldKey}:${collection}`;
3
- }
@@ -1,5 +0,0 @@
1
- import type { CollectionKey, FieldMap, QueryPath } from '../types.js';
2
- export declare function getInfoForPath(fieldMap: FieldMap, group: keyof FieldMap, path: QueryPath, collection: CollectionKey): {
3
- collection: string;
4
- fields: Set<string>;
5
- };
@@ -1,7 +0,0 @@
1
- export function getInfoForPath(fieldMap, group, path, collection) {
2
- const pathStr = path.join('.');
3
- if (fieldMap[group].has(pathStr) === false) {
4
- fieldMap[group].set(pathStr, { collection, fields: new Set() });
5
- }
6
- return fieldMap[group].get(pathStr);
7
- }
@@ -1,2 +0,0 @@
1
- import type { Permission } from '@directus/types';
2
- export declare function hasItemPermissions(permission: Permission): boolean;
@@ -1,3 +0,0 @@
1
- export function hasItemPermissions(permission) {
2
- return permission.permissions !== null && Object.keys(permission.permissions).length > 0;
3
- }
@@ -1,2 +0,0 @@
1
- import type { QueryPath } from '../types.js';
2
- export declare function stringifyQueryPath(queryPath: QueryPath): string;
@@ -1,3 +0,0 @@
1
- export function stringifyQueryPath(queryPath) {
2
- return queryPath.join('.');
3
- }
@@ -1,3 +0,0 @@
1
- import { type DirectusError } from '@directus/errors';
2
- export declare function createCollectionForbiddenError(path: string, collection: string): DirectusError<any>;
3
- export declare function createFieldsForbiddenError(path: string, collection: string, fields: string[]): DirectusError<any>;
@@ -1,16 +0,0 @@
1
- import { ForbiddenError } from '@directus/errors';
2
- export function createCollectionForbiddenError(path, collection) {
3
- const pathSuffix = path === '' ? 'root' : `"${path}"`;
4
- return new ForbiddenError({
5
- reason: `You don't have permission to access collection "${collection}" or it does not exist. Queried in ${pathSuffix}.`,
6
- });
7
- }
8
- export function createFieldsForbiddenError(path, collection, fields) {
9
- const pathSuffix = path === '' ? 'root' : `"${path}"`;
10
- const fieldStr = fields.map((field) => `"${field}"`).join(', ');
11
- return new ForbiddenError({
12
- reason: fields.length === 1
13
- ? `You don't have permission to access field ${fieldStr} in collection "${collection}" or it does not exist. Queried in ${pathSuffix}.`
14
- : `You don't have permission to access fields ${fieldStr} in collection "${collection}" or they do not exist. Queried in ${pathSuffix}.`,
15
- });
16
- }
@@ -1,2 +0,0 @@
1
- import type { SchemaOverview } from '@directus/types';
2
- export declare function validatePathExistence(path: string, collection: string, fields: Set<string>, schema: SchemaOverview): void;
@@ -1,12 +0,0 @@
1
- import { createCollectionForbiddenError, createFieldsForbiddenError } from './create-error.js';
2
- export function validatePathExistence(path, collection, fields, schema) {
3
- const collectionInfo = schema.collections[collection];
4
- if (collectionInfo === undefined) {
5
- throw createCollectionForbiddenError(path, collection);
6
- }
7
- const requestedFields = Array.from(fields);
8
- const nonExistentFields = requestedFields.filter((field) => collectionInfo.fields[field] === undefined);
9
- if (nonExistentFields.length > 0) {
10
- throw createFieldsForbiddenError(path, collection, nonExistentFields);
11
- }
12
- }
@@ -1,2 +0,0 @@
1
- import type { Permission } from '@directus/types';
2
- export declare function validatePathPermissions(path: string, permissions: Permission[], collection: string, fields: Set<string>): void;
@@ -1,28 +0,0 @@
1
- import { createCollectionForbiddenError, createFieldsForbiddenError } from './create-error.js';
2
- export function validatePathPermissions(path, permissions, collection, fields) {
3
- const permissionsForCollection = permissions.filter((permission) => permission.collection === collection);
4
- if (permissionsForCollection.length === 0) {
5
- throw createCollectionForbiddenError(path, collection);
6
- }
7
- // Set of all fields that are allowed to be queried combined
8
- const allowedFields = new Set();
9
- for (const { fields } of permissionsForCollection) {
10
- if (!fields) {
11
- continue;
12
- }
13
- for (const field of fields) {
14
- if (field === '*') {
15
- // Early exit in case all fields are allowed
16
- return;
17
- }
18
- allowedFields.add(field);
19
- }
20
- }
21
- const requestedFields = Array.from(fields);
22
- const forbiddenFields = allowedFields.has('*')
23
- ? []
24
- : requestedFields.filter((field) => allowedFields.has(field) === false);
25
- if (forbiddenFields.length > 0) {
26
- throw createFieldsForbiddenError(path, collection, forbiddenFields);
27
- }
28
- }
@@ -1,5 +0,0 @@
1
- import type { FieldOverview } from '@directus/types';
2
- /**
3
- * Checks if a given field is allowed to be set to `null`.
4
- */
5
- export declare function isFieldNullable(field: FieldOverview): boolean;
@@ -1,12 +0,0 @@
1
- import { GENERATE_SPECIAL } from '../../../../constants.js';
2
- /**
3
- * Checks if a given field is allowed to be set to `null`.
4
- */
5
- export function isFieldNullable(field) {
6
- if (field.nullable)
7
- return true;
8
- if (field.generated)
9
- return true;
10
- const hasGenerateSpecial = GENERATE_SPECIAL.some((name) => field.special.includes(name));
11
- return hasGenerateSpecial;
12
- }
@@ -1,13 +0,0 @@
1
- import type { Accountability, Item, PermissionsAction } from '@directus/types';
2
- import type { Context } from '../../types.js';
3
- export interface ProcessPayloadOptions {
4
- accountability: Accountability;
5
- action: PermissionsAction;
6
- collection: string;
7
- payload: Item;
8
- }
9
- /**
10
- * @note this only validates the top-level fields. The expectation is that this function is called
11
- * for each level of nested insert separately
12
- */
13
- export declare function processPayload(options: ProcessPayloadOptions, context: Context): Promise<any>;
@@ -1,77 +0,0 @@
1
- import { ForbiddenError } from '@directus/errors';
2
- import { validatePayload } from '@directus/utils';
3
- import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
4
- import { assign, difference, uniq } from 'lodash-es';
5
- import { fetchPermissions } from '../../lib/fetch-permissions.js';
6
- import { fetchPolicies } from '../../lib/fetch-policies.js';
7
- import { isFieldNullable } from './lib/is-field-nullable.js';
8
- /**
9
- * @note this only validates the top-level fields. The expectation is that this function is called
10
- * for each level of nested insert separately
11
- */
12
- export async function processPayload(options, context) {
13
- let permissions;
14
- let permissionValidationRules = [];
15
- if (!options.accountability.admin) {
16
- const policies = await fetchPolicies(options.accountability, context);
17
- permissions = await fetchPermissions({ action: options.action, policies, collections: [options.collection], accountability: options.accountability }, context);
18
- if (permissions.length === 0) {
19
- throw new ForbiddenError({
20
- reason: `You don't have permission to "${options.action}" from collection "${options.collection}" or it does not exist.`,
21
- });
22
- }
23
- const fieldsAllowed = uniq(permissions.map(({ fields }) => fields ?? []).flat());
24
- if (fieldsAllowed.includes('*') === false) {
25
- const fieldsUsed = Object.keys(options.payload);
26
- const notAllowed = difference(fieldsUsed, fieldsAllowed);
27
- if (notAllowed.length > 0) {
28
- const fieldStr = notAllowed.map((field) => `"${field}"`).join(', ');
29
- throw new ForbiddenError({
30
- reason: notAllowed.length === 1
31
- ? `You don't have permission to access field ${fieldStr} in collection "${options.collection}" or it does not exist.`
32
- : `You don't have permission to access fields ${fieldStr} in collection "${options.collection}" or they do not exist.`,
33
- });
34
- }
35
- }
36
- permissionValidationRules = permissions.map(({ validation }) => validation);
37
- }
38
- const fields = Object.values(context.schema.collections[options.collection]?.fields ?? {});
39
- const fieldValidationRules = [];
40
- for (const field of fields) {
41
- if (!isFieldNullable(field)) {
42
- const isSubmissionRequired = options.action === 'create' && field.defaultValue === null;
43
- if (isSubmissionRequired) {
44
- fieldValidationRules.push({
45
- [field.field]: {
46
- _submitted: true,
47
- },
48
- });
49
- }
50
- fieldValidationRules.push({
51
- [field.field]: {
52
- _nnull: true,
53
- },
54
- });
55
- }
56
- fieldValidationRules.push(field.validation);
57
- }
58
- const validationRules = [...fieldValidationRules, ...permissionValidationRules].filter((rule) => {
59
- if (rule === null)
60
- return false;
61
- if (Object.keys(rule).length === 0)
62
- return false;
63
- return true;
64
- });
65
- if (validationRules.length > 0) {
66
- const validationErrors = [];
67
- validationErrors.push(...validatePayload({ _and: validationRules }, options.payload)
68
- .map((error) => error.details.map((details) => new FailedValidationError(joiValidationErrorItemToErrorExtensions(details))))
69
- .flat());
70
- if (validationErrors.length > 0)
71
- throw validationErrors;
72
- }
73
- if (!permissions)
74
- return options.payload;
75
- const presets = permissions.map((permission) => permission.presets);
76
- return assign({}, ...presets, options.payload);
77
- }
@@ -1,12 +0,0 @@
1
- import type { Accountability, PermissionsAction } from '@directus/types';
2
- import type { Context } from '../../../types.js';
3
- export interface ValidateCollectionAccessOptions {
4
- accountability: Accountability;
5
- action: PermissionsAction;
6
- collection: string;
7
- }
8
- /**
9
- * Check if you have (limited) access to a given collection by making sure there's at least 1
10
- * permission rule available for the collection and action combo
11
- */
12
- export declare function validateCollectionAccess(options: ValidateCollectionAccessOptions, context: Context): Promise<boolean>;
@@ -1,11 +0,0 @@
1
- import { fetchPermissions } from '../../../lib/fetch-permissions.js';
2
- import { fetchPolicies } from '../../../lib/fetch-policies.js';
3
- /**
4
- * Check if you have (limited) access to a given collection by making sure there's at least 1
5
- * permission rule available for the collection and action combo
6
- */
7
- export async function validateCollectionAccess(options, context) {
8
- const policies = await fetchPolicies(options.accountability, context);
9
- const permissions = await fetchPermissions({ action: options.action, policies, collections: [options.collection], accountability: options.accountability }, context);
10
- return permissions.length > 0;
11
- }
@@ -1,9 +0,0 @@
1
- import type { Accountability, PermissionsAction, PrimaryKey } from '@directus/types';
2
- import type { Context } from '../../../types.js';
3
- export interface ValidateItemAccessOptions {
4
- accountability: Accountability;
5
- action: PermissionsAction;
6
- collection: string;
7
- primaryKeys: PrimaryKey[];
8
- }
9
- export declare function validateItemAccess(options: ValidateItemAccessOptions, context: Context): Promise<boolean>;