@directus/api 20.0.0 → 21.0.0-rc.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 +5 -5
- package/dist/auth/drivers/ldap.js +5 -5
- package/dist/auth/drivers/local.js +4 -4
- package/dist/auth/drivers/oauth2.js +5 -5
- package/dist/auth/drivers/openid.js +3 -5
- package/dist/auth/drivers/saml.js +1 -1
- package/dist/auth.js +1 -1
- package/dist/cache.js +4 -1
- package/dist/cli/commands/bootstrap/index.js +10 -3
- package/dist/cli/commands/count/index.js +1 -1
- package/dist/cli/commands/database/install.js +1 -1
- package/dist/cli/commands/database/migrate.js +1 -1
- package/dist/cli/commands/init/index.js +9 -10
- package/dist/cli/commands/roles/create.js +1 -1
- package/dist/cli/commands/schema/apply.js +1 -1
- package/dist/cli/commands/schema/snapshot.js +1 -1
- package/dist/cli/commands/users/create.js +1 -1
- package/dist/cli/commands/users/passwd.js +1 -1
- package/dist/cli/load-extensions.js +1 -1
- package/dist/cli/utils/defaults.d.ts +4 -11
- package/dist/cli/utils/defaults.js +7 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +2 -2
- package/dist/controllers/access.d.ts +2 -0
- package/dist/controllers/access.js +148 -0
- package/dist/controllers/assets.js +1 -1
- package/dist/controllers/auth.js +6 -17
- package/dist/controllers/files.js +1 -1
- 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/schema.js +1 -1
- package/dist/controllers/tus.js +11 -23
- 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 +190 -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/index.js +3 -2
- package/dist/database/migrations/20210518A-add-foreign-key-constraints.js +1 -1
- package/dist/database/migrations/20210519A-add-system-fk-triggers.js +1 -1
- package/dist/database/migrations/20210802A-replace-groups.js +1 -1
- package/dist/database/migrations/20230721A-require-shares-fields.js +1 -1
- package/dist/database/migrations/20240710A-permissions-policies.d.ts +3 -0
- package/dist/database/migrations/20240710A-permissions-policies.js +169 -0
- package/dist/database/migrations/run.js +1 -1
- package/dist/database/run-ast/lib/get-db-query.d.ts +4 -0
- package/dist/database/run-ast/lib/get-db-query.js +208 -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 +26 -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/emitter.js +1 -1
- package/dist/extensions/lib/get-shared-deps-mapping.js +1 -1
- package/dist/extensions/lib/installation/manager.js +1 -1
- package/dist/extensions/lib/sandbox/register/call-reference.js +1 -1
- package/dist/extensions/lib/sandbox/sdk/generators/log.js +1 -1
- package/dist/extensions/lib/sync-extensions.js +1 -1
- package/dist/extensions/manager.js +1 -1
- package/dist/flows.js +4 -5
- package/dist/{logger.js → logger/index.js} +2 -8
- package/dist/logger/redact-query.d.ts +1 -0
- package/dist/logger/redact-query.js +13 -0
- package/dist/mailer.js +1 -1
- package/dist/middleware/authenticate.js +2 -7
- package/dist/middleware/cache.js +2 -2
- package/dist/middleware/error-handler.js +1 -1
- package/dist/middleware/rate-limiter-global.js +1 -1
- package/dist/middleware/respond.js +2 -2
- package/dist/operations/log/index.js +1 -1
- package/dist/operations/mail/index.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 +10 -0
- package/dist/permissions/lib/fetch-permissions.js +55 -0
- package/dist/permissions/lib/fetch-policies.d.ts +7 -0
- package/dist/permissions/lib/fetch-policies.js +28 -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 +24 -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 +50 -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/request/is-denied-ip.js +1 -1
- package/dist/server.js +1 -1
- package/dist/services/access.d.ts +10 -0
- package/dist/services/access.js +43 -0
- package/dist/services/activity.js +23 -11
- package/dist/services/assets.d.ts +2 -3
- package/dist/services/assets.js +11 -6
- 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 +53 -24
- package/dist/services/files/utils/get-metadata.js +1 -1
- package/dist/services/files.js +25 -15
- 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/graphql/utils/process-error.js +1 -1
- package/dist/services/graphql/utils/sanitize-gql-schema.js +1 -1
- package/dist/services/import-export.js +19 -5
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.js +3 -2
- package/dist/services/items.d.ts +3 -3
- package/dist/services/items.js +115 -44
- package/dist/services/mail/index.js +1 -1
- package/dist/services/meta.js +60 -23
- package/dist/services/notifications.js +15 -7
- 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 +26 -29
- package/dist/services/roles.d.ts +4 -12
- package/dist/services/roles.js +57 -424
- package/dist/services/server.js +1 -1
- package/dist/services/shares.d.ts +0 -2
- package/dist/services/shares.js +13 -9
- package/dist/services/specifications.d.ts +2 -2
- package/dist/services/specifications.js +39 -27
- package/dist/services/tus/data-store.js +3 -1
- package/dist/services/users.d.ts +1 -5
- package/dist/services/users.js +79 -162
- package/dist/services/utils.js +11 -7
- package/dist/services/versions.d.ts +0 -2
- package/dist/services/versions.js +34 -10
- package/dist/services/webhooks.js +1 -1
- package/dist/telemetry/lib/get-report.js +2 -2
- package/dist/telemetry/lib/track.js +1 -1
- 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/items.d.ts +11 -0
- package/dist/utils/apply-diff.js +1 -1
- package/dist/utils/apply-query.d.ts +4 -3
- package/dist/utils/apply-query.js +37 -8
- package/dist/utils/delete-from-require-cache.js +1 -1
- package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +17 -0
- package/dist/utils/fetch-user-count/fetch-access-lookup.js +22 -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 +57 -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-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-default-value.js +1 -1
- package/dist/utils/get-ip-from-req.js +1 -1
- package/dist/utils/get-schema.js +1 -1
- package/dist/utils/get-service.js +5 -1
- package/dist/utils/is-url-allowed.js +1 -1
- package/dist/utils/reduce-schema.d.ts +4 -6
- package/dist/utils/reduce-schema.js +16 -32
- package/dist/utils/sanitize-query.js +1 -1
- package/dist/utils/transaction.js +1 -1
- package/dist/utils/validate-env.js +1 -1
- package/dist/utils/validate-storage.js +1 -1
- 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/base.d.ts +1 -1
- package/dist/websocket/controllers/base.js +21 -17
- package/dist/websocket/controllers/graphql.js +2 -5
- package/dist/websocket/controllers/hooks.js +4 -0
- package/dist/websocket/controllers/rest.js +1 -3
- package/dist/websocket/errors.js +1 -1
- package/dist/websocket/handlers/subscribe.js +0 -2
- package/dist/websocket/utils/items.d.ts +1 -1
- package/package.json +28 -27
- package/dist/database/run-ast.js +0 -450
- 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.d.ts +0 -3
- package/dist/utils/merge-permissions.js +0 -95
- /package/dist/{logger.d.ts → logger/index.d.ts} +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { PERMISSION_ACTIONS } from '@directus/constants';
|
|
2
|
+
import { mapValues, uniq } from 'lodash-es';
|
|
3
|
+
import { fetchPermissions } from '../../lib/fetch-permissions.js';
|
|
4
|
+
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
5
|
+
/**
|
|
6
|
+
* Get all permissions + minimal app permissions (if applicable) for the user + role in the current accountability.
|
|
7
|
+
* The permissions will be filtered by IP access.
|
|
8
|
+
*/
|
|
9
|
+
export async function fetchAccountabilityCollectionAccess(accountability, context) {
|
|
10
|
+
if (accountability.admin) {
|
|
11
|
+
return mapValues(context.schema.collections, () => Object.fromEntries(PERMISSION_ACTIONS.map((action) => [
|
|
12
|
+
action,
|
|
13
|
+
{
|
|
14
|
+
access: 'full',
|
|
15
|
+
fields: ['*'],
|
|
16
|
+
},
|
|
17
|
+
])));
|
|
18
|
+
}
|
|
19
|
+
const policies = await fetchPolicies(accountability, context);
|
|
20
|
+
const permissions = await fetchPermissions({ policies, accountability }, context);
|
|
21
|
+
const infos = {};
|
|
22
|
+
for (const perm of permissions) {
|
|
23
|
+
// Ensure that collection is in infos
|
|
24
|
+
if (!infos[perm.collection]) {
|
|
25
|
+
infos[perm.collection] = {
|
|
26
|
+
read: { access: 'none' },
|
|
27
|
+
create: { access: 'none' },
|
|
28
|
+
update: { access: 'none' },
|
|
29
|
+
delete: { access: 'none' },
|
|
30
|
+
share: { access: 'none' },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// Ensure that action with default values is in collection infos
|
|
34
|
+
if (infos[perm.collection][perm.action]?.access === 'none') {
|
|
35
|
+
// If a permissions is iterated over it means that the user has access to it, so set access to 'full'
|
|
36
|
+
// Set access to 'full' initially and refine that whenever a permission with filters is encountered
|
|
37
|
+
infos[perm.collection][perm.action].access = 'full';
|
|
38
|
+
}
|
|
39
|
+
const info = infos[perm.collection][perm.action];
|
|
40
|
+
// Set access to 'partial' if the permission has filters, which means that the user has conditional access
|
|
41
|
+
if (info.access === 'full' && perm.permissions && Object.keys(perm.permissions).length > 0) {
|
|
42
|
+
info.access = 'partial';
|
|
43
|
+
}
|
|
44
|
+
if (perm.fields && info.fields?.[0] !== '*') {
|
|
45
|
+
info.fields = uniq([...(info.fields || []), ...(perm.fields || [])]);
|
|
46
|
+
if (info.fields.includes('*')) {
|
|
47
|
+
info.fields = ['*'];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (perm.presets) {
|
|
51
|
+
info.presets = { ...(info.presets ?? {}), ...perm.presets };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// TODO Should fields by null, undefined or and empty array if no access?
|
|
55
|
+
return infos;
|
|
56
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Accountability, Globals } from '@directus/types';
|
|
2
|
+
import type { Context } from '../../types.js';
|
|
3
|
+
export declare function fetchAccountabilityPolicyGlobals(accountability: Pick<Accountability, 'user' | 'roles' | 'ip' | 'admin' | 'app'>, context: Context): Promise<Globals>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
2
|
+
export async function fetchAccountabilityPolicyGlobals(accountability, context) {
|
|
3
|
+
const policies = await fetchPolicies(accountability, context);
|
|
4
|
+
// Policies are already filtered down by the accountability IP, so we don't need to check it again
|
|
5
|
+
const result = await context.knex
|
|
6
|
+
.select(1)
|
|
7
|
+
.from('directus_policies')
|
|
8
|
+
.whereIn('id', policies)
|
|
9
|
+
.where('enforce_tfa', true)
|
|
10
|
+
.first();
|
|
11
|
+
return {
|
|
12
|
+
app_access: accountability.app,
|
|
13
|
+
admin_access: accountability.admin,
|
|
14
|
+
enforce_tfa: !!result,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Accountability, PermissionsAction } from '@directus/types';
|
|
2
|
+
import type { Context } from '../../types.js';
|
|
3
|
+
export interface FetchAllowedCollectionsOptions {
|
|
4
|
+
action: PermissionsAction;
|
|
5
|
+
accountability: Pick<Accountability, 'user' | 'role' | 'roles' | 'ip' | 'admin' | 'app'>;
|
|
6
|
+
}
|
|
7
|
+
export declare const fetchAllowedCollections: typeof _fetchAllowedCollections;
|
|
8
|
+
export declare function _fetchAllowedCollections({ action, accountability }: FetchAllowedCollectionsOptions, { knex, schema }: Context): Promise<string[]>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { uniq } from 'lodash-es';
|
|
2
|
+
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
3
|
+
import { withCache } from '../../utils/with-cache.js';
|
|
4
|
+
import { fetchPermissions } from '../../lib/fetch-permissions.js';
|
|
5
|
+
export const fetchAllowedCollections = withCache('allowed-collections', _fetchAllowedCollections, ({ action, accountability: { user, role, roles, ip, admin, app } }) => ({
|
|
6
|
+
action,
|
|
7
|
+
accountability: {
|
|
8
|
+
user,
|
|
9
|
+
role,
|
|
10
|
+
roles,
|
|
11
|
+
ip,
|
|
12
|
+
admin,
|
|
13
|
+
app,
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
export async function _fetchAllowedCollections({ action, accountability }, { knex, schema }) {
|
|
17
|
+
if (accountability.admin) {
|
|
18
|
+
return Object.keys(schema.collections);
|
|
19
|
+
}
|
|
20
|
+
const policies = await fetchPolicies(accountability, { knex, schema });
|
|
21
|
+
const permissions = await fetchPermissions({ action, policies, accountability }, { knex, schema });
|
|
22
|
+
const collections = permissions.map(({ collection }) => collection);
|
|
23
|
+
return uniq(collections);
|
|
24
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Accountability, PermissionsAction } from '@directus/types';
|
|
2
|
+
import type { Context } from '../../types.js';
|
|
3
|
+
export type FieldMap = Record<string, string[]>;
|
|
4
|
+
export interface FetchAllowedFieldMapOptions {
|
|
5
|
+
accountability: Pick<Accountability, 'user' | 'role' | 'roles' | 'ip' | 'admin' | 'app'>;
|
|
6
|
+
action: PermissionsAction;
|
|
7
|
+
}
|
|
8
|
+
export declare const fetchAllowedFieldMap: typeof _fetchAllowedFieldMap;
|
|
9
|
+
export declare function _fetchAllowedFieldMap({ accountability, action }: FetchAllowedFieldMapOptions, { knex, schema }: Context): Promise<FieldMap>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { uniq } from 'lodash-es';
|
|
2
|
+
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
3
|
+
import { withCache } from '../../utils/with-cache.js';
|
|
4
|
+
import { fetchPermissions } from '../../lib/fetch-permissions.js';
|
|
5
|
+
export const fetchAllowedFieldMap = withCache('allowed-field-map', _fetchAllowedFieldMap, ({ action, accountability: { user, role, roles, ip, admin, app } }) => ({
|
|
6
|
+
action,
|
|
7
|
+
accountability: { user, role, roles, ip, admin, app },
|
|
8
|
+
}));
|
|
9
|
+
export async function _fetchAllowedFieldMap({ accountability, action }, { knex, schema }) {
|
|
10
|
+
const fieldMap = {};
|
|
11
|
+
if (accountability.admin) {
|
|
12
|
+
for (const [collection, { fields }] of Object.entries(schema.collections)) {
|
|
13
|
+
fieldMap[collection] = Object.keys(fields);
|
|
14
|
+
}
|
|
15
|
+
return fieldMap;
|
|
16
|
+
}
|
|
17
|
+
const policies = await fetchPolicies(accountability, { knex, schema });
|
|
18
|
+
const permissions = await fetchPermissions({ action, policies, accountability }, { knex, schema });
|
|
19
|
+
for (const { collection, fields } of permissions) {
|
|
20
|
+
if (!fieldMap[collection]) {
|
|
21
|
+
fieldMap[collection] = [];
|
|
22
|
+
}
|
|
23
|
+
if (fields) {
|
|
24
|
+
fieldMap[collection].push(...fields);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const [collection, fields] of Object.entries(fieldMap)) {
|
|
28
|
+
fieldMap[collection] = uniq(fields);
|
|
29
|
+
}
|
|
30
|
+
return fieldMap;
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Accountability, PermissionsAction } from '@directus/types';
|
|
2
|
+
import type { Context } from '../../types.js';
|
|
3
|
+
export interface FetchAllowedFieldsOptions {
|
|
4
|
+
collection: string;
|
|
5
|
+
action: PermissionsAction;
|
|
6
|
+
accountability: Pick<Accountability, 'user' | 'role' | 'roles' | 'ip' | 'app'>;
|
|
7
|
+
}
|
|
8
|
+
export declare const fetchAllowedFields: typeof _fetchAllowedFields;
|
|
9
|
+
/**
|
|
10
|
+
* Look up all fields that are allowed to be used for the given collection and action for the given
|
|
11
|
+
* accountability object
|
|
12
|
+
*
|
|
13
|
+
* Done by looking up all available policies for the current accountability object, and reading all
|
|
14
|
+
* permissions that exist for the collection+action+policy combination
|
|
15
|
+
*/
|
|
16
|
+
export declare function _fetchAllowedFields({ accountability, action, collection }: FetchAllowedFieldsOptions, { knex, schema }: Context): Promise<string[]>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { uniq } from 'lodash-es';
|
|
2
|
+
import { fetchPermissions } from '../../lib/fetch-permissions.js';
|
|
3
|
+
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
4
|
+
import { withCache } from '../../utils/with-cache.js';
|
|
5
|
+
export const fetchAllowedFields = withCache('allowed-fields', _fetchAllowedFields, ({ action, collection, accountability: { user, role, roles, ip, app } }) => ({
|
|
6
|
+
action,
|
|
7
|
+
collection,
|
|
8
|
+
accountability: { user, role, roles, ip, app },
|
|
9
|
+
}));
|
|
10
|
+
/**
|
|
11
|
+
* Look up all fields that are allowed to be used for the given collection and action for the given
|
|
12
|
+
* accountability object
|
|
13
|
+
*
|
|
14
|
+
* Done by looking up all available policies for the current accountability object, and reading all
|
|
15
|
+
* permissions that exist for the collection+action+policy combination
|
|
16
|
+
*/
|
|
17
|
+
export async function _fetchAllowedFields({ accountability, action, collection }, { knex, schema }) {
|
|
18
|
+
const policies = await fetchPolicies(accountability, { knex, schema });
|
|
19
|
+
const permissions = await fetchPermissions({ action, collections: [collection], policies, accountability }, { knex, schema });
|
|
20
|
+
const allowedFields = [];
|
|
21
|
+
for (const { fields } of permissions) {
|
|
22
|
+
if (!fields)
|
|
23
|
+
continue;
|
|
24
|
+
allowedFields.push(...fields);
|
|
25
|
+
}
|
|
26
|
+
return uniq(allowedFields).filter((field) => field === '*' || field in (schema.collections[collection]?.fields ?? {}));
|
|
27
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Accountability } from '@directus/types';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import type { GlobalAccess } from './types.js';
|
|
4
|
+
export declare const fetchGlobalAccess: typeof _fetchGlobalAccess;
|
|
5
|
+
/**
|
|
6
|
+
* Fetch the global access (eg admin/app access) rules for the given roles, or roles+user combination
|
|
7
|
+
*
|
|
8
|
+
* Will fetch roles and user info separately so they can be cached and reused individually
|
|
9
|
+
*/
|
|
10
|
+
export declare function _fetchGlobalAccess(accountability: Pick<Accountability, 'user' | 'roles' | 'ip'>, knex: Knex): Promise<GlobalAccess>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { withCache } from '../../utils/with-cache.js';
|
|
2
|
+
import { fetchGlobalAccessForRoles } from './lib/fetch-global-access-for-roles.js';
|
|
3
|
+
import { fetchGlobalAccessForUser } from './lib/fetch-global-access-for-user.js';
|
|
4
|
+
export const fetchGlobalAccess = withCache('global-access', _fetchGlobalAccess, ({ user, roles, ip }) => ({
|
|
5
|
+
user,
|
|
6
|
+
roles,
|
|
7
|
+
ip,
|
|
8
|
+
}));
|
|
9
|
+
/**
|
|
10
|
+
* Fetch the global access (eg admin/app access) rules for the given roles, or roles+user combination
|
|
11
|
+
*
|
|
12
|
+
* Will fetch roles and user info separately so they can be cached and reused individually
|
|
13
|
+
*/
|
|
14
|
+
export async function _fetchGlobalAccess(accountability, knex) {
|
|
15
|
+
const access = await fetchGlobalAccessForRoles(accountability, knex);
|
|
16
|
+
if (accountability.user !== undefined) {
|
|
17
|
+
const userAccess = await fetchGlobalAccessForUser(accountability, knex);
|
|
18
|
+
// If app/admin is already true, keep it true
|
|
19
|
+
access.app ||= userAccess.app;
|
|
20
|
+
access.admin ||= userAccess.admin;
|
|
21
|
+
}
|
|
22
|
+
return access;
|
|
23
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Accountability } from '@directus/types';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import type { GlobalAccess } from '../types.js';
|
|
4
|
+
export declare const fetchGlobalAccessForRoles: typeof _fetchGlobalAccessForRoles;
|
|
5
|
+
export declare function _fetchGlobalAccessForRoles(accountability: Pick<Accountability, 'roles' | 'ip'>, knex: Knex): Promise<GlobalAccess>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { withCache } from '../../../utils/with-cache.js';
|
|
2
|
+
import { fetchGlobalAccessForQuery } from '../utils/fetch-global-access-for-query.js';
|
|
3
|
+
export const fetchGlobalAccessForRoles = withCache('global-access-role', _fetchGlobalAccessForRoles, ({ roles, ip }) => ({ roles, ip }));
|
|
4
|
+
export async function _fetchGlobalAccessForRoles(accountability, knex) {
|
|
5
|
+
const query = knex.where('role', 'in', accountability.roles);
|
|
6
|
+
return await fetchGlobalAccessForQuery(query, accountability);
|
|
7
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Accountability } from '@directus/types';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import type { GlobalAccess } from '../types.js';
|
|
4
|
+
export declare const fetchGlobalAccessForUser: typeof _fetchGlobalAccessForUser;
|
|
5
|
+
export declare function _fetchGlobalAccessForUser(accountability: Pick<Accountability, 'user' | 'ip'>, knex: Knex): Promise<GlobalAccess>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { withCache } from '../../../utils/with-cache.js';
|
|
2
|
+
import { fetchGlobalAccessForQuery } from '../utils/fetch-global-access-for-query.js';
|
|
3
|
+
export const fetchGlobalAccessForUser = withCache('global-access-user', _fetchGlobalAccessForUser, ({ user, ip }) => ({
|
|
4
|
+
user,
|
|
5
|
+
ip,
|
|
6
|
+
}));
|
|
7
|
+
export async function _fetchGlobalAccessForUser(accountability, knex) {
|
|
8
|
+
const query = knex.where('user', '=', accountability.user);
|
|
9
|
+
return await fetchGlobalAccessForQuery(query, accountability);
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Accountability } from '@directus/types';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
import type { GlobalAccess } from '../types.js';
|
|
4
|
+
export declare function fetchGlobalAccessForQuery(query: Knex.QueryBuilder<any, any[]>, accountability: Pick<Accountability, 'ip'>): Promise<GlobalAccess>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { toBoolean, toArray } from '@directus/utils';
|
|
2
|
+
import { ipInNetworks } from '../../../../utils/ip-in-networks.js';
|
|
3
|
+
export async function fetchGlobalAccessForQuery(query, accountability) {
|
|
4
|
+
const globalAccess = {
|
|
5
|
+
app: false,
|
|
6
|
+
admin: false,
|
|
7
|
+
};
|
|
8
|
+
const accessRows = await query
|
|
9
|
+
.select('directus_policies.admin_access', 'directus_policies.app_access', 'directus_policies.ip_access')
|
|
10
|
+
.from('directus_access')
|
|
11
|
+
// @NOTE: `where` clause comes from the caller
|
|
12
|
+
.leftJoin('directus_policies', 'directus_policies.id', 'directus_access.policy');
|
|
13
|
+
// Additively merge access permissions
|
|
14
|
+
for (const { admin_access, app_access, ip_access } of accessRows) {
|
|
15
|
+
if (accountability.ip && ip_access) {
|
|
16
|
+
// Skip row if IP is not in the allowed networks
|
|
17
|
+
const networks = toArray(ip_access);
|
|
18
|
+
if (!ipInNetworks(accountability.ip, networks))
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
globalAccess.admin ||= toBoolean(admin_access);
|
|
22
|
+
globalAccess.app ||= globalAccess.admin || toBoolean(app_access);
|
|
23
|
+
if (globalAccess.admin)
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
return globalAccess;
|
|
27
|
+
}
|
package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Accountability, PermissionsAction } from '@directus/types';
|
|
2
|
+
import type { Context } from '../../types.js';
|
|
3
|
+
export type FieldMap = Record<string, string[]>;
|
|
4
|
+
export interface FetchInconsistentFieldMapOptions {
|
|
5
|
+
accountability: Pick<Accountability, 'user' | 'role' | 'roles' | 'ip' | 'admin' | 'app'> | null;
|
|
6
|
+
action: PermissionsAction;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Fetch a field map for fields that may or may not be null based on item-by-item permissions.
|
|
10
|
+
*/
|
|
11
|
+
export declare const fetchInconsistentFieldMap: typeof _fetchInconsistentFieldMap;
|
|
12
|
+
export declare function _fetchInconsistentFieldMap({ accountability, action }: FetchInconsistentFieldMapOptions, { knex, schema }: Context): Promise<FieldMap>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { uniq, intersection, difference, pick } from 'lodash-es';
|
|
2
|
+
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
3
|
+
import { withCache } from '../../utils/with-cache.js';
|
|
4
|
+
import { fetchPermissions } from '../../lib/fetch-permissions.js';
|
|
5
|
+
/**
|
|
6
|
+
* Fetch a field map for fields that may or may not be null based on item-by-item permissions.
|
|
7
|
+
*/
|
|
8
|
+
export const fetchInconsistentFieldMap = withCache('inconsistent-field-map', _fetchInconsistentFieldMap, ({ action, accountability }) => ({
|
|
9
|
+
action,
|
|
10
|
+
accountability: accountability ? pick(accountability, ['user', 'role', 'roles', 'ip', 'admin', 'app']) : null,
|
|
11
|
+
}));
|
|
12
|
+
export async function _fetchInconsistentFieldMap({ accountability, action }, { knex, schema }) {
|
|
13
|
+
const fieldMap = {};
|
|
14
|
+
if (!accountability || accountability.admin) {
|
|
15
|
+
for (const collection of Object.keys(schema.collections)) {
|
|
16
|
+
fieldMap[collection] = [];
|
|
17
|
+
}
|
|
18
|
+
return fieldMap;
|
|
19
|
+
}
|
|
20
|
+
const policies = await fetchPolicies(accountability, { knex, schema });
|
|
21
|
+
const permissions = await fetchPermissions({ action, policies, accountability }, { knex, schema });
|
|
22
|
+
const collections = uniq(permissions.map(({ collection }) => collection));
|
|
23
|
+
for (const collection of collections) {
|
|
24
|
+
const fields = permissions
|
|
25
|
+
.filter((permission) => permission.collection === collection)
|
|
26
|
+
.map((permission) => permission.fields ?? []);
|
|
27
|
+
const availableEverywhere = intersection(...fields);
|
|
28
|
+
const availableSomewhere = difference(uniq(fields.flat()), availableEverywhere);
|
|
29
|
+
fieldMap[collection] = availableSomewhere;
|
|
30
|
+
}
|
|
31
|
+
return fieldMap;
|
|
32
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Accountability } from '@directus/types';
|
|
2
|
+
import type { Knex } from 'knex';
|
|
3
|
+
export declare const fetchPoliciesIpAccess: typeof _fetchPoliciesIpAccess;
|
|
4
|
+
export declare function _fetchPoliciesIpAccess(accountability: Pick<Accountability, 'user' | 'roles'>, knex: Knex): Promise<string[][]>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { toArray } from '@directus/utils';
|
|
2
|
+
import { withCache } from '../../utils/with-cache.js';
|
|
3
|
+
export const fetchPoliciesIpAccess = withCache('policies-ip-access', _fetchPoliciesIpAccess, ({ user, roles }) => ({
|
|
4
|
+
user,
|
|
5
|
+
roles,
|
|
6
|
+
}));
|
|
7
|
+
export async function _fetchPoliciesIpAccess(accountability, knex) {
|
|
8
|
+
const query = knex('directus_access')
|
|
9
|
+
.select({ ip_access: 'directus_policies.ip_access' })
|
|
10
|
+
.leftJoin('directus_policies', 'directus_access.policy', 'directus_policies.id')
|
|
11
|
+
.whereNotNull('directus_policies.ip_access');
|
|
12
|
+
// No roles and no user means unauthenticated request
|
|
13
|
+
if (accountability.roles.length === 0 && !accountability.user) {
|
|
14
|
+
query.where({
|
|
15
|
+
role: null,
|
|
16
|
+
user: null,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
query.where(function () {
|
|
21
|
+
if (accountability.user) {
|
|
22
|
+
this.orWhere('directus_access.user', accountability.user);
|
|
23
|
+
}
|
|
24
|
+
this.orWhereIn('directus_access.role', accountability.roles);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const rows = await query;
|
|
28
|
+
return rows.filter(({ ip_access }) => ip_access).map(({ ip_access }) => toArray(ip_access));
|
|
29
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { FieldNode, FunctionFieldNode, NestedCollectionNode } from '../../../../types/ast.js';
|
|
3
|
+
import type { FieldMap, QueryPath } from '../types.js';
|
|
4
|
+
export declare function extractFieldsFromChildren(collection: string, children: (NestedCollectionNode | FieldNode | FunctionFieldNode)[], fieldMap: FieldMap, schema: SchemaOverview, path?: QueryPath): void;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getUnaliasedFieldKey } from '../../../utils/get-unaliased-field-key.js';
|
|
2
|
+
import { formatA2oKey } from '../utils/format-a2o-key.js';
|
|
3
|
+
import { getInfoForPath } from '../utils/get-info-for-path.js';
|
|
4
|
+
import { extractFieldsFromQuery } from './extract-fields-from-query.js';
|
|
5
|
+
export function extractFieldsFromChildren(collection, children, fieldMap, schema, path = []) {
|
|
6
|
+
const info = getInfoForPath(fieldMap, 'other', path, collection);
|
|
7
|
+
for (const child of children) {
|
|
8
|
+
info.fields.add(getUnaliasedFieldKey(child));
|
|
9
|
+
if (child.type === 'a2o') {
|
|
10
|
+
for (const [collection, children] of Object.entries(child.children)) {
|
|
11
|
+
extractFieldsFromChildren(collection, children, fieldMap, schema, [
|
|
12
|
+
...path,
|
|
13
|
+
formatA2oKey(child.fieldKey, collection),
|
|
14
|
+
]);
|
|
15
|
+
}
|
|
16
|
+
if (child.query) {
|
|
17
|
+
for (const [collection, query] of Object.entries(child.query)) {
|
|
18
|
+
extractFieldsFromQuery(collection, query, fieldMap, schema, [
|
|
19
|
+
...path,
|
|
20
|
+
formatA2oKey(child.fieldKey, collection),
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else if (child.type === 'm2o') {
|
|
26
|
+
extractFieldsFromChildren(child.relation.related_collection, child.children, fieldMap, schema, [
|
|
27
|
+
...path,
|
|
28
|
+
child.fieldKey,
|
|
29
|
+
]);
|
|
30
|
+
extractFieldsFromQuery(child.relation.related_collection, child.query, fieldMap, schema, [
|
|
31
|
+
...path,
|
|
32
|
+
child.fieldKey,
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
else if (child.type === 'o2m') {
|
|
36
|
+
extractFieldsFromChildren(child.relation.collection, child.children, fieldMap, schema, [
|
|
37
|
+
...path,
|
|
38
|
+
child.fieldKey,
|
|
39
|
+
]);
|
|
40
|
+
extractFieldsFromQuery(child.relation.collection, child.query, fieldMap, schema, [...path, child.fieldKey]);
|
|
41
|
+
}
|
|
42
|
+
else if (child.type === 'functionField') {
|
|
43
|
+
// functionFields operate on a related o2m collection, we have to make sure we include a
|
|
44
|
+
// no-field read check to the related collection
|
|
45
|
+
extractFieldsFromChildren(child.relatedCollection, [], fieldMap, schema, [...path, child.fieldKey]);
|
|
46
|
+
extractFieldsFromQuery(child.relatedCollection, child.query, fieldMap, schema, [...path, child.fieldKey]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Query, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { CollectionKey, FieldKey, FieldMap } from '../types.js';
|
|
3
|
+
export declare function extractFieldsFromQuery(collection: CollectionKey, query: Query, fieldMap: FieldMap, schema: SchemaOverview, pathPrefix?: FieldKey[]): void;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { parseFilterKey } from '../../../../utils/parse-filter-key.js';
|
|
2
|
+
import { extractPathsFromQuery } from '../utils/extract-paths-from-query.js';
|
|
3
|
+
import { findRelatedCollection } from '../utils/find-related-collection.js';
|
|
4
|
+
import { getInfoForPath } from '../utils/get-info-for-path.js';
|
|
5
|
+
export function extractFieldsFromQuery(collection, query, fieldMap, schema, pathPrefix = []) {
|
|
6
|
+
if (!query)
|
|
7
|
+
return;
|
|
8
|
+
const { paths: otherPaths, readOnlyPaths } = extractPathsFromQuery(query);
|
|
9
|
+
const groupedPaths = {
|
|
10
|
+
other: otherPaths,
|
|
11
|
+
read: readOnlyPaths,
|
|
12
|
+
};
|
|
13
|
+
for (const [group, paths] of Object.entries(groupedPaths)) {
|
|
14
|
+
for (const path of paths) {
|
|
15
|
+
/**
|
|
16
|
+
* Current path stack. For each iteration of the path loop this will be appended with the
|
|
17
|
+
* current part we're operating on. So when looping over ['category', 'created_by', 'name']
|
|
18
|
+
* the first iteration it'll be `['category']`, and then `['category', 'created_by']` etc.
|
|
19
|
+
*/
|
|
20
|
+
const stack = [];
|
|
21
|
+
/**
|
|
22
|
+
* Current collection the path part we're operating on lives in. Once we hit a relational
|
|
23
|
+
* field, this will be updated to the related collection, so we can follow the relational path
|
|
24
|
+
* left to right.
|
|
25
|
+
*/
|
|
26
|
+
let collectionContext = collection;
|
|
27
|
+
for (const part of path) {
|
|
28
|
+
const info = getInfoForPath(fieldMap, group, [...pathPrefix, ...stack], collectionContext);
|
|
29
|
+
// A2o specifier field fetch
|
|
30
|
+
if (part.includes(':')) {
|
|
31
|
+
const [fieldKey, collection] = part.split(':');
|
|
32
|
+
info.fields.add(fieldKey);
|
|
33
|
+
collectionContext = collection;
|
|
34
|
+
stack.push(part);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (part.startsWith('$FOLLOW(') && part.endsWith(')')) {
|
|
38
|
+
// Don't add this implicit relation field to fields, as it will be accounted for in the reverse direction
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const { fieldName } = parseFilterKey(part);
|
|
42
|
+
info.fields.add(fieldName);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Related collection for the current part. Is null when the current field isn't a
|
|
46
|
+
* relational field.
|
|
47
|
+
*/
|
|
48
|
+
const relatedCollection = findRelatedCollection(collectionContext, part, schema);
|
|
49
|
+
if (relatedCollection) {
|
|
50
|
+
collectionContext = relatedCollection;
|
|
51
|
+
stack.push(part);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { extractFieldsFromChildren } from './extract-fields-from-children.js';
|
|
2
|
+
import { extractFieldsFromQuery } from './extract-fields-from-query.js';
|
|
3
|
+
export function fieldMapFromAst(ast, schema) {
|
|
4
|
+
const fieldMap = { read: new Map(), other: new Map() };
|
|
5
|
+
extractFieldsFromChildren(ast.name, ast.children, fieldMap, schema);
|
|
6
|
+
extractFieldsFromQuery(ast.name, ast.query, fieldMap, schema);
|
|
7
|
+
return fieldMap;
|
|
8
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Permission } from '@directus/types';
|
|
2
|
+
import type { AST } from '../../../../types/ast.js';
|
|
3
|
+
/**
|
|
4
|
+
* Mutates passed AST
|
|
5
|
+
*
|
|
6
|
+
* @param ast - Read query AST
|
|
7
|
+
* @param permissions - Expected to be filtered down for the policies and action already
|
|
8
|
+
*/
|
|
9
|
+
export declare function injectCases(ast: AST, permissions: Permission[]): void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { getUnaliasedFieldKey } from '../../../utils/get-unaliased-field-key.js';
|
|
2
|
+
import { dedupeAccess } from '../utils/dedupe-access.js';
|
|
3
|
+
import { hasItemPermissions } from '../utils/has-item-permissions.js';
|
|
4
|
+
import { uniq } from 'lodash-es';
|
|
5
|
+
/**
|
|
6
|
+
* Mutates passed AST
|
|
7
|
+
*
|
|
8
|
+
* @param ast - Read query AST
|
|
9
|
+
* @param permissions - Expected to be filtered down for the policies and action already
|
|
10
|
+
*/
|
|
11
|
+
export function injectCases(ast, permissions) {
|
|
12
|
+
ast.cases = processChildren(ast.name, ast.children, permissions);
|
|
13
|
+
}
|
|
14
|
+
function processChildren(collection, children, permissions) {
|
|
15
|
+
// Use uniq here, since there might be multiple duplications due to aliases or functions
|
|
16
|
+
const requestedKeys = uniq(children.map(getUnaliasedFieldKey));
|
|
17
|
+
const { cases, caseMap, allowedFields } = getCases(collection, permissions, requestedKeys);
|
|
18
|
+
// TODO this can be optimized if there is only one rule to skip the whole case/where system,
|
|
19
|
+
// since fields that are not allowed at all are already filtered out
|
|
20
|
+
// TODO this can be optimized if all cases are the same for all requested keys, as those should be
|
|
21
|
+
//
|
|
22
|
+
for (const child of children) {
|
|
23
|
+
// If there's one or more permissions that allow full access to this field, we can safe some
|
|
24
|
+
// query perf overhead by ignoring the whole case/where system
|
|
25
|
+
const fieldKey = getUnaliasedFieldKey(child);
|
|
26
|
+
if (allowedFields.has('*') || allowedFields.has(fieldKey))
|
|
27
|
+
continue;
|
|
28
|
+
const globalWhenCase = caseMap['*'];
|
|
29
|
+
const fieldWhenCase = caseMap[fieldKey];
|
|
30
|
+
// Validation should catch any fields that are attempted to be read that don't have any access control configured.
|
|
31
|
+
// When there are no access rules for this field, and no rules for "all" fields `*`, we missed something in the validation
|
|
32
|
+
// and should abort.
|
|
33
|
+
if (!globalWhenCase && !fieldWhenCase) {
|
|
34
|
+
throw new Error(`Cannot extract access permissions for field "${fieldKey}" in collection "${collection}"`);
|
|
35
|
+
}
|
|
36
|
+
// Global and field can't both be undefined as per the error check prior
|
|
37
|
+
child.whenCase = [...(globalWhenCase ?? []), ...(fieldWhenCase ?? [])];
|
|
38
|
+
if (child.type === 'm2o') {
|
|
39
|
+
child.cases = processChildren(child.relation.related_collection, child.children, permissions);
|
|
40
|
+
}
|
|
41
|
+
if (child.type === 'o2m') {
|
|
42
|
+
child.cases = processChildren(child.relation.collection, child.children, permissions);
|
|
43
|
+
}
|
|
44
|
+
if (child.type === 'a2o') {
|
|
45
|
+
for (const collection of child.names) {
|
|
46
|
+
child.cases[collection] = processChildren(collection, child.children[collection] ?? [], permissions);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (child.type === 'functionField') {
|
|
50
|
+
const { cases } = getCases(child.relatedCollection, permissions, []);
|
|
51
|
+
child.cases = cases;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return cases;
|
|
55
|
+
}
|
|
56
|
+
function getCases(collection, permissions, requestedKeys) {
|
|
57
|
+
const permissionsForCollection = permissions.filter((permission) => permission.collection === collection);
|
|
58
|
+
const rules = dedupeAccess(permissionsForCollection);
|
|
59
|
+
const cases = [];
|
|
60
|
+
const caseMap = {};
|
|
61
|
+
// TODO this can be optimized if there is only one rule to skip the whole case/where system,
|
|
62
|
+
// since fields that are not allowed at all are already filtered out
|
|
63
|
+
// TODO this can be optimized if all cases are the same for all requested keys, as those should be
|
|
64
|
+
//
|
|
65
|
+
let index = 0;
|
|
66
|
+
for (const { rule, fields } of rules) {
|
|
67
|
+
// If none of the fields in the current permissions rule overlap with the actually requested
|
|
68
|
+
// fields in the AST, we can ignore this case altogether
|
|
69
|
+
if (requestedKeys.length > 0 &&
|
|
70
|
+
fields.has('*') === false &&
|
|
71
|
+
Array.from(fields).every((field) => requestedKeys.includes(field) === false)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (rule === null)
|
|
75
|
+
continue;
|
|
76
|
+
cases.push(rule);
|
|
77
|
+
for (const field of fields) {
|
|
78
|
+
caseMap[field] = [...(caseMap[field] ?? []), index];
|
|
79
|
+
}
|
|
80
|
+
index++;
|
|
81
|
+
}
|
|
82
|
+
// Field that are allowed no matter what conditions exist for the item. These come from
|
|
83
|
+
// permissions where the item read access is "everything"
|
|
84
|
+
const allowedFields = new Set(permissionsForCollection
|
|
85
|
+
.filter((permission) => hasItemPermissions(permission) === false)
|
|
86
|
+
.map((permission) => permission.fields ?? [])
|
|
87
|
+
.flat());
|
|
88
|
+
return {
|
|
89
|
+
cases,
|
|
90
|
+
caseMap,
|
|
91
|
+
allowedFields,
|
|
92
|
+
};
|
|
93
|
+
}
|