@directus/api 21.0.0-rc.0 → 21.0.1

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 (286) 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 +2 -3
  7. package/dist/cache.js +9 -27
  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 +2 -1
  43. package/dist/request/is-denied-ip.js +7 -1
  44. package/dist/services/activity.js +10 -22
  45. package/dist/services/assets.d.ts +3 -2
  46. package/dist/services/assets.js +7 -15
  47. package/dist/services/authentication.js +18 -18
  48. package/dist/services/authorization.d.ts +17 -0
  49. package/dist/services/authorization.js +456 -0
  50. package/dist/services/collections.js +17 -18
  51. package/dist/services/fields.d.ts +4 -0
  52. package/dist/services/fields.js +53 -58
  53. package/dist/services/files/lib/get-sharp-instance.d.ts +2 -0
  54. package/dist/services/files/lib/get-sharp-instance.js +10 -0
  55. package/dist/services/files/utils/get-metadata.js +7 -6
  56. package/dist/services/files.js +8 -10
  57. package/dist/services/graphql/index.d.ts +3 -3
  58. package/dist/services/graphql/index.js +22 -126
  59. package/dist/services/graphql/subscription.js +4 -2
  60. package/dist/services/import-export.js +4 -18
  61. package/dist/services/index.d.ts +2 -3
  62. package/dist/services/index.js +2 -3
  63. package/dist/services/items.js +44 -115
  64. package/dist/services/mail/index.d.ts +1 -1
  65. package/dist/services/mail/index.js +9 -1
  66. package/dist/services/meta.js +23 -60
  67. package/dist/services/notifications.js +6 -14
  68. package/dist/services/payload.d.ts +10 -9
  69. package/dist/services/payload.js +3 -18
  70. package/dist/services/{permissions.d.ts → permissions/index.d.ts} +7 -5
  71. package/dist/services/{permissions.js → permissions/index.js} +54 -30
  72. package/dist/{permissions → services/permissions}/lib/with-app-minimal-permissions.d.ts +1 -1
  73. package/dist/services/permissions/lib/with-app-minimal-permissions.js +13 -0
  74. package/dist/services/relations.d.ts +9 -1
  75. package/dist/services/relations.js +56 -31
  76. package/dist/services/roles.d.ts +12 -4
  77. package/dist/services/roles.js +424 -57
  78. package/dist/services/shares.d.ts +2 -0
  79. package/dist/services/shares.js +8 -12
  80. package/dist/services/specifications.d.ts +2 -2
  81. package/dist/services/specifications.js +27 -39
  82. package/dist/services/tus/data-store.js +4 -5
  83. package/dist/services/tus/server.d.ts +1 -1
  84. package/dist/services/tus/server.js +9 -2
  85. package/dist/services/users.d.ts +5 -1
  86. package/dist/services/users.js +161 -78
  87. package/dist/services/utils.js +7 -11
  88. package/dist/services/versions.d.ts +2 -0
  89. package/dist/services/versions.js +10 -34
  90. package/dist/telemetry/lib/get-report.js +2 -2
  91. package/dist/telemetry/utils/check-increased-user-limits.d.ts +7 -0
  92. package/dist/telemetry/utils/check-increased-user-limits.js +25 -0
  93. package/dist/telemetry/utils/get-role-counts-by-roles.d.ts +6 -0
  94. package/dist/telemetry/utils/get-role-counts-by-roles.js +27 -0
  95. package/dist/telemetry/utils/get-role-counts-by-users.d.ts +11 -0
  96. package/dist/telemetry/utils/get-role-counts-by-users.js +34 -0
  97. package/dist/telemetry/utils/get-user-count.d.ts +8 -0
  98. package/dist/telemetry/utils/get-user-count.js +33 -0
  99. package/dist/telemetry/utils/get-user-counts-by-roles.d.ts +7 -0
  100. package/dist/telemetry/utils/get-user-counts-by-roles.js +35 -0
  101. package/dist/types/ast.d.ts +1 -43
  102. package/dist/types/items.d.ts +0 -11
  103. package/dist/utils/apply-query.d.ts +3 -4
  104. package/dist/utils/apply-query.js +16 -39
  105. package/dist/utils/get-accountability-for-role.js +25 -16
  106. package/dist/utils/get-accountability-for-token.js +16 -17
  107. package/dist/utils/get-ast-from-query.d.ts +13 -0
  108. package/dist/utils/get-ast-from-query.js +297 -0
  109. package/dist/utils/get-cache-key.d.ts +1 -1
  110. package/dist/utils/get-cache-key.js +1 -12
  111. package/dist/utils/get-column.d.ts +1 -2
  112. package/dist/utils/get-column.js +0 -1
  113. package/dist/utils/get-permissions.d.ts +2 -0
  114. package/dist/utils/get-permissions.js +150 -0
  115. package/dist/utils/get-schema.js +22 -27
  116. package/dist/utils/get-service.js +1 -5
  117. package/dist/utils/merge-permissions-for-share.d.ts +4 -0
  118. package/dist/utils/merge-permissions-for-share.js +109 -0
  119. package/dist/utils/merge-permissions.d.ts +3 -0
  120. package/dist/utils/merge-permissions.js +95 -0
  121. package/dist/utils/reduce-schema.d.ts +6 -4
  122. package/dist/utils/reduce-schema.js +32 -16
  123. package/dist/websocket/authenticate.d.ts +2 -0
  124. package/dist/websocket/authenticate.js +12 -0
  125. package/dist/websocket/controllers/graphql.js +4 -1
  126. package/dist/websocket/controllers/hooks.js +0 -4
  127. package/dist/websocket/controllers/rest.js +2 -0
  128. package/dist/websocket/handlers/subscribe.js +2 -0
  129. package/dist/websocket/utils/items.d.ts +1 -1
  130. package/package.json +35 -36
  131. package/dist/controllers/access.d.ts +0 -2
  132. package/dist/controllers/access.js +0 -148
  133. package/dist/controllers/policies.d.ts +0 -2
  134. package/dist/controllers/policies.js +0 -169
  135. package/dist/database/get-ast-from-query/get-ast-from-query.d.ts +0 -16
  136. package/dist/database/get-ast-from-query/get-ast-from-query.js +0 -82
  137. package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +0 -13
  138. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +0 -69
  139. package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +0 -15
  140. package/dist/database/get-ast-from-query/lib/parse-fields.js +0 -190
  141. package/dist/database/get-ast-from-query/utils/get-deep-query.d.ts +0 -14
  142. package/dist/database/get-ast-from-query/utils/get-deep-query.js +0 -17
  143. package/dist/database/get-ast-from-query/utils/get-related-collection.d.ts +0 -2
  144. package/dist/database/get-ast-from-query/utils/get-related-collection.js +0 -13
  145. package/dist/database/get-ast-from-query/utils/get-relation.d.ts +0 -2
  146. package/dist/database/get-ast-from-query/utils/get-relation.js +0 -7
  147. package/dist/database/migrations/20240710A-permissions-policies.js +0 -169
  148. package/dist/database/run-ast/lib/get-db-query.d.ts +0 -4
  149. package/dist/database/run-ast/lib/get-db-query.js +0 -208
  150. package/dist/database/run-ast/lib/parse-current-level.d.ts +0 -7
  151. package/dist/database/run-ast/lib/parse-current-level.js +0 -41
  152. package/dist/database/run-ast/run-ast.d.ts +0 -7
  153. package/dist/database/run-ast/run-ast.js +0 -107
  154. package/dist/database/run-ast/types.js +0 -1
  155. package/dist/database/run-ast/utils/apply-case-when.d.ts +0 -16
  156. package/dist/database/run-ast/utils/apply-case-when.js +0 -26
  157. package/dist/database/run-ast/utils/apply-parent-filters.d.ts +0 -3
  158. package/dist/database/run-ast/utils/apply-parent-filters.js +0 -55
  159. package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +0 -10
  160. package/dist/database/run-ast/utils/get-column-pre-processor.js +0 -57
  161. package/dist/database/run-ast/utils/get-field-alias.d.ts +0 -2
  162. package/dist/database/run-ast/utils/get-field-alias.js +0 -4
  163. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.d.ts +0 -5
  164. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.js +0 -23
  165. package/dist/database/run-ast/utils/merge-with-parent-items.d.ts +0 -3
  166. package/dist/database/run-ast/utils/merge-with-parent-items.js +0 -87
  167. package/dist/database/run-ast/utils/remove-temporary-fields.d.ts +0 -3
  168. package/dist/database/run-ast/utils/remove-temporary-fields.js +0 -73
  169. package/dist/permissions/cache.d.ts +0 -2
  170. package/dist/permissions/cache.js +0 -23
  171. package/dist/permissions/lib/fetch-permissions.d.ts +0 -10
  172. package/dist/permissions/lib/fetch-permissions.js +0 -55
  173. package/dist/permissions/lib/fetch-policies.d.ts +0 -7
  174. package/dist/permissions/lib/fetch-policies.js +0 -28
  175. package/dist/permissions/lib/fetch-roles-tree.d.ts +0 -3
  176. package/dist/permissions/lib/fetch-roles-tree.js +0 -28
  177. package/dist/permissions/lib/with-app-minimal-permissions.js +0 -10
  178. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.d.ts +0 -7
  179. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js +0 -56
  180. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.d.ts +0 -3
  181. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js +0 -16
  182. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.d.ts +0 -8
  183. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +0 -24
  184. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.d.ts +0 -9
  185. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +0 -31
  186. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.d.ts +0 -16
  187. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js +0 -27
  188. package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +0 -10
  189. package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +0 -23
  190. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +0 -5
  191. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +0 -7
  192. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +0 -5
  193. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +0 -10
  194. package/dist/permissions/modules/fetch-global-access/types.d.ts +0 -4
  195. package/dist/permissions/modules/fetch-global-access/types.js +0 -1
  196. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +0 -4
  197. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +0 -27
  198. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts +0 -12
  199. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +0 -32
  200. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +0 -4
  201. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js +0 -29
  202. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.d.ts +0 -4
  203. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.js +0 -49
  204. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.d.ts +0 -3
  205. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.js +0 -56
  206. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.d.ts +0 -4
  207. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.js +0 -8
  208. package/dist/permissions/modules/process-ast/lib/inject-cases.d.ts +0 -9
  209. package/dist/permissions/modules/process-ast/lib/inject-cases.js +0 -93
  210. package/dist/permissions/modules/process-ast/process-ast.d.ts +0 -9
  211. package/dist/permissions/modules/process-ast/process-ast.js +0 -39
  212. package/dist/permissions/modules/process-ast/types.d.ts +0 -24
  213. package/dist/permissions/modules/process-ast/types.js +0 -1
  214. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.d.ts +0 -2
  215. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.js +0 -7
  216. package/dist/permissions/modules/process-ast/utils/dedupe-access.d.ts +0 -12
  217. package/dist/permissions/modules/process-ast/utils/dedupe-access.js +0 -30
  218. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.d.ts +0 -15
  219. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +0 -50
  220. package/dist/permissions/modules/process-ast/utils/find-related-collection.d.ts +0 -3
  221. package/dist/permissions/modules/process-ast/utils/find-related-collection.js +0 -9
  222. package/dist/permissions/modules/process-ast/utils/flatten-filter.d.ts +0 -3
  223. package/dist/permissions/modules/process-ast/utils/flatten-filter.js +0 -34
  224. package/dist/permissions/modules/process-ast/utils/format-a2o-key.d.ts +0 -1
  225. package/dist/permissions/modules/process-ast/utils/format-a2o-key.js +0 -3
  226. package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +0 -5
  227. package/dist/permissions/modules/process-ast/utils/get-info-for-path.js +0 -7
  228. package/dist/permissions/modules/process-ast/utils/has-item-permissions.d.ts +0 -2
  229. package/dist/permissions/modules/process-ast/utils/has-item-permissions.js +0 -3
  230. package/dist/permissions/modules/process-ast/utils/stringify-query-path.d.ts +0 -2
  231. package/dist/permissions/modules/process-ast/utils/stringify-query-path.js +0 -3
  232. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.d.ts +0 -3
  233. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.js +0 -16
  234. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.d.ts +0 -2
  235. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.js +0 -12
  236. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.d.ts +0 -2
  237. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.js +0 -28
  238. package/dist/permissions/modules/process-payload/lib/is-field-nullable.d.ts +0 -5
  239. package/dist/permissions/modules/process-payload/lib/is-field-nullable.js +0 -12
  240. package/dist/permissions/modules/process-payload/process-payload.d.ts +0 -13
  241. package/dist/permissions/modules/process-payload/process-payload.js +0 -77
  242. package/dist/permissions/modules/validate-access/lib/validate-collection-access.d.ts +0 -12
  243. package/dist/permissions/modules/validate-access/lib/validate-collection-access.js +0 -11
  244. package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +0 -9
  245. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +0 -33
  246. package/dist/permissions/modules/validate-access/validate-access.d.ts +0 -14
  247. package/dist/permissions/modules/validate-access/validate-access.js +0 -28
  248. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.d.ts +0 -1
  249. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.js +0 -8
  250. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +0 -5
  251. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js +0 -10
  252. package/dist/permissions/types.d.ts +0 -6
  253. package/dist/permissions/types.js +0 -1
  254. package/dist/permissions/utils/create-default-accountability.d.ts +0 -2
  255. package/dist/permissions/utils/create-default-accountability.js +0 -11
  256. package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +0 -8
  257. package/dist/permissions/utils/extract-required-dynamic-variable-context.js +0 -27
  258. package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +0 -9
  259. package/dist/permissions/utils/fetch-dynamic-variable-context.js +0 -43
  260. package/dist/permissions/utils/filter-policies-by-ip.d.ts +0 -2
  261. package/dist/permissions/utils/filter-policies-by-ip.js +0 -15
  262. package/dist/permissions/utils/get-unaliased-field-key.d.ts +0 -5
  263. package/dist/permissions/utils/get-unaliased-field-key.js +0 -17
  264. package/dist/permissions/utils/process-permissions.d.ts +0 -7
  265. package/dist/permissions/utils/process-permissions.js +0 -9
  266. package/dist/permissions/utils/with-cache.d.ts +0 -10
  267. package/dist/permissions/utils/with-cache.js +0 -25
  268. package/dist/services/access.d.ts +0 -10
  269. package/dist/services/access.js +0 -43
  270. package/dist/services/policies.d.ts +0 -12
  271. package/dist/services/policies.js +0 -87
  272. package/dist/telemetry/utils/check-user-limits.d.ts +0 -5
  273. package/dist/telemetry/utils/check-user-limits.js +0 -19
  274. package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +0 -17
  275. package/dist/utils/fetch-user-count/fetch-access-lookup.js +0 -22
  276. package/dist/utils/fetch-user-count/fetch-access-roles.d.ts +0 -16
  277. package/dist/utils/fetch-user-count/fetch-access-roles.js +0 -37
  278. package/dist/utils/fetch-user-count/fetch-active-users.d.ts +0 -6
  279. package/dist/utils/fetch-user-count/fetch-active-users.js +0 -3
  280. package/dist/utils/fetch-user-count/fetch-user-count.d.ts +0 -12
  281. package/dist/utils/fetch-user-count/fetch-user-count.js +0 -57
  282. package/dist/utils/fetch-user-count/get-user-count-query.d.ts +0 -20
  283. package/dist/utils/fetch-user-count/get-user-count-query.js +0 -17
  284. package/dist/utils/validate-user-count-integrity.d.ts +0 -13
  285. package/dist/utils/validate-user-count-integrity.js +0 -29
  286. /package/dist/database/migrations/{20240710A-permissions-policies.d.ts → 20240716A-update-files-date-fields.d.ts} +0 -0
@@ -0,0 +1,456 @@
1
+ import { ForbiddenError } from '@directus/errors';
2
+ import { validatePayload } from '@directus/utils';
3
+ import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
4
+ import { cloneDeep, flatten, isArray, isNil, merge, reduce, uniq, uniqWith } from 'lodash-es';
5
+ import { GENERATE_SPECIAL } from '../constants.js';
6
+ import getDatabase from '../database/index.js';
7
+ import { getRelationInfo } from '../utils/get-relation-info.js';
8
+ import { parseFilterKey } from '../utils/parse-filter-key.js';
9
+ import { ItemsService } from './items.js';
10
+ import { PayloadService } from './payload.js';
11
+ export class AuthorizationService {
12
+ knex;
13
+ accountability;
14
+ payloadService;
15
+ schema;
16
+ constructor(options) {
17
+ this.knex = options.knex || getDatabase();
18
+ this.accountability = options.accountability || null;
19
+ this.schema = options.schema;
20
+ this.payloadService = new PayloadService('directus_permissions', {
21
+ knex: this.knex,
22
+ schema: this.schema,
23
+ });
24
+ }
25
+ async processAST(ast, action = 'read') {
26
+ const collectionsRequested = getCollectionsFromAST(ast);
27
+ const permissionsForCollections = uniqWith(this.accountability?.permissions?.filter((permission) => {
28
+ return (permission.action === action &&
29
+ collectionsRequested.map(({ collection }) => collection).includes(permission.collection));
30
+ }), (curr, prev) => curr.collection === prev.collection && curr.action === prev.action && curr.role === prev.role) ?? [];
31
+ // If the permissions don't match the collections, you don't have permission to read all of them
32
+ const uniqueCollectionsRequestedCount = uniq(collectionsRequested.map(({ collection }) => collection)).length;
33
+ if (uniqueCollectionsRequestedCount !== permissionsForCollections.length) {
34
+ throw new ForbiddenError();
35
+ }
36
+ validateFields(ast);
37
+ validateFilterPermissions(ast, this.schema, action, this.accountability);
38
+ applyFilters(ast, this.accountability);
39
+ return ast;
40
+ /**
41
+ * Traverses the AST and returns an array of all collections that are being fetched
42
+ */
43
+ function getCollectionsFromAST(ast) {
44
+ const collections = [];
45
+ if (ast.type === 'a2o') {
46
+ collections.push(...ast.names.map((name) => ({ collection: name, field: ast.fieldKey })));
47
+ for (const children of Object.values(ast.children)) {
48
+ for (const nestedNode of children) {
49
+ if (nestedNode.type !== 'field' && nestedNode.type !== 'functionField') {
50
+ collections.push(...getCollectionsFromAST(nestedNode));
51
+ }
52
+ }
53
+ }
54
+ }
55
+ else {
56
+ collections.push({
57
+ collection: ast.name,
58
+ field: ast.type === 'root' ? null : ast.fieldKey,
59
+ });
60
+ for (const nestedNode of ast.children) {
61
+ if (nestedNode.type === 'functionField') {
62
+ collections.push({
63
+ collection: nestedNode.relatedCollection,
64
+ field: null,
65
+ });
66
+ }
67
+ else if (nestedNode.type !== 'field') {
68
+ collections.push(...getCollectionsFromAST(nestedNode));
69
+ }
70
+ }
71
+ }
72
+ return collections;
73
+ }
74
+ function validateFields(ast) {
75
+ if (ast.type !== 'field' && ast.type !== 'functionField') {
76
+ if (ast.type === 'a2o') {
77
+ for (const [collection, children] of Object.entries(ast.children)) {
78
+ checkFields(collection, children, ast.query?.[collection]?.aggregate);
79
+ }
80
+ }
81
+ else {
82
+ checkFields(ast.name, ast.children, ast.query?.aggregate);
83
+ }
84
+ }
85
+ function checkFields(collection, children, aggregate) {
86
+ // We check the availability of the permissions in the step before this is run
87
+ const permissions = permissionsForCollections.find((permission) => permission.collection === collection);
88
+ const allowedFields = permissions.fields || [];
89
+ if (aggregate && allowedFields.includes('*') === false) {
90
+ for (const aliasMap of Object.values(aggregate)) {
91
+ if (!aliasMap)
92
+ continue;
93
+ for (const column of Object.values(aliasMap)) {
94
+ if (column === '*')
95
+ continue;
96
+ if (allowedFields.includes(column) === false)
97
+ throw new ForbiddenError();
98
+ }
99
+ }
100
+ }
101
+ for (const childNode of children) {
102
+ if (childNode.type !== 'field') {
103
+ validateFields(childNode);
104
+ continue;
105
+ }
106
+ if (allowedFields.includes('*'))
107
+ continue;
108
+ const { fieldName } = parseFilterKey(childNode.name);
109
+ if (allowedFields.includes(fieldName) === false) {
110
+ throw new ForbiddenError();
111
+ }
112
+ }
113
+ }
114
+ }
115
+ function validateFilterPermissions(ast, schema, action, accountability) {
116
+ let requiredFieldPermissions = {};
117
+ if (ast.type !== 'field' && ast.type !== 'functionField') {
118
+ if (ast.type === 'a2o') {
119
+ for (const collection of Object.keys(ast.children)) {
120
+ requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, extractRequiredFieldPermissions(collection, ast.query?.[collection]?.filter ?? {}));
121
+ for (const child of ast.children[collection]) {
122
+ const childPermissions = validateFilterPermissions(child, schema, action, accountability);
123
+ if (Object.keys(childPermissions).length > 0) {
124
+ //Only add relational field if deep child has a filter
125
+ if (child.type !== 'field') {
126
+ (requiredFieldPermissions[collection] || (requiredFieldPermissions[collection] = new Set())).add(child.fieldKey);
127
+ }
128
+ requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, childPermissions);
129
+ }
130
+ }
131
+ }
132
+ }
133
+ else {
134
+ requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, extractRequiredFieldPermissions(ast.name, ast.query?.filter ?? {}));
135
+ for (const child of ast.children) {
136
+ const childPermissions = validateFilterPermissions(child, schema, action, accountability);
137
+ if (Object.keys(childPermissions).length > 0) {
138
+ // Only add relational field if deep child has a filter
139
+ if (child.type !== 'field') {
140
+ (requiredFieldPermissions[ast.name] || (requiredFieldPermissions[ast.name] = new Set())).add(child.fieldKey);
141
+ }
142
+ requiredFieldPermissions = mergeRequiredFieldPermissions(requiredFieldPermissions, childPermissions);
143
+ }
144
+ }
145
+ }
146
+ }
147
+ if (ast.type === 'root') {
148
+ // Validate all required permissions once at the root level
149
+ checkFieldPermissions(ast.name, schema, action, requiredFieldPermissions, ast.query.alias);
150
+ }
151
+ return requiredFieldPermissions;
152
+ function extractRequiredFieldPermissions(collection, filter, parentCollection, parentField) {
153
+ return reduce(filter, function (result, filterValue, filterKey) {
154
+ if (filterKey.startsWith('_')) {
155
+ if (filterKey === '_and' || filterKey === '_or') {
156
+ if (isArray(filterValue)) {
157
+ for (const filter of filterValue) {
158
+ const requiredPermissions = extractRequiredFieldPermissions(collection, filter, parentCollection, parentField);
159
+ result = mergeRequiredFieldPermissions(result, requiredPermissions);
160
+ }
161
+ }
162
+ return result;
163
+ }
164
+ // Filter value is not a filter, so we should skip it
165
+ return result;
166
+ }
167
+ // virtual o2m/o2a filter in the form of `$FOLLOW(...)`
168
+ else if (collection && filterKey.startsWith('$FOLLOW')) {
169
+ (result[collection] || (result[collection] = new Set())).add(filterKey);
170
+ // add virtual relation to the required permissions
171
+ const { relation } = getRelationInfo([], collection, filterKey);
172
+ if (relation?.collection && relation?.field) {
173
+ (result[relation.collection] || (result[relation.collection] = new Set())).add(relation.field);
174
+ }
175
+ }
176
+ // a2o filter in the form of `item:collection`
177
+ else if (filterKey.includes(':')) {
178
+ const [field, collectionScope] = filterKey.split(':');
179
+ if (collection) {
180
+ // Add the `item` field to the required permissions
181
+ (result[collection] || (result[collection] = new Set())).add(field);
182
+ // Add the `collection` field to the required permissions
183
+ result[collection].add('collection');
184
+ }
185
+ else {
186
+ const relation = schema.relations.find((relation) => {
187
+ return ((relation.collection === parentCollection && relation.field === parentField) ||
188
+ (relation.related_collection === parentCollection && relation.meta?.one_field === parentField));
189
+ });
190
+ // Filter key not found in parent collection
191
+ if (!relation)
192
+ throw new ForbiddenError();
193
+ const relatedCollectionName = relation.related_collection === parentCollection ? relation.collection : relation.related_collection;
194
+ // Add the `item` field to the required permissions
195
+ (result[relatedCollectionName] || (result[relatedCollectionName] = new Set())).add(field);
196
+ // Add the `collection` field to the required permissions
197
+ result[relatedCollectionName].add('collection');
198
+ }
199
+ // Continue to parse the filter for nested `collection` afresh
200
+ const requiredPermissions = extractRequiredFieldPermissions(collectionScope, filterValue);
201
+ result = mergeRequiredFieldPermissions(result, requiredPermissions);
202
+ }
203
+ else {
204
+ if (collection) {
205
+ (result[collection] || (result[collection] = new Set())).add(filterKey);
206
+ }
207
+ else {
208
+ const relation = schema.relations.find((relation) => {
209
+ return ((relation.collection === parentCollection && relation.field === parentField) ||
210
+ (relation.related_collection === parentCollection && relation.meta?.one_field === parentField));
211
+ });
212
+ // Filter key not found in parent collection
213
+ if (!relation)
214
+ throw new ForbiddenError();
215
+ parentCollection =
216
+ relation.related_collection === parentCollection ? relation.collection : relation.related_collection;
217
+ (result[parentCollection] || (result[parentCollection] = new Set())).add(filterKey);
218
+ }
219
+ if (typeof filterValue === 'object') {
220
+ // Parent collection is undefined when we process the top level filter
221
+ if (!parentCollection)
222
+ parentCollection = collection;
223
+ for (const [childFilterKey, childFilterValue] of Object.entries(filterValue)) {
224
+ if (childFilterKey.startsWith('_')) {
225
+ if (childFilterKey === '_and' || childFilterKey === '_or') {
226
+ if (isArray(childFilterValue)) {
227
+ for (const filter of childFilterValue) {
228
+ const requiredPermissions = extractRequiredFieldPermissions('', filter, parentCollection, filterKey);
229
+ result = mergeRequiredFieldPermissions(result, requiredPermissions);
230
+ }
231
+ }
232
+ }
233
+ }
234
+ else {
235
+ const requiredPermissions = extractRequiredFieldPermissions('', filterValue, parentCollection, filterKey);
236
+ result = mergeRequiredFieldPermissions(result, requiredPermissions);
237
+ }
238
+ }
239
+ }
240
+ }
241
+ return result;
242
+ }, {});
243
+ }
244
+ function mergeRequiredFieldPermissions(current, child) {
245
+ for (const collection of Object.keys(child)) {
246
+ if (!current[collection]) {
247
+ current[collection] = child[collection];
248
+ }
249
+ else {
250
+ current[collection] = new Set([...current[collection], ...child[collection]]);
251
+ }
252
+ }
253
+ return current;
254
+ }
255
+ function checkFieldPermissions(rootCollection, schema, action, requiredPermissions, aliasMap) {
256
+ if (accountability?.admin === true)
257
+ return;
258
+ for (const collection of Object.keys(requiredPermissions)) {
259
+ const permission = accountability?.permissions?.find((permission) => permission.collection === collection && permission.action === 'read');
260
+ let allowedFields;
261
+ // Allow the filtering of top level ID for actions such as update and delete
262
+ if (action !== 'read' && collection === rootCollection) {
263
+ const actionPermission = accountability?.permissions?.find((permission) => permission.collection === collection && permission.action === action);
264
+ if (!actionPermission || !actionPermission.fields) {
265
+ throw new ForbiddenError();
266
+ }
267
+ allowedFields = permission?.fields
268
+ ? [...permission.fields, schema.collections[collection].primary]
269
+ : [schema.collections[collection].primary];
270
+ }
271
+ else if (!permission || !permission.fields) {
272
+ throw new ForbiddenError();
273
+ }
274
+ else {
275
+ allowedFields = permission.fields;
276
+ }
277
+ if (allowedFields.includes('*'))
278
+ continue;
279
+ // Allow legacy permissions with an empty fields array, where id can be accessed
280
+ if (allowedFields.length === 0)
281
+ allowedFields.push(schema.collections[collection].primary);
282
+ for (const field of requiredPermissions[collection]) {
283
+ if (field.startsWith('$FOLLOW'))
284
+ continue;
285
+ const { fieldName } = parseFilterKey(field);
286
+ let originalFieldName = fieldName;
287
+ if (collection === rootCollection && aliasMap?.[fieldName]) {
288
+ originalFieldName = aliasMap[fieldName];
289
+ }
290
+ if (!allowedFields.includes(originalFieldName)) {
291
+ throw new ForbiddenError();
292
+ }
293
+ }
294
+ }
295
+ }
296
+ }
297
+ function applyFilters(ast, accountability) {
298
+ if (ast.type === 'functionField') {
299
+ const collection = ast.relatedCollection;
300
+ updateFilterQuery(collection, ast.query);
301
+ }
302
+ else if (ast.type !== 'field') {
303
+ if (ast.type === 'a2o') {
304
+ const collections = Object.keys(ast.children);
305
+ for (const collection of collections) {
306
+ updateFilterQuery(collection, ast.query[collection]);
307
+ }
308
+ for (const [collection, children] of Object.entries(ast.children)) {
309
+ ast.children[collection] = children.map((child) => applyFilters(child, accountability));
310
+ }
311
+ }
312
+ else {
313
+ const collection = ast.name;
314
+ updateFilterQuery(collection, ast.query);
315
+ ast.children = ast.children.map((child) => applyFilters(child, accountability));
316
+ }
317
+ }
318
+ return ast;
319
+ function updateFilterQuery(collection, query) {
320
+ // We check the availability of the permissions in the step before this is run
321
+ const permissions = permissionsForCollections.find((permission) => permission.collection === collection);
322
+ if (!query.filter || Object.keys(query.filter).length === 0) {
323
+ query.filter = { _and: [] };
324
+ }
325
+ else {
326
+ query.filter = { _and: [query.filter] };
327
+ }
328
+ if (permissions.permissions && Object.keys(permissions.permissions).length > 0) {
329
+ query.filter._and.push(permissions.permissions);
330
+ }
331
+ if (query.filter._and.length === 0)
332
+ delete query.filter;
333
+ }
334
+ }
335
+ }
336
+ /**
337
+ * Checks if the provided payload matches the configured permissions, and adds the presets to the payload.
338
+ */
339
+ validatePayload(action, collection, data) {
340
+ const payload = cloneDeep(data);
341
+ let permission;
342
+ if (this.accountability?.admin === true) {
343
+ permission = {
344
+ id: 0,
345
+ role: this.accountability?.role,
346
+ collection,
347
+ action,
348
+ permissions: {},
349
+ validation: {},
350
+ fields: ['*'],
351
+ presets: {},
352
+ };
353
+ }
354
+ else {
355
+ permission = this.accountability?.permissions?.find((permission) => {
356
+ return permission.collection === collection && permission.action === action;
357
+ });
358
+ if (!permission)
359
+ throw new ForbiddenError();
360
+ // Check if you have permission to access the fields you're trying to access
361
+ const allowedFields = permission.fields || [];
362
+ if (allowedFields.includes('*') === false) {
363
+ const keysInData = Object.keys(payload);
364
+ const invalidKeys = keysInData.filter((fieldKey) => allowedFields.includes(fieldKey) === false);
365
+ if (invalidKeys.length > 0) {
366
+ throw new ForbiddenError();
367
+ }
368
+ }
369
+ }
370
+ const preset = permission.presets ?? {};
371
+ const payloadWithPresets = merge({}, preset, payload);
372
+ const fieldValidationRules = Object.values(this.schema.collections[collection].fields)
373
+ .map((field) => field.validation)
374
+ .filter((v) => v);
375
+ const hasValidationRules = isNil(permission.validation) === false && Object.keys(permission.validation ?? {}).length > 0;
376
+ const hasFieldValidationRules = fieldValidationRules && fieldValidationRules.length > 0;
377
+ const requiredColumns = [];
378
+ for (const field of Object.values(this.schema.collections[collection].fields)) {
379
+ const specials = field?.special ?? [];
380
+ const hasGenerateSpecial = GENERATE_SPECIAL.some((name) => specials.includes(name));
381
+ const nullable = field.nullable || hasGenerateSpecial || field.generated;
382
+ if (!nullable) {
383
+ requiredColumns.push(field);
384
+ }
385
+ }
386
+ if (hasValidationRules === false && hasFieldValidationRules === false && requiredColumns.length === 0) {
387
+ return payloadWithPresets;
388
+ }
389
+ if (requiredColumns.length > 0) {
390
+ permission.validation = hasValidationRules ? { _and: [permission.validation] } : { _and: [] };
391
+ for (const field of requiredColumns) {
392
+ if (action === 'create' && field.defaultValue === null) {
393
+ permission.validation._and.push({
394
+ [field.field]: {
395
+ _submitted: true,
396
+ },
397
+ });
398
+ }
399
+ permission.validation._and.push({
400
+ [field.field]: {
401
+ _nnull: true,
402
+ },
403
+ });
404
+ }
405
+ }
406
+ if (hasFieldValidationRules) {
407
+ if (permission.validation && Object.keys(permission.validation).length > 0) {
408
+ permission.validation = { _and: [permission.validation, ...fieldValidationRules] };
409
+ }
410
+ else {
411
+ permission.validation = { _and: fieldValidationRules };
412
+ }
413
+ }
414
+ const validationErrors = [];
415
+ validationErrors.push(...flatten(validatePayload(permission.validation, payloadWithPresets).map((error) => error.details.map((details) => new FailedValidationError(joiValidationErrorItemToErrorExtensions(details))))));
416
+ if (validationErrors.length > 0)
417
+ throw validationErrors;
418
+ return payloadWithPresets;
419
+ }
420
+ async checkAccess(action, collection, pk) {
421
+ if (this.accountability?.admin === true)
422
+ return;
423
+ const itemsService = new ItemsService(collection, {
424
+ accountability: this.accountability,
425
+ knex: this.knex,
426
+ schema: this.schema,
427
+ });
428
+ const query = {
429
+ fields: ['*'],
430
+ };
431
+ if (Array.isArray(pk)) {
432
+ const result = await itemsService.readMany(pk, { ...query, limit: pk.length }, { permissionsAction: action });
433
+ // for the unexpected case that the result is not an array (for example due to filter hook)
434
+ if (!isArray(result))
435
+ throw new ForbiddenError();
436
+ if (result.length !== pk.length)
437
+ throw new ForbiddenError();
438
+ }
439
+ else if (pk) {
440
+ const result = await itemsService.readOne(pk, query, { permissionsAction: action });
441
+ if (!result)
442
+ throw new ForbiddenError();
443
+ }
444
+ else {
445
+ query.limit = 1;
446
+ const result = await itemsService.readByQuery(query, { permissionsAction: action });
447
+ // for the unexpected case that the result is not an array (for example due to filter hook)
448
+ if (!isArray(result))
449
+ throw new ForbiddenError();
450
+ // for create action, an empty array is expected - for other actions, the first item is expected to be available
451
+ const access = action === 'create' ? result.length === 0 : !!result[0];
452
+ if (!access)
453
+ throw new ForbiddenError();
454
+ }
455
+ }
456
+ }
@@ -9,8 +9,6 @@ import { ALIAS_TYPES } from '../constants.js';
9
9
  import { getHelpers } from '../database/helpers/index.js';
10
10
  import getDatabase, { getSchemaInspector } from '../database/index.js';
11
11
  import emitter from '../emitter.js';
12
- import { fetchAllowedCollections } from '../permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js';
13
- import { validateAccess } from '../permissions/modules/validate-access/validate-access.js';
14
12
  import { getSchema } from '../utils/get-schema.js';
15
13
  import { shouldClearCache } from '../utils/should-clear-cache.js';
16
14
  import { transaction } from '../utils/transaction.js';
@@ -225,13 +223,11 @@ export class CollectionsService {
225
223
  ...meta,
226
224
  [item.collection]: item.group,
227
225
  }), {});
228
- let collectionsYouHavePermissionToRead = await fetchAllowedCollections({
229
- accountability: this.accountability,
230
- action: 'read',
231
- }, {
232
- knex: this.knex,
233
- schema: this.schema,
234
- });
226
+ let collectionsYouHavePermissionToRead = this.accountability
227
+ .permissions.filter((permission) => {
228
+ return permission.action === 'read';
229
+ })
230
+ .map(({ collection }) => collection);
235
231
  for (const collection of collectionsYouHavePermissionToRead) {
236
232
  const group = collectionsGroups[collection];
237
233
  if (group)
@@ -283,15 +279,18 @@ export class CollectionsService {
283
279
  * Read many collections by name
284
280
  */
285
281
  async readMany(collectionKeys) {
286
- if (this.accountability) {
287
- await Promise.all(collectionKeys.map((collection) => validateAccess({
288
- accountability: this.accountability,
289
- action: 'read',
290
- collection,
291
- }, {
292
- schema: this.schema,
293
- knex: this.knex,
294
- })));
282
+ if (this.accountability && this.accountability.admin !== true) {
283
+ const permissions = this.accountability.permissions.filter((permission) => {
284
+ return permission.action === 'read' && collectionKeys.includes(permission.collection);
285
+ });
286
+ if (collectionKeys.length !== permissions.length) {
287
+ const collectionsYouHavePermissionToRead = permissions.map(({ collection }) => collection);
288
+ for (const collectionKey of collectionKeys) {
289
+ if (collectionsYouHavePermissionToRead.includes(collectionKey) === false) {
290
+ throw new ForbiddenError();
291
+ }
292
+ }
293
+ }
295
294
  }
296
295
  const collections = await this.readByQuery();
297
296
  return collections.filter(({ collection }) => collectionKeys.includes(collection));
@@ -16,7 +16,11 @@ export declare class FieldsService {
16
16
  schema: SchemaOverview;
17
17
  cache: Keyv<any> | null;
18
18
  systemCache: Keyv<any>;
19
+ schemaCache: Keyv<any>;
19
20
  constructor(options: AbstractServiceOptions);
21
+ private get hasReadAccess();
22
+ columnInfo(collection?: string): Promise<Column[]>;
23
+ columnInfo(collection: string, field: string): Promise<Column>;
20
24
  readAll(collection?: string): Promise<Field[]>;
21
25
  readOne(collection: string, field: string): Promise<Record<string, any>>;
22
26
  createField(collection: string, field: Partial<Field> & {