@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.
- package/dist/app.js +4 -4
- package/dist/auth/drivers/ldap.js +4 -4
- package/dist/auth/drivers/local.js +4 -4
- package/dist/auth/drivers/oauth2.js +4 -4
- package/dist/auth/drivers/openid.js +2 -4
- package/dist/cache.js +3 -0
- package/dist/cli/commands/bootstrap/index.js +8 -2
- package/dist/cli/commands/init/index.js +9 -10
- package/dist/cli/commands/init/questions.d.ts +7 -6
- package/dist/cli/commands/init/questions.js +2 -2
- package/dist/cli/utils/create-env/index.d.ts +2 -2
- package/dist/cli/utils/create-env/index.js +3 -1
- package/dist/cli/utils/defaults.d.ts +4 -11
- package/dist/cli/utils/defaults.js +7 -1
- package/dist/cli/utils/drivers.js +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/controllers/access.d.ts +2 -0
- package/dist/controllers/access.js +148 -0
- package/dist/controllers/auth.js +5 -16
- package/dist/controllers/permissions.js +14 -2
- package/dist/controllers/policies.d.ts +2 -0
- package/dist/controllers/policies.js +169 -0
- package/dist/controllers/roles.js +22 -1
- package/dist/controllers/tus.js +14 -26
- package/dist/controllers/users.js +0 -55
- package/dist/database/get-ast-from-query/get-ast-from-query.d.ts +16 -0
- package/dist/database/get-ast-from-query/get-ast-from-query.js +82 -0
- package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +13 -0
- package/dist/database/get-ast-from-query/lib/convert-wildcards.js +69 -0
- package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +15 -0
- package/dist/database/get-ast-from-query/lib/parse-fields.js +200 -0
- package/dist/database/get-ast-from-query/utils/get-deep-query.d.ts +14 -0
- package/dist/database/get-ast-from-query/utils/get-deep-query.js +17 -0
- package/dist/database/get-ast-from-query/utils/get-related-collection.d.ts +2 -0
- package/dist/database/get-ast-from-query/utils/get-related-collection.js +13 -0
- package/dist/database/get-ast-from-query/utils/get-relation.d.ts +2 -0
- package/dist/database/get-ast-from-query/utils/get-relation.js +7 -0
- package/dist/database/helpers/fn/types.d.ts +2 -1
- package/dist/database/helpers/fn/types.js +1 -1
- package/dist/database/helpers/geometry/dialects/mssql.d.ts +1 -1
- package/dist/database/helpers/geometry/dialects/mssql.js +4 -2
- package/dist/database/helpers/geometry/dialects/mysql.js +1 -1
- package/dist/database/helpers/geometry/dialects/oracle.d.ts +1 -1
- package/dist/database/helpers/geometry/dialects/oracle.js +5 -3
- package/dist/database/helpers/geometry/types.d.ts +1 -1
- package/dist/database/helpers/geometry/types.js +4 -2
- package/dist/database/helpers/index.d.ts +3 -3
- package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/cockroachdb.js +4 -0
- package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/mssql.js +4 -0
- package/dist/database/helpers/schema/dialects/oracle.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/oracle.js +4 -0
- package/dist/database/helpers/schema/dialects/postgres.d.ts +2 -1
- package/dist/database/helpers/schema/dialects/postgres.js +4 -0
- package/dist/database/helpers/schema/types.d.ts +5 -0
- package/dist/database/helpers/schema/types.js +3 -0
- package/dist/database/helpers/schema/utils/preprocess-bindings.d.ts +8 -0
- package/dist/database/helpers/schema/utils/preprocess-bindings.js +30 -0
- package/dist/database/index.js +6 -1
- package/dist/database/migrations/20210519A-add-system-fk-triggers.js +3 -2
- package/dist/database/migrations/20230721A-require-shares-fields.js +3 -5
- package/dist/database/migrations/20240716A-update-files-date-fields.js +3 -7
- package/dist/{utils/merge-permissions.d.ts → database/migrations/20240806A-permissions-policies.d.ts} +4 -1
- package/dist/database/migrations/20240806A-permissions-policies.js +352 -0
- package/dist/database/run-ast/lib/get-db-query.d.ts +4 -0
- package/dist/database/run-ast/lib/get-db-query.js +218 -0
- package/dist/database/run-ast/lib/parse-current-level.d.ts +7 -0
- package/dist/database/run-ast/lib/parse-current-level.js +41 -0
- package/dist/database/run-ast/run-ast.d.ts +7 -0
- package/dist/database/run-ast/run-ast.js +107 -0
- package/dist/database/{run-ast.d.ts → run-ast/types.d.ts} +3 -9
- package/dist/database/run-ast/types.js +1 -0
- package/dist/database/run-ast/utils/apply-case-when.d.ts +16 -0
- package/dist/database/run-ast/utils/apply-case-when.js +27 -0
- package/dist/database/run-ast/utils/apply-parent-filters.d.ts +3 -0
- package/dist/database/run-ast/utils/apply-parent-filters.js +55 -0
- package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +10 -0
- package/dist/database/run-ast/utils/get-column-pre-processor.js +57 -0
- package/dist/database/run-ast/utils/get-field-alias.d.ts +2 -0
- package/dist/database/run-ast/utils/get-field-alias.js +4 -0
- package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.d.ts +5 -0
- package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.js +23 -0
- package/dist/database/run-ast/utils/merge-with-parent-items.d.ts +3 -0
- package/dist/database/run-ast/utils/merge-with-parent-items.js +87 -0
- package/dist/database/run-ast/utils/remove-temporary-fields.d.ts +3 -0
- package/dist/database/run-ast/utils/remove-temporary-fields.js +73 -0
- package/dist/database/run-ast/utils/with-preprocess-bindings.d.ts +2 -0
- package/dist/database/run-ast/utils/with-preprocess-bindings.js +14 -0
- package/dist/flows.js +3 -4
- package/dist/middleware/authenticate.js +2 -7
- package/dist/middleware/cache.js +1 -1
- package/dist/middleware/respond.js +1 -1
- package/dist/permissions/cache.d.ts +2 -0
- package/dist/permissions/cache.js +23 -0
- package/dist/permissions/lib/fetch-permissions.d.ts +11 -0
- package/dist/permissions/lib/fetch-permissions.js +56 -0
- package/dist/permissions/lib/fetch-policies.d.ts +14 -0
- package/dist/permissions/lib/fetch-policies.js +43 -0
- package/dist/permissions/lib/fetch-roles-tree.d.ts +3 -0
- package/dist/permissions/lib/fetch-roles-tree.js +28 -0
- package/dist/{services/permissions → permissions}/lib/with-app-minimal-permissions.d.ts +1 -1
- package/dist/permissions/lib/with-app-minimal-permissions.js +10 -0
- package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.d.ts +7 -0
- package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js +56 -0
- package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.d.ts +3 -0
- package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js +16 -0
- package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.d.ts +8 -0
- package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +24 -0
- package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.d.ts +9 -0
- package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +31 -0
- package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.d.ts +16 -0
- package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js +27 -0
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +10 -0
- package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +23 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +5 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +7 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +5 -0
- package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +10 -0
- package/dist/permissions/modules/fetch-global-access/types.d.ts +4 -0
- package/dist/permissions/modules/fetch-global-access/types.js +1 -0
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +4 -0
- package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +27 -0
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts +12 -0
- package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +32 -0
- package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +4 -0
- package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js +29 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.d.ts +4 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.js +49 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.d.ts +3 -0
- package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.js +56 -0
- package/dist/permissions/modules/process-ast/lib/field-map-from-ast.d.ts +4 -0
- package/dist/permissions/modules/process-ast/lib/field-map-from-ast.js +8 -0
- package/dist/permissions/modules/process-ast/lib/inject-cases.d.ts +9 -0
- package/dist/permissions/modules/process-ast/lib/inject-cases.js +93 -0
- package/dist/permissions/modules/process-ast/process-ast.d.ts +9 -0
- package/dist/permissions/modules/process-ast/process-ast.js +39 -0
- package/dist/permissions/modules/process-ast/types.d.ts +18 -0
- package/dist/permissions/modules/process-ast/types.js +1 -0
- package/dist/permissions/modules/process-ast/utils/collections-in-field-map.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/collections-in-field-map.js +7 -0
- package/dist/permissions/modules/process-ast/utils/dedupe-access.d.ts +12 -0
- package/dist/permissions/modules/process-ast/utils/dedupe-access.js +30 -0
- package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.d.ts +15 -0
- package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +60 -0
- package/dist/permissions/modules/process-ast/utils/find-related-collection.d.ts +3 -0
- package/dist/permissions/modules/process-ast/utils/find-related-collection.js +9 -0
- package/dist/permissions/modules/process-ast/utils/flatten-filter.d.ts +3 -0
- package/dist/permissions/modules/process-ast/utils/flatten-filter.js +34 -0
- package/dist/permissions/modules/process-ast/utils/format-a2o-key.d.ts +1 -0
- package/dist/permissions/modules/process-ast/utils/format-a2o-key.js +3 -0
- package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +5 -0
- package/dist/permissions/modules/process-ast/utils/get-info-for-path.js +7 -0
- package/dist/permissions/modules/process-ast/utils/has-item-permissions.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/has-item-permissions.js +3 -0
- package/dist/permissions/modules/process-ast/utils/stringify-query-path.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/stringify-query-path.js +3 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/create-error.d.ts +3 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/create-error.js +16 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.js +12 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.d.ts +2 -0
- package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.js +28 -0
- package/dist/permissions/modules/process-payload/lib/is-field-nullable.d.ts +5 -0
- package/dist/permissions/modules/process-payload/lib/is-field-nullable.js +12 -0
- package/dist/permissions/modules/process-payload/process-payload.d.ts +13 -0
- package/dist/permissions/modules/process-payload/process-payload.js +77 -0
- package/dist/permissions/modules/validate-access/lib/validate-collection-access.d.ts +12 -0
- package/dist/permissions/modules/validate-access/lib/validate-collection-access.js +11 -0
- package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +9 -0
- package/dist/permissions/modules/validate-access/lib/validate-item-access.js +33 -0
- package/dist/permissions/modules/validate-access/validate-access.d.ts +14 -0
- package/dist/permissions/modules/validate-access/validate-access.js +28 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.d.ts +1 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.js +8 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +5 -0
- package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js +10 -0
- package/dist/permissions/types.d.ts +6 -0
- package/dist/permissions/types.js +1 -0
- package/dist/permissions/utils/create-default-accountability.d.ts +2 -0
- package/dist/permissions/utils/create-default-accountability.js +11 -0
- package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +8 -0
- package/dist/permissions/utils/extract-required-dynamic-variable-context.js +27 -0
- package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +9 -0
- package/dist/permissions/utils/fetch-dynamic-variable-context.js +43 -0
- package/dist/permissions/utils/filter-policies-by-ip.d.ts +2 -0
- package/dist/permissions/utils/filter-policies-by-ip.js +15 -0
- package/dist/permissions/utils/get-unaliased-field-key.d.ts +5 -0
- package/dist/permissions/utils/get-unaliased-field-key.js +17 -0
- package/dist/permissions/utils/process-permissions.d.ts +7 -0
- package/dist/permissions/utils/process-permissions.js +9 -0
- package/dist/permissions/utils/with-cache.d.ts +10 -0
- package/dist/permissions/utils/with-cache.js +25 -0
- package/dist/server.js +17 -4
- package/dist/services/access.d.ts +10 -0
- package/dist/services/access.js +43 -0
- package/dist/services/activity.js +22 -10
- package/dist/services/assets.d.ts +2 -3
- package/dist/services/assets.js +10 -5
- package/dist/services/authentication.js +18 -18
- package/dist/services/collections.js +18 -17
- package/dist/services/fields.d.ts +0 -1
- package/dist/services/fields.js +54 -25
- package/dist/services/files.js +10 -3
- package/dist/services/graphql/index.d.ts +3 -3
- package/dist/services/graphql/index.js +126 -22
- package/dist/services/graphql/subscription.js +2 -4
- package/dist/services/import-export.d.ts +3 -1
- package/dist/services/import-export.js +67 -9
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.js +3 -2
- package/dist/services/items.js +115 -44
- package/dist/services/meta.js +60 -23
- package/dist/services/notifications.js +14 -6
- package/dist/services/payload.d.ts +9 -10
- package/dist/services/payload.js +18 -3
- package/dist/services/{permissions/index.d.ts → permissions.d.ts} +5 -7
- package/dist/services/{permissions/index.js → permissions.js} +30 -54
- package/dist/services/policies.d.ts +12 -0
- package/dist/services/policies.js +87 -0
- package/dist/services/relations.d.ts +0 -6
- package/dist/services/relations.js +27 -30
- package/dist/services/roles.d.ts +4 -12
- package/dist/services/roles.js +57 -424
- package/dist/services/shares.d.ts +0 -2
- package/dist/services/shares.js +12 -8
- package/dist/services/specifications.d.ts +2 -2
- package/dist/services/specifications.js +39 -27
- package/dist/services/users.d.ts +1 -5
- package/dist/services/users.js +78 -161
- package/dist/services/utils.js +11 -7
- package/dist/services/versions.d.ts +0 -2
- package/dist/services/versions.js +34 -10
- package/dist/telemetry/lib/get-report.js +2 -2
- package/dist/telemetry/utils/check-user-limits.d.ts +5 -0
- package/dist/telemetry/utils/check-user-limits.js +19 -0
- package/dist/types/ast.d.ts +43 -1
- package/dist/types/database.d.ts +1 -1
- package/dist/types/items.d.ts +11 -0
- package/dist/utils/apply-query.d.ts +11 -7
- package/dist/utils/apply-query.js +69 -11
- package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +19 -0
- package/dist/utils/fetch-user-count/fetch-access-lookup.js +23 -0
- package/dist/utils/fetch-user-count/fetch-access-roles.d.ts +16 -0
- package/dist/utils/fetch-user-count/fetch-access-roles.js +37 -0
- package/dist/utils/fetch-user-count/fetch-active-users.d.ts +6 -0
- package/dist/utils/fetch-user-count/fetch-active-users.js +3 -0
- package/dist/utils/fetch-user-count/fetch-user-count.d.ts +12 -0
- package/dist/utils/fetch-user-count/fetch-user-count.js +64 -0
- package/dist/utils/fetch-user-count/get-user-count-query.d.ts +20 -0
- package/dist/utils/fetch-user-count/get-user-count-query.js +17 -0
- package/dist/utils/get-accountability-for-role.js +16 -25
- package/dist/utils/get-accountability-for-token.js +17 -16
- package/dist/utils/get-address.d.ts +5 -0
- package/dist/utils/get-address.js +13 -0
- package/dist/utils/get-cache-key.d.ts +1 -1
- package/dist/utils/get-cache-key.js +12 -1
- package/dist/utils/get-column.d.ts +2 -1
- package/dist/utils/get-column.js +1 -0
- package/dist/utils/get-service.js +5 -1
- package/dist/utils/reduce-schema.d.ts +4 -6
- package/dist/utils/reduce-schema.js +16 -32
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/utils/transaction.js +28 -11
- package/dist/utils/validate-user-count-integrity.d.ts +13 -0
- package/dist/utils/validate-user-count-integrity.js +29 -0
- package/dist/websocket/authenticate.d.ts +0 -2
- package/dist/websocket/authenticate.js +0 -12
- package/dist/websocket/controllers/graphql.js +3 -7
- package/dist/websocket/controllers/hooks.js +4 -0
- package/dist/websocket/controllers/rest.js +2 -5
- package/dist/websocket/handlers/subscribe.js +0 -2
- package/dist/websocket/utils/items.d.ts +1 -1
- package/package.json +31 -30
- package/dist/database/run-ast.js +0 -458
- package/dist/middleware/check-ip.d.ts +0 -2
- package/dist/middleware/check-ip.js +0 -37
- package/dist/middleware/get-permissions.d.ts +0 -3
- package/dist/middleware/get-permissions.js +0 -10
- package/dist/services/authorization.d.ts +0 -17
- package/dist/services/authorization.js +0 -456
- package/dist/services/permissions/lib/with-app-minimal-permissions.js +0 -13
- package/dist/telemetry/utils/check-increased-user-limits.d.ts +0 -7
- package/dist/telemetry/utils/check-increased-user-limits.js +0 -25
- package/dist/telemetry/utils/get-role-counts-by-roles.d.ts +0 -6
- package/dist/telemetry/utils/get-role-counts-by-roles.js +0 -27
- package/dist/telemetry/utils/get-role-counts-by-users.d.ts +0 -11
- package/dist/telemetry/utils/get-role-counts-by-users.js +0 -34
- package/dist/telemetry/utils/get-user-count.d.ts +0 -8
- package/dist/telemetry/utils/get-user-count.js +0 -33
- package/dist/telemetry/utils/get-user-counts-by-roles.d.ts +0 -7
- package/dist/telemetry/utils/get-user-counts-by-roles.js +0 -35
- package/dist/utils/get-ast-from-query.d.ts +0 -13
- package/dist/utils/get-ast-from-query.js +0 -297
- package/dist/utils/get-permissions.d.ts +0 -2
- package/dist/utils/get-permissions.js +0 -150
- package/dist/utils/merge-permissions-for-share.d.ts +0 -4
- package/dist/utils/merge-permissions-for-share.js +0 -109
- 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
|
+
}
|
package/dist/types/ast.d.ts
CHANGED
|
@@ -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
|
};
|
package/dist/types/database.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type Driver = '
|
|
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];
|
package/dist/types/items.d.ts
CHANGED
|
@@ -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):
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,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 {
|
|
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
|
|
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
|
-
|
|
20
|
-
};
|
|
13
|
+
});
|
|
21
14
|
}
|
|
22
15
|
else {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
};
|
|
37
|
-
generatedAccountability.permissions = await getPermissions(generatedAccountability, context.schema);
|
|
27
|
+
...globalAccess,
|
|
28
|
+
});
|
|
38
29
|
}
|
|
39
30
|
return generatedAccountability;
|
|
40
31
|
}
|