@directus/api 21.0.0 → 22.1.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 (299) hide show
  1. package/dist/app.js +4 -4
  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 +2 -4
  6. package/dist/cache.js +3 -0
  7. package/dist/cli/commands/bootstrap/index.js +8 -2
  8. package/dist/cli/commands/init/index.js +9 -10
  9. package/dist/cli/commands/init/questions.d.ts +7 -6
  10. package/dist/cli/commands/init/questions.js +2 -2
  11. package/dist/cli/utils/create-env/index.d.ts +2 -2
  12. package/dist/cli/utils/create-env/index.js +3 -1
  13. package/dist/cli/utils/defaults.d.ts +4 -11
  14. package/dist/cli/utils/defaults.js +7 -1
  15. package/dist/cli/utils/drivers.js +1 -1
  16. package/dist/constants.d.ts +1 -1
  17. package/dist/controllers/access.d.ts +2 -0
  18. package/dist/controllers/access.js +148 -0
  19. package/dist/controllers/auth.js +5 -16
  20. package/dist/controllers/permissions.js +14 -2
  21. package/dist/controllers/policies.d.ts +2 -0
  22. package/dist/controllers/policies.js +169 -0
  23. package/dist/controllers/roles.js +22 -1
  24. package/dist/controllers/tus.js +14 -26
  25. package/dist/controllers/users.js +0 -55
  26. package/dist/database/get-ast-from-query/get-ast-from-query.d.ts +16 -0
  27. package/dist/database/get-ast-from-query/get-ast-from-query.js +82 -0
  28. package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +13 -0
  29. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +69 -0
  30. package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +15 -0
  31. package/dist/database/get-ast-from-query/lib/parse-fields.js +200 -0
  32. package/dist/database/get-ast-from-query/utils/get-deep-query.d.ts +14 -0
  33. package/dist/database/get-ast-from-query/utils/get-deep-query.js +17 -0
  34. package/dist/database/get-ast-from-query/utils/get-related-collection.d.ts +2 -0
  35. package/dist/database/get-ast-from-query/utils/get-related-collection.js +13 -0
  36. package/dist/database/get-ast-from-query/utils/get-relation.d.ts +2 -0
  37. package/dist/database/get-ast-from-query/utils/get-relation.js +7 -0
  38. package/dist/database/helpers/fn/types.d.ts +2 -1
  39. package/dist/database/helpers/fn/types.js +1 -1
  40. package/dist/database/helpers/geometry/dialects/mssql.d.ts +1 -1
  41. package/dist/database/helpers/geometry/dialects/mssql.js +4 -2
  42. package/dist/database/helpers/geometry/dialects/mysql.js +1 -1
  43. package/dist/database/helpers/geometry/dialects/oracle.d.ts +1 -1
  44. package/dist/database/helpers/geometry/dialects/oracle.js +5 -3
  45. package/dist/database/helpers/geometry/types.d.ts +1 -1
  46. package/dist/database/helpers/geometry/types.js +4 -2
  47. package/dist/database/helpers/index.d.ts +3 -3
  48. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +2 -1
  49. package/dist/database/helpers/schema/dialects/cockroachdb.js +4 -0
  50. package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -1
  51. package/dist/database/helpers/schema/dialects/mssql.js +4 -0
  52. package/dist/database/helpers/schema/dialects/oracle.d.ts +2 -1
  53. package/dist/database/helpers/schema/dialects/oracle.js +4 -0
  54. package/dist/database/helpers/schema/dialects/postgres.d.ts +2 -1
  55. package/dist/database/helpers/schema/dialects/postgres.js +4 -0
  56. package/dist/database/helpers/schema/types.d.ts +5 -0
  57. package/dist/database/helpers/schema/types.js +3 -0
  58. package/dist/database/helpers/schema/utils/preprocess-bindings.d.ts +8 -0
  59. package/dist/database/helpers/schema/utils/preprocess-bindings.js +30 -0
  60. package/dist/database/index.js +6 -1
  61. package/dist/database/migrations/20210519A-add-system-fk-triggers.js +3 -2
  62. package/dist/database/migrations/20230721A-require-shares-fields.js +3 -5
  63. package/dist/database/migrations/20240716A-update-files-date-fields.js +3 -7
  64. package/dist/{utils/merge-permissions.d.ts → database/migrations/20240806A-permissions-policies.d.ts} +4 -1
  65. package/dist/database/migrations/20240806A-permissions-policies.js +352 -0
  66. package/dist/database/run-ast/lib/get-db-query.d.ts +4 -0
  67. package/dist/database/run-ast/lib/get-db-query.js +218 -0
  68. package/dist/database/run-ast/lib/parse-current-level.d.ts +7 -0
  69. package/dist/database/run-ast/lib/parse-current-level.js +41 -0
  70. package/dist/database/run-ast/run-ast.d.ts +7 -0
  71. package/dist/database/run-ast/run-ast.js +107 -0
  72. package/dist/database/{run-ast.d.ts → run-ast/types.d.ts} +3 -9
  73. package/dist/database/run-ast/types.js +1 -0
  74. package/dist/database/run-ast/utils/apply-case-when.d.ts +16 -0
  75. package/dist/database/run-ast/utils/apply-case-when.js +27 -0
  76. package/dist/database/run-ast/utils/apply-parent-filters.d.ts +3 -0
  77. package/dist/database/run-ast/utils/apply-parent-filters.js +55 -0
  78. package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +10 -0
  79. package/dist/database/run-ast/utils/get-column-pre-processor.js +57 -0
  80. package/dist/database/run-ast/utils/get-field-alias.d.ts +2 -0
  81. package/dist/database/run-ast/utils/get-field-alias.js +4 -0
  82. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.d.ts +5 -0
  83. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.js +23 -0
  84. package/dist/database/run-ast/utils/merge-with-parent-items.d.ts +3 -0
  85. package/dist/database/run-ast/utils/merge-with-parent-items.js +87 -0
  86. package/dist/database/run-ast/utils/remove-temporary-fields.d.ts +3 -0
  87. package/dist/database/run-ast/utils/remove-temporary-fields.js +73 -0
  88. package/dist/database/run-ast/utils/with-preprocess-bindings.d.ts +2 -0
  89. package/dist/database/run-ast/utils/with-preprocess-bindings.js +14 -0
  90. package/dist/flows.js +3 -4
  91. package/dist/middleware/authenticate.js +2 -7
  92. package/dist/middleware/cache.js +1 -1
  93. package/dist/middleware/respond.js +1 -1
  94. package/dist/permissions/cache.d.ts +2 -0
  95. package/dist/permissions/cache.js +23 -0
  96. package/dist/permissions/lib/fetch-permissions.d.ts +11 -0
  97. package/dist/permissions/lib/fetch-permissions.js +56 -0
  98. package/dist/permissions/lib/fetch-policies.d.ts +14 -0
  99. package/dist/permissions/lib/fetch-policies.js +43 -0
  100. package/dist/permissions/lib/fetch-roles-tree.d.ts +3 -0
  101. package/dist/permissions/lib/fetch-roles-tree.js +28 -0
  102. package/dist/{services/permissions → permissions}/lib/with-app-minimal-permissions.d.ts +1 -1
  103. package/dist/permissions/lib/with-app-minimal-permissions.js +10 -0
  104. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.d.ts +7 -0
  105. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js +56 -0
  106. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.d.ts +3 -0
  107. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js +16 -0
  108. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.d.ts +8 -0
  109. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +24 -0
  110. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.d.ts +9 -0
  111. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +31 -0
  112. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.d.ts +16 -0
  113. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js +27 -0
  114. package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +10 -0
  115. package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +23 -0
  116. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +5 -0
  117. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +7 -0
  118. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +5 -0
  119. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +10 -0
  120. package/dist/permissions/modules/fetch-global-access/types.d.ts +4 -0
  121. package/dist/permissions/modules/fetch-global-access/types.js +1 -0
  122. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +4 -0
  123. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +27 -0
  124. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts +12 -0
  125. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +32 -0
  126. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +4 -0
  127. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js +29 -0
  128. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.d.ts +4 -0
  129. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.js +49 -0
  130. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.d.ts +3 -0
  131. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.js +56 -0
  132. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.d.ts +4 -0
  133. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.js +8 -0
  134. package/dist/permissions/modules/process-ast/lib/inject-cases.d.ts +9 -0
  135. package/dist/permissions/modules/process-ast/lib/inject-cases.js +93 -0
  136. package/dist/permissions/modules/process-ast/process-ast.d.ts +9 -0
  137. package/dist/permissions/modules/process-ast/process-ast.js +39 -0
  138. package/dist/permissions/modules/process-ast/types.d.ts +18 -0
  139. package/dist/permissions/modules/process-ast/types.js +1 -0
  140. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.d.ts +2 -0
  141. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.js +7 -0
  142. package/dist/permissions/modules/process-ast/utils/dedupe-access.d.ts +12 -0
  143. package/dist/permissions/modules/process-ast/utils/dedupe-access.js +30 -0
  144. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.d.ts +15 -0
  145. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +60 -0
  146. package/dist/permissions/modules/process-ast/utils/find-related-collection.d.ts +3 -0
  147. package/dist/permissions/modules/process-ast/utils/find-related-collection.js +9 -0
  148. package/dist/permissions/modules/process-ast/utils/flatten-filter.d.ts +3 -0
  149. package/dist/permissions/modules/process-ast/utils/flatten-filter.js +34 -0
  150. package/dist/permissions/modules/process-ast/utils/format-a2o-key.d.ts +1 -0
  151. package/dist/permissions/modules/process-ast/utils/format-a2o-key.js +3 -0
  152. package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +5 -0
  153. package/dist/permissions/modules/process-ast/utils/get-info-for-path.js +7 -0
  154. package/dist/permissions/modules/process-ast/utils/has-item-permissions.d.ts +2 -0
  155. package/dist/permissions/modules/process-ast/utils/has-item-permissions.js +3 -0
  156. package/dist/permissions/modules/process-ast/utils/stringify-query-path.d.ts +2 -0
  157. package/dist/permissions/modules/process-ast/utils/stringify-query-path.js +3 -0
  158. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.d.ts +3 -0
  159. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.js +16 -0
  160. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.d.ts +2 -0
  161. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.js +12 -0
  162. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.d.ts +2 -0
  163. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.js +28 -0
  164. package/dist/permissions/modules/process-payload/lib/is-field-nullable.d.ts +5 -0
  165. package/dist/permissions/modules/process-payload/lib/is-field-nullable.js +12 -0
  166. package/dist/permissions/modules/process-payload/process-payload.d.ts +13 -0
  167. package/dist/permissions/modules/process-payload/process-payload.js +77 -0
  168. package/dist/permissions/modules/validate-access/lib/validate-collection-access.d.ts +12 -0
  169. package/dist/permissions/modules/validate-access/lib/validate-collection-access.js +11 -0
  170. package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +9 -0
  171. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +33 -0
  172. package/dist/permissions/modules/validate-access/validate-access.d.ts +14 -0
  173. package/dist/permissions/modules/validate-access/validate-access.js +28 -0
  174. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.d.ts +1 -0
  175. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.js +8 -0
  176. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +5 -0
  177. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js +10 -0
  178. package/dist/permissions/types.d.ts +6 -0
  179. package/dist/permissions/types.js +1 -0
  180. package/dist/permissions/utils/create-default-accountability.d.ts +2 -0
  181. package/dist/permissions/utils/create-default-accountability.js +11 -0
  182. package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +8 -0
  183. package/dist/permissions/utils/extract-required-dynamic-variable-context.js +27 -0
  184. package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +9 -0
  185. package/dist/permissions/utils/fetch-dynamic-variable-context.js +43 -0
  186. package/dist/permissions/utils/filter-policies-by-ip.d.ts +2 -0
  187. package/dist/permissions/utils/filter-policies-by-ip.js +15 -0
  188. package/dist/permissions/utils/get-unaliased-field-key.d.ts +5 -0
  189. package/dist/permissions/utils/get-unaliased-field-key.js +17 -0
  190. package/dist/permissions/utils/process-permissions.d.ts +7 -0
  191. package/dist/permissions/utils/process-permissions.js +9 -0
  192. package/dist/permissions/utils/with-cache.d.ts +10 -0
  193. package/dist/permissions/utils/with-cache.js +25 -0
  194. package/dist/server.js +17 -4
  195. package/dist/services/access.d.ts +10 -0
  196. package/dist/services/access.js +43 -0
  197. package/dist/services/activity.js +22 -10
  198. package/dist/services/assets.d.ts +2 -3
  199. package/dist/services/assets.js +10 -5
  200. package/dist/services/authentication.js +18 -18
  201. package/dist/services/collections.js +18 -17
  202. package/dist/services/fields.d.ts +0 -1
  203. package/dist/services/fields.js +54 -25
  204. package/dist/services/files.js +10 -3
  205. package/dist/services/graphql/index.d.ts +3 -3
  206. package/dist/services/graphql/index.js +126 -22
  207. package/dist/services/graphql/subscription.js +2 -4
  208. package/dist/services/import-export.d.ts +3 -1
  209. package/dist/services/import-export.js +67 -9
  210. package/dist/services/index.d.ts +3 -2
  211. package/dist/services/index.js +3 -2
  212. package/dist/services/items.js +115 -44
  213. package/dist/services/meta.js +60 -23
  214. package/dist/services/notifications.js +14 -6
  215. package/dist/services/payload.d.ts +9 -10
  216. package/dist/services/payload.js +18 -3
  217. package/dist/services/{permissions/index.d.ts → permissions.d.ts} +5 -7
  218. package/dist/services/{permissions/index.js → permissions.js} +30 -54
  219. package/dist/services/policies.d.ts +12 -0
  220. package/dist/services/policies.js +87 -0
  221. package/dist/services/relations.d.ts +0 -6
  222. package/dist/services/relations.js +27 -30
  223. package/dist/services/roles.d.ts +4 -12
  224. package/dist/services/roles.js +57 -424
  225. package/dist/services/shares.d.ts +0 -2
  226. package/dist/services/shares.js +12 -8
  227. package/dist/services/specifications.d.ts +2 -2
  228. package/dist/services/specifications.js +39 -27
  229. package/dist/services/users.d.ts +1 -5
  230. package/dist/services/users.js +78 -161
  231. package/dist/services/utils.js +11 -7
  232. package/dist/services/versions.d.ts +0 -2
  233. package/dist/services/versions.js +34 -10
  234. package/dist/telemetry/lib/get-report.js +2 -2
  235. package/dist/telemetry/utils/check-user-limits.d.ts +5 -0
  236. package/dist/telemetry/utils/check-user-limits.js +19 -0
  237. package/dist/types/ast.d.ts +43 -1
  238. package/dist/types/database.d.ts +1 -1
  239. package/dist/types/items.d.ts +11 -0
  240. package/dist/utils/apply-query.d.ts +11 -7
  241. package/dist/utils/apply-query.js +69 -11
  242. package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +19 -0
  243. package/dist/utils/fetch-user-count/fetch-access-lookup.js +23 -0
  244. package/dist/utils/fetch-user-count/fetch-access-roles.d.ts +16 -0
  245. package/dist/utils/fetch-user-count/fetch-access-roles.js +37 -0
  246. package/dist/utils/fetch-user-count/fetch-active-users.d.ts +6 -0
  247. package/dist/utils/fetch-user-count/fetch-active-users.js +3 -0
  248. package/dist/utils/fetch-user-count/fetch-user-count.d.ts +12 -0
  249. package/dist/utils/fetch-user-count/fetch-user-count.js +64 -0
  250. package/dist/utils/fetch-user-count/get-user-count-query.d.ts +20 -0
  251. package/dist/utils/fetch-user-count/get-user-count-query.js +17 -0
  252. package/dist/utils/get-accountability-for-role.js +16 -25
  253. package/dist/utils/get-accountability-for-token.js +17 -16
  254. package/dist/utils/get-address.d.ts +5 -0
  255. package/dist/utils/get-address.js +13 -0
  256. package/dist/utils/get-cache-key.d.ts +1 -1
  257. package/dist/utils/get-cache-key.js +12 -1
  258. package/dist/utils/get-column.d.ts +2 -1
  259. package/dist/utils/get-column.js +1 -0
  260. package/dist/utils/get-service.js +5 -1
  261. package/dist/utils/reduce-schema.d.ts +4 -6
  262. package/dist/utils/reduce-schema.js +16 -32
  263. package/dist/utils/sanitize-schema.d.ts +1 -1
  264. package/dist/utils/transaction.js +28 -11
  265. package/dist/utils/validate-user-count-integrity.d.ts +13 -0
  266. package/dist/utils/validate-user-count-integrity.js +29 -0
  267. package/dist/websocket/authenticate.d.ts +0 -2
  268. package/dist/websocket/authenticate.js +0 -12
  269. package/dist/websocket/controllers/graphql.js +3 -7
  270. package/dist/websocket/controllers/hooks.js +4 -0
  271. package/dist/websocket/controllers/rest.js +2 -5
  272. package/dist/websocket/handlers/subscribe.js +0 -2
  273. package/dist/websocket/utils/items.d.ts +1 -1
  274. package/package.json +31 -30
  275. package/dist/database/run-ast.js +0 -458
  276. package/dist/middleware/check-ip.d.ts +0 -2
  277. package/dist/middleware/check-ip.js +0 -37
  278. package/dist/middleware/get-permissions.d.ts +0 -3
  279. package/dist/middleware/get-permissions.js +0 -10
  280. package/dist/services/authorization.d.ts +0 -17
  281. package/dist/services/authorization.js +0 -456
  282. package/dist/services/permissions/lib/with-app-minimal-permissions.js +0 -13
  283. package/dist/telemetry/utils/check-increased-user-limits.d.ts +0 -7
  284. package/dist/telemetry/utils/check-increased-user-limits.js +0 -25
  285. package/dist/telemetry/utils/get-role-counts-by-roles.d.ts +0 -6
  286. package/dist/telemetry/utils/get-role-counts-by-roles.js +0 -27
  287. package/dist/telemetry/utils/get-role-counts-by-users.d.ts +0 -11
  288. package/dist/telemetry/utils/get-role-counts-by-users.js +0 -34
  289. package/dist/telemetry/utils/get-user-count.d.ts +0 -8
  290. package/dist/telemetry/utils/get-user-count.js +0 -33
  291. package/dist/telemetry/utils/get-user-counts-by-roles.d.ts +0 -7
  292. package/dist/telemetry/utils/get-user-counts-by-roles.js +0 -35
  293. package/dist/utils/get-ast-from-query.d.ts +0 -13
  294. package/dist/utils/get-ast-from-query.js +0 -297
  295. package/dist/utils/get-permissions.d.ts +0 -2
  296. package/dist/utils/get-permissions.js +0 -150
  297. package/dist/utils/merge-permissions-for-share.d.ts +0 -4
  298. package/dist/utils/merge-permissions-for-share.js +0 -109
  299. package/dist/utils/merge-permissions.js +0 -95
@@ -0,0 +1,19 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { LimitExceededError } from '@directus/errors';
3
+ import {} from '../../utils/fetch-user-count/fetch-user-count.js';
4
+ const env = useEnv();
5
+ /**
6
+ * Ensure that user limits are not reached
7
+ */
8
+ export async function checkUserLimits(userCounts) {
9
+ if (userCounts.admin > Number(env['USERS_ADMIN_ACCESS_LIMIT'])) {
10
+ throw new LimitExceededError({ category: 'Active Admin users' });
11
+ }
12
+ // Both app and admin users count against the app access limit
13
+ if (userCounts.app + userCounts.admin > Number(env['USERS_APP_ACCESS_LIMIT'])) {
14
+ throw new LimitExceededError({ category: 'Active App users' });
15
+ }
16
+ if (userCounts.api > Number(env['USERS_API_ACCESS_LIMIT'])) {
17
+ throw new LimitExceededError({ category: 'Active API users' });
18
+ }
19
+ }
@@ -1,4 +1,4 @@
1
- import type { Query, Relation } from '@directus/types';
1
+ import type { Filter, Query, Relation } from '@directus/types';
2
2
  export type M2ONode = {
3
3
  type: 'm2o';
4
4
  name: string;
@@ -8,6 +8,14 @@ export type M2ONode = {
8
8
  relation: Relation;
9
9
  parentKey: string;
10
10
  relatedKey: string;
11
+ /**
12
+ * Which permission cases have to be met on the current item for this field to return a value
13
+ */
14
+ whenCase: number[];
15
+ /**
16
+ * Permissions rules for the item access of the children of this item.
17
+ */
18
+ cases: Filter[];
11
19
  };
12
20
  export type A2MNode = {
13
21
  type: 'a2o';
@@ -24,6 +32,16 @@ export type A2MNode = {
24
32
  fieldKey: string;
25
33
  relation: Relation;
26
34
  parentKey: string;
35
+ /**
36
+ * Which permission cases have to be met on the current item for this field to return a value
37
+ */
38
+ whenCase: number[];
39
+ /**
40
+ * Permissions rules for the item access of the children of this item.
41
+ */
42
+ cases: {
43
+ [collection: string]: Filter[];
44
+ };
27
45
  };
28
46
  export type O2MNode = {
29
47
  type: 'o2m';
@@ -34,12 +52,24 @@ export type O2MNode = {
34
52
  relation: Relation;
35
53
  parentKey: string;
36
54
  relatedKey: string;
55
+ /**
56
+ * Which permission cases have to be met on the current item for this field to return a value
57
+ */
58
+ whenCase: number[];
59
+ /**
60
+ * Permissions rules for the item access of the children of this item.
61
+ */
62
+ cases: Filter[];
37
63
  };
38
64
  export type NestedCollectionNode = M2ONode | O2MNode | A2MNode;
39
65
  export type FieldNode = {
40
66
  type: 'field';
41
67
  name: string;
42
68
  fieldKey: string;
69
+ /**
70
+ * Which permission cases have to be met on the current item for this field to return a value
71
+ */
72
+ whenCase: number[];
43
73
  };
44
74
  export type FunctionFieldNode = {
45
75
  type: 'functionField';
@@ -47,10 +77,22 @@ export type FunctionFieldNode = {
47
77
  fieldKey: string;
48
78
  query: Query;
49
79
  relatedCollection: string;
80
+ /**
81
+ * Which permission cases have to be met on the current item for this field to return a value
82
+ */
83
+ whenCase: number[];
84
+ /**
85
+ * Permissions rules for the item access of the related collection of this item.
86
+ */
87
+ cases: Filter[];
50
88
  };
51
89
  export type AST = {
52
90
  type: 'root';
53
91
  name: string;
54
92
  children: (NestedCollectionNode | FieldNode | FunctionFieldNode)[];
55
93
  query: Query;
94
+ /**
95
+ * Permissions rules for the item access of the children of this item.
96
+ */
97
+ cases: Filter[];
56
98
  };
@@ -1,3 +1,3 @@
1
- export type Driver = 'mysql' | 'pg' | 'cockroachdb' | 'sqlite3' | 'oracledb' | 'mssql';
1
+ export type Driver = 'mysql2' | 'pg' | 'cockroachdb' | 'sqlite3' | 'oracledb' | 'mssql';
2
2
  export declare const DatabaseClients: readonly ["mysql", "postgres", "cockroachdb", "sqlite", "oracle", "mssql", "redshift"];
3
3
  export type DatabaseClient = (typeof DatabaseClients)[number];
@@ -1,6 +1,7 @@
1
1
  import type { DirectusError } from '@directus/errors';
2
2
  import type { EventContext, PrimaryKey } from '@directus/types';
3
3
  import type { MutationTracker } from '../services/items.js';
4
+ import type { UserIntegrityCheckFlag } from '../utils/validate-user-count-integrity.js';
4
5
  export type MutationOptions = {
5
6
  /**
6
7
  * Callback function that's fired whenever a revision is made in the mutation
@@ -33,6 +34,16 @@ export type MutationOptions = {
33
34
  mutationTracker?: MutationTracker | undefined;
34
35
  preMutationError?: DirectusError | undefined;
35
36
  bypassAutoIncrementSequenceReset?: boolean;
37
+ /**
38
+ * Indicate that the top level mutation needs to perform a user integrity check before commiting the transaction
39
+ * This is a combination of flags
40
+ * @see UserIntegrityCheckFlag
41
+ */
42
+ userIntegrityCheckFlags?: UserIntegrityCheckFlag;
43
+ /**
44
+ * Callback function that is called whenever a mutation requires a user integrity check to be made
45
+ */
46
+ onRequireUserIntegrityCheck?: ((flags: UserIntegrityCheckFlag) => void) | undefined;
36
47
  };
37
48
  export type ActionEventParams = {
38
49
  event: string | string[];
@@ -2,14 +2,16 @@ import type { Aggregate, Filter, Query, SchemaOverview } from '@directus/types';
2
2
  import type { Knex } from 'knex';
3
3
  import type { AliasMap } from './get-column-path.js';
4
4
  export declare const generateAlias: (size?: number | undefined) => string;
5
- /**
6
- * Apply the Query to a given Knex query builder instance
7
- */
8
- export default function applyQuery(knex: Knex, collection: string, dbQuery: Knex.QueryBuilder, query: Query, schema: SchemaOverview, options?: {
5
+ type ApplyQueryOptions = {
9
6
  aliasMap?: AliasMap;
10
7
  isInnerQuery?: boolean;
11
8
  hasMultiRelationalSort?: boolean | undefined;
12
- }): {
9
+ groupWhenCases?: number[][] | undefined;
10
+ };
11
+ /**
12
+ * Apply the Query to a given Knex query builder instance
13
+ */
14
+ export default function applyQuery(knex: Knex, collection: string, dbQuery: Knex.QueryBuilder, query: Query, schema: SchemaOverview, cases: Filter[], options?: ApplyQueryOptions): {
13
15
  query: Knex.QueryBuilder<any, any>;
14
16
  hasJoins: boolean;
15
17
  hasMultiRelationalFilter: boolean;
@@ -32,10 +34,12 @@ export declare function applySort(knex: Knex, schema: SchemaOverview, rootQuery:
32
34
  };
33
35
  export declare function applyLimit(knex: Knex, rootQuery: Knex.QueryBuilder, limit: any): void;
34
36
  export declare function applyOffset(knex: Knex, rootQuery: Knex.QueryBuilder, offset: any): void;
35
- export declare function applyFilter(knex: Knex, schema: SchemaOverview, rootQuery: Knex.QueryBuilder, rootFilter: Filter, collection: string, aliasMap: AliasMap): {
37
+ export declare function applyFilter(knex: Knex, schema: SchemaOverview, rootQuery: Knex.QueryBuilder, rootFilter: Filter, collection: string, aliasMap: AliasMap, cases: Filter[]): {
36
38
  query: Knex.QueryBuilder<any, any>;
37
39
  hasJoins: boolean;
38
40
  hasMultiRelationalFilter: boolean;
39
41
  };
40
- export declare function applySearch(knex: Knex, schema: SchemaOverview, dbQuery: Knex.QueryBuilder, searchQuery: string, collection: string): Promise<void>;
42
+ export declare function applySearch(knex: Knex, schema: SchemaOverview, dbQuery: Knex.QueryBuilder, searchQuery: string, collection: string): void;
41
43
  export declare function applyAggregate(schema: SchemaOverview, dbQuery: Knex.QueryBuilder, aggregate: Aggregate, collection: string, hasJoins: boolean): void;
44
+ export declare function joinFilterWithCases(filter: Filter | null | undefined, cases: Filter[]): Filter | null;
45
+ export {};
@@ -4,6 +4,7 @@ import { getFilterOperatorsForType, getFunctionsForType, getOutputTypeForFunctio
4
4
  import { clone, isPlainObject } from 'lodash-es';
5
5
  import { customAlphabet } from 'nanoid/non-secure';
6
6
  import { getHelpers } from '../database/helpers/index.js';
7
+ import { applyCaseWhen } from '../database/run-ast/utils/apply-case-when.js';
7
8
  import { getColumnPath } from './get-column-path.js';
8
9
  import { getColumn } from './get-column.js';
9
10
  import { getRelationInfo } from './get-relation-info.js';
@@ -14,7 +15,7 @@ export const generateAlias = customAlphabet('abcdefghijklmnopqrstuvwxyz', 5);
14
15
  /**
15
16
  * Apply the Query to a given Knex query builder instance
16
17
  */
17
- export default function applyQuery(knex, collection, dbQuery, query, schema, options) {
18
+ export default function applyQuery(knex, collection, dbQuery, query, schema, cases, options) {
18
19
  const aliasMap = options?.aliasMap ?? Object.create(null);
19
20
  let hasJoins = false;
20
21
  let hasMultiRelationalFilter = false;
@@ -34,16 +35,50 @@ export default function applyQuery(knex, collection, dbQuery, query, schema, opt
34
35
  if (query.search) {
35
36
  applySearch(knex, schema, dbQuery, query.search, collection);
36
37
  }
37
- if (query.group) {
38
- dbQuery.groupBy(query.group.map((column) => getColumn(knex, collection, column, false, schema)));
39
- }
40
- if (query.filter) {
41
- const filterResult = applyFilter(knex, schema, dbQuery, query.filter, collection, aliasMap);
38
+ // `cases` are the permissions cases that are required for the current data set. We're
39
+ // dynamically adding those into the filters that the user provided to enforce the permission
40
+ // rules. You should be able to read an item if one or more of the cases matches. The actual case
41
+ // is reused in the column selection case/when to dynamically return or nullify the field values
42
+ // you're actually allowed to read
43
+ const filter = joinFilterWithCases(query.filter, cases);
44
+ if (filter) {
45
+ const filterResult = applyFilter(knex, schema, dbQuery, filter, collection, aliasMap, cases);
42
46
  if (!hasJoins) {
43
47
  hasJoins = filterResult.hasJoins;
44
48
  }
45
49
  hasMultiRelationalFilter = filterResult.hasMultiRelationalFilter;
46
50
  }
51
+ if (query.group) {
52
+ const rawColumns = query.group.map((column) => getColumn(knex, collection, column, false, schema));
53
+ let columns;
54
+ if (options?.groupWhenCases) {
55
+ columns = rawColumns.map((column, index) => applyCaseWhen({
56
+ columnCases: options.groupWhenCases[index].map((caseIndex) => cases[caseIndex]),
57
+ column,
58
+ aliasMap,
59
+ cases,
60
+ table: collection,
61
+ }, {
62
+ knex,
63
+ schema,
64
+ }));
65
+ if (query.sort && query.sort.length === 1 && query.sort[0] === query.group[0]) {
66
+ // Special case, where the sort query is injected by the group by operation
67
+ dbQuery.clear('order');
68
+ let order = 'asc';
69
+ if (query.sort[0].startsWith('-')) {
70
+ order = 'desc';
71
+ }
72
+ // @ts-expect-error (orderBy does not accept Knex.Raw for some reason, even though it is handled correctly)
73
+ // https://github.com/knex/knex/issues/5711
74
+ dbQuery.orderBy([{ column: columns[0], order }]);
75
+ }
76
+ }
77
+ else {
78
+ columns = rawColumns;
79
+ }
80
+ dbQuery.groupBy(columns);
81
+ }
47
82
  if (query.aggregate) {
48
83
  applyAggregate(schema, dbQuery, query.aggregate, collection, hasJoins);
49
84
  }
@@ -226,7 +261,7 @@ export function applyOffset(knex, rootQuery, offset) {
226
261
  getHelpers(knex).schema.applyOffset(rootQuery, offset);
227
262
  }
228
263
  }
229
- export function applyFilter(knex, schema, rootQuery, rootFilter, collection, aliasMap) {
264
+ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, aliasMap, cases) {
230
265
  const helpers = getHelpers(knex);
231
266
  const relations = schema.relations;
232
267
  let hasJoins = false;
@@ -235,12 +270,23 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
235
270
  addWhereClauses(knex, rootQuery, rootFilter, collection);
236
271
  return { query: rootQuery, hasJoins, hasMultiRelationalFilter };
237
272
  function addJoins(dbQuery, filter, collection) {
238
- for (const [key, value] of Object.entries(filter)) {
273
+ // eslint-disable-next-line prefer-const
274
+ for (let [key, value] of Object.entries(filter)) {
239
275
  if (key === '_or' || key === '_and') {
240
276
  // If the _or array contains an empty object (full permissions), we should short-circuit and ignore all other
241
277
  // permission checks, as {} already matches full permissions.
242
278
  if (key === '_or' && value.some((subFilter) => Object.keys(subFilter).length === 0)) {
243
- continue;
279
+ // But only do so, if the value is not equal to `cases` (since then this is not permission related at all)
280
+ // or the length of value is 1, ie. only the empty filter.
281
+ // If the length is more than one it means that some items (and fields) might now be available, so
282
+ // the joins are required for the case/when construction.
283
+ if (value !== cases || value.length === 1) {
284
+ continue;
285
+ }
286
+ else {
287
+ // Otherwise we can at least filter out all empty filters that would not add joins anyway
288
+ value = value.filter((subFilter) => Object.keys(subFilter).length > 0);
289
+ }
244
290
  }
245
291
  value.forEach((subFilter) => {
246
292
  addJoins(dbQuery, subFilter, collection);
@@ -311,7 +357,7 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
311
357
  .select({ [field]: column })
312
358
  .from(collection)
313
359
  .whereNotNull(column);
314
- applyQuery(knex, relation.collection, subQueryKnex, { filter }, schema);
360
+ applyQuery(knex, relation.collection, subQueryKnex, { filter }, schema, cases);
315
361
  };
316
362
  const childKey = Object.keys(value)?.[0];
317
363
  if (childKey === '_none') {
@@ -557,7 +603,7 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
557
603
  }
558
604
  }
559
605
  }
560
- export async function applySearch(knex, schema, dbQuery, searchQuery, collection) {
606
+ export function applySearch(knex, schema, dbQuery, searchQuery, collection) {
561
607
  const { number: numberHelper } = getHelpers(knex);
562
608
  const fields = Object.entries(schema.collections[collection].fields);
563
609
  dbQuery.andWhere(function () {
@@ -633,6 +679,18 @@ export function applyAggregate(schema, dbQuery, aggregate, collection, hasJoins)
633
679
  }
634
680
  }
635
681
  }
682
+ export function joinFilterWithCases(filter, cases) {
683
+ if (cases.length > 0 && !filter) {
684
+ return { _or: cases };
685
+ }
686
+ else if (filter && cases.length === 0) {
687
+ return filter ?? null;
688
+ }
689
+ else if (filter && cases.length > 0) {
690
+ return { _and: [filter, { _or: cases }] };
691
+ }
692
+ return null;
693
+ }
636
694
  function getFilterPath(key, value) {
637
695
  const path = [key];
638
696
  const childKey = Object.keys(value)[0];
@@ -0,0 +1,19 @@
1
+ import type { PrimaryKey } from '@directus/types';
2
+ import type { Knex } from 'knex';
3
+ export interface AccessLookup {
4
+ role: string | null;
5
+ user: string | null;
6
+ app_access: boolean | number;
7
+ admin_access: boolean | number;
8
+ user_status: 'active' | string;
9
+ user_role: string | null;
10
+ }
11
+ export interface FetchAccessLookupOptions {
12
+ excludeAccessRows?: PrimaryKey[];
13
+ excludePolicies?: PrimaryKey[];
14
+ excludeUsers?: PrimaryKey[];
15
+ excludeRoles?: PrimaryKey[];
16
+ adminOnly?: boolean;
17
+ knex: Knex;
18
+ }
19
+ export declare function fetchAccessLookup(options: FetchAccessLookupOptions): Promise<AccessLookup[]>;
@@ -0,0 +1,23 @@
1
+ export async function fetchAccessLookup(options) {
2
+ let query = options.knex
3
+ .select('directus_access.role', 'directus_access.user', 'directus_policies.app_access', 'directus_policies.admin_access', 'directus_users.status as user_status', 'directus_users.role as user_role')
4
+ .from('directus_access')
5
+ .leftJoin('directus_policies', 'directus_access.policy', 'directus_policies.id')
6
+ .leftJoin('directus_users', 'directus_access.user', 'directus_users.id');
7
+ if (options.excludeAccessRows && options.excludeAccessRows.length > 0) {
8
+ query = query.whereNotIn('directus_access.id', options.excludeAccessRows);
9
+ }
10
+ if (options.excludePolicies && options.excludePolicies.length > 0) {
11
+ query = query.whereNotIn('directus_access.policy', options.excludePolicies);
12
+ }
13
+ if (options.excludeUsers && options.excludeUsers.length > 0) {
14
+ query = query.where((q) => q.whereNotIn('directus_access.user', options.excludeUsers).orWhereNull('directus_access.user'));
15
+ }
16
+ if (options.excludeRoles && options.excludeRoles.length > 0) {
17
+ query = query.where((q) => q.whereNotIn('directus_access.role', options.excludeRoles).orWhereNull('directus_access.role'));
18
+ }
19
+ if (options.adminOnly) {
20
+ query = query.where('directus_policies.admin_access', 1);
21
+ }
22
+ return query;
23
+ }
@@ -0,0 +1,16 @@
1
+ import type { PrimaryKey } from '@directus/types';
2
+ import type { Knex } from 'knex';
3
+ export interface FetchAccessRolesOptions {
4
+ adminRoles: Set<string>;
5
+ appRoles: Set<string>;
6
+ excludeRoles?: PrimaryKey[];
7
+ }
8
+ /**
9
+ * Return a set of roles that allow app or admin access, if itself or any of its parents do
10
+ */
11
+ export declare function fetchAccessRoles(options: FetchAccessRolesOptions, context: {
12
+ knex: Knex;
13
+ }): Promise<{
14
+ adminRoles: Set<string>;
15
+ appRoles: Set<string>;
16
+ }>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Return a set of roles that allow app or admin access, if itself or any of its parents do
3
+ */
4
+ export async function fetchAccessRoles(options, context) {
5
+ // Only fetch the roles that have a parent, as otherwise those roles should already be included in at least one of the input set
6
+ const allChildRoles = await context.knex
7
+ .select('id', 'parent')
8
+ .from('directus_roles')
9
+ .whereNotNull('parent')
10
+ .whereNotIn('id', options.excludeRoles ?? []);
11
+ const adminRoles = new Set(options.adminRoles);
12
+ const appRoles = new Set(options.appRoles);
13
+ const remainingRoles = new Set(allChildRoles);
14
+ let hasChanged = remainingRoles.size > 0;
15
+ // This loop accounts for the undefined order in which the roles are returned, as there is the possibility
16
+ // of a role parent not being in the set of roles yet, so we need to iterate over the roles multiple times
17
+ // until no further roles are added to the sets
18
+ while (hasChanged) {
19
+ hasChanged = false;
20
+ for (const role of remainingRoles) {
21
+ if (adminRoles.has(role.parent)) {
22
+ adminRoles.add(role.id);
23
+ remainingRoles.delete(role);
24
+ hasChanged = true;
25
+ }
26
+ if (appRoles.has(role.parent)) {
27
+ appRoles.add(role.id);
28
+ remainingRoles.delete(role);
29
+ hasChanged = true;
30
+ }
31
+ }
32
+ }
33
+ return {
34
+ adminRoles,
35
+ appRoles,
36
+ };
37
+ }
@@ -0,0 +1,6 @@
1
+ import type { Knex } from 'knex';
2
+ export interface ActiveUser {
3
+ id: string;
4
+ role: string | null;
5
+ }
6
+ export declare function fetchActiveUsers(knex: Knex): Promise<ActiveUser[]>;
@@ -0,0 +1,3 @@
1
+ export async function fetchActiveUsers(knex) {
2
+ return await knex.select('id', 'role').from('directus_users').where('status', 'active');
3
+ }
@@ -0,0 +1,12 @@
1
+ import { type FetchAccessLookupOptions } from './fetch-access-lookup.js';
2
+ export interface FetchUserCountOptions extends FetchAccessLookupOptions {
3
+ }
4
+ export interface UserCount {
5
+ admin: number;
6
+ app: number;
7
+ api: number;
8
+ }
9
+ /**
10
+ * Returns counts of all active users in the system grouped by admin, app, and api access
11
+ */
12
+ export declare function fetchUserCount(options: FetchUserCountOptions): Promise<UserCount>;
@@ -0,0 +1,64 @@
1
+ import { toBoolean } from '@directus/utils';
2
+ import { fetchAccessLookup } from './fetch-access-lookup.js';
3
+ import { fetchAccessRoles } from './fetch-access-roles.js';
4
+ import { getUserCountQuery } from './get-user-count-query.js';
5
+ /**
6
+ * Returns counts of all active users in the system grouped by admin, app, and api access
7
+ */
8
+ export async function fetchUserCount(options) {
9
+ const accessRows = await fetchAccessLookup(options);
10
+ const adminRoles = new Set(accessRows.filter((row) => toBoolean(row.admin_access) && row.role !== null).map((row) => row.role));
11
+ const appRoles = new Set(accessRows
12
+ .filter((row) => !toBoolean(row.admin_access) && toBoolean(row.app_access) && row.role !== null)
13
+ .map((row) => row.role));
14
+ // All users that are directly granted rights through a connected policy
15
+ const adminUsers = new Set(accessRows
16
+ .filter((row) => toBoolean(row.admin_access) && row.user !== null && row.user_status === 'active')
17
+ .map((row) => row.user));
18
+ // Some roles might be granted access rights through nesting, so determine all roles that grant admin or app access,
19
+ // including nested roles
20
+ const { adminRoles: allAdminRoles, appRoles: allAppRoles } = await fetchAccessRoles({
21
+ adminRoles,
22
+ appRoles,
23
+ ...options,
24
+ }, { knex: options.knex });
25
+ // All users that are granted admin rights through a role, but not directly
26
+ const adminCountQuery = getUserCountQuery(options.knex, {
27
+ includeRoles: Array.from(allAdminRoles),
28
+ excludeIds: [...adminUsers, ...(options.excludeUsers ?? [])],
29
+ });
30
+ if (options.adminOnly) {
31
+ // Shortcut for only counting admin users
32
+ const adminResult = await adminCountQuery;
33
+ return {
34
+ admin: Number(adminResult?.['count'] ?? 0) + adminUsers.size,
35
+ app: 0,
36
+ api: 0,
37
+ };
38
+ }
39
+ const appUsers = new Set(accessRows
40
+ .filter((row) => !toBoolean(row.admin_access) &&
41
+ toBoolean(row.app_access) &&
42
+ row.user !== null &&
43
+ row.user_status === 'active' &&
44
+ adminUsers.has(row.user) === false &&
45
+ adminRoles.has(row.user_role) === false)
46
+ .map((row) => row.user));
47
+ // All users that are granted app rights through a role, but not directly, and that aren't admin users
48
+ const appCountQuery = getUserCountQuery(options.knex, {
49
+ includeRoles: Array.from(allAppRoles),
50
+ excludeRoles: Array.from(allAdminRoles),
51
+ excludeIds: [...appUsers, ...adminUsers, ...(options.excludeUsers ?? [])],
52
+ });
53
+ const allCountQuery = getUserCountQuery(options.knex, {
54
+ excludeIds: options.excludeUsers ?? [],
55
+ });
56
+ const [adminResult, appResult, allResult] = await Promise.all([adminCountQuery, appCountQuery, allCountQuery]);
57
+ const adminCount = Number(adminResult?.['count'] ?? 0) + adminUsers.size;
58
+ const appCount = Number(appResult?.['count'] ?? 0) + appUsers.size;
59
+ return {
60
+ admin: adminCount,
61
+ app: appCount,
62
+ api: Math.max(0, Number(allResult?.['count'] ?? 0) - adminCount - appCount),
63
+ };
64
+ }
@@ -0,0 +1,20 @@
1
+ import type { PrimaryKey } from '@directus/types';
2
+ import type { Knex } from 'knex';
3
+ export interface GetUserCountOptions {
4
+ excludeIds?: PrimaryKey[];
5
+ excludeRoles?: PrimaryKey[];
6
+ includeRoles?: PrimaryKey[];
7
+ }
8
+ export declare function getUserCountQuery(knex: Knex, options: GetUserCountOptions): Promise<{
9
+ count: number;
10
+ }> | Knex.QueryBuilder<any, {
11
+ _base: {};
12
+ _hasSelection: true;
13
+ _keys: never;
14
+ _aliases: {};
15
+ _single: false;
16
+ _intersectProps: {
17
+ count?: string | number;
18
+ };
19
+ _unionProps: undefined;
20
+ }>;
@@ -0,0 +1,17 @@
1
+ export function getUserCountQuery(knex, options) {
2
+ // Safety check for an empty list of includeRoles, which would otherwise return all users
3
+ if (options.includeRoles && options.includeRoles.length === 0) {
4
+ return Promise.resolve({ count: 0 });
5
+ }
6
+ let query = knex('directus_users').count({ count: '*' }).as('count').where('status', '=', 'active');
7
+ if (options.excludeIds && options.excludeIds.length > 0) {
8
+ query = query.whereNotIn('id', options.excludeIds);
9
+ }
10
+ if (options.excludeRoles && options.excludeRoles.length > 0) {
11
+ query = query.whereNotIn('role', options.excludeRoles);
12
+ }
13
+ if (options.includeRoles && options.includeRoles.length > 0) {
14
+ query = query.whereIn('role', options.includeRoles);
15
+ }
16
+ return query.first();
17
+ }
@@ -1,40 +1,31 @@
1
- import { getPermissions } from './get-permissions.js';
1
+ import { fetchRolesTree } from '../permissions/lib/fetch-roles-tree.js';
2
+ import { fetchGlobalAccess } from '../permissions/modules/fetch-global-access/fetch-global-access.js';
3
+ import { createDefaultAccountability } from '../permissions/utils/create-default-accountability.js';
2
4
  export async function getAccountabilityForRole(role, context) {
3
- let generatedAccountability = context.accountability;
5
+ let generatedAccountability;
4
6
  if (role === null) {
5
- generatedAccountability = {
6
- role: null,
7
- user: null,
8
- admin: false,
9
- app: false,
10
- };
11
- generatedAccountability.permissions = await getPermissions(generatedAccountability, context.schema);
7
+ generatedAccountability = createDefaultAccountability();
12
8
  }
13
9
  else if (role === 'system') {
14
- generatedAccountability = {
15
- user: null,
16
- role: null,
10
+ generatedAccountability = createDefaultAccountability({
17
11
  admin: true,
18
12
  app: true,
19
- permissions: [],
20
- };
13
+ });
21
14
  }
22
15
  else {
23
- const roleInfo = await context.database
24
- .select(['app_access', 'admin_access'])
25
- .from('directus_roles')
26
- .where({ id: role })
27
- .first();
28
- if (!roleInfo) {
16
+ const roles = await fetchRolesTree(role, context.database);
17
+ // The roles tree should always include the passed role. If it doesn't, it's because it
18
+ // couldn't be read from the database and therefore doesn't exist
19
+ if (roles.length === 0) {
29
20
  throw new Error(`Configured role "${role}" isn't a valid role ID or doesn't exist.`);
30
21
  }
31
- generatedAccountability = {
22
+ const globalAccess = await fetchGlobalAccess({ user: null, roles, ip: context.accountability?.ip ?? null }, context.database);
23
+ generatedAccountability = createDefaultAccountability({
32
24
  role,
25
+ roles,
33
26
  user: null,
34
- admin: roleInfo.admin_access === 1 || roleInfo.admin_access === '1' || roleInfo.admin_access === true,
35
- app: roleInfo.app_access === 1 || roleInfo.app_access === '1' || roleInfo.app_access === true,
36
- };
37
- generatedAccountability.permissions = await getPermissions(generatedAccountability, context.schema);
27
+ ...globalAccess,
28
+ });
38
29
  }
39
30
  return generatedAccountability;
40
31
  }