@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,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
|
+
const fieldKey = getUnaliasedFieldKey(child);
|
|
24
|
+
const globalWhenCase = caseMap['*'];
|
|
25
|
+
const fieldWhenCase = caseMap[fieldKey];
|
|
26
|
+
// Validation should catch any fields that are attempted to be read that don't have any access control configured.
|
|
27
|
+
// When there are no access rules for this field, and no rules for "all" fields `*`, we missed something in the validation
|
|
28
|
+
// and should abort.
|
|
29
|
+
if (!globalWhenCase && !fieldWhenCase) {
|
|
30
|
+
throw new Error(`Cannot extract access permissions for field "${fieldKey}" in collection "${collection}"`);
|
|
31
|
+
}
|
|
32
|
+
// The case/when system only needs to take place if no full access is given on this field,
|
|
33
|
+
// otherwise we can skip and thus safe some query perf overhead
|
|
34
|
+
if (!allowedFields.has('*') && !allowedFields.has(fieldKey)) {
|
|
35
|
+
// Global and field can't both be undefined as per the error check prior
|
|
36
|
+
child.whenCase = [...(globalWhenCase ?? []), ...(fieldWhenCase ?? [])];
|
|
37
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Accountability, PermissionsAction } from '@directus/types';
|
|
2
|
+
import type { AST } from '../../../types/ast.js';
|
|
3
|
+
import type { Context } from '../../types.js';
|
|
4
|
+
export interface ProcessAstOptions {
|
|
5
|
+
ast: AST;
|
|
6
|
+
action: PermissionsAction;
|
|
7
|
+
accountability: Accountability | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function processAst(options: ProcessAstOptions, context: Context): Promise<AST>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { fetchPermissions } from '../../lib/fetch-permissions.js';
|
|
2
|
+
import { fetchPolicies } from '../../lib/fetch-policies.js';
|
|
3
|
+
import { fieldMapFromAst } from './lib/field-map-from-ast.js';
|
|
4
|
+
import { injectCases } from './lib/inject-cases.js';
|
|
5
|
+
import { collectionsInFieldMap } from './utils/collections-in-field-map.js';
|
|
6
|
+
import { validatePathPermissions } from './utils/validate-path/validate-path-permissions.js';
|
|
7
|
+
import { validatePathExistence } from './utils/validate-path/validate-path-existence.js';
|
|
8
|
+
export async function processAst(options, context) {
|
|
9
|
+
// FieldMap is a Map of paths in the AST, with each path containing the collection and fields in
|
|
10
|
+
// that collection that the AST path tries to access
|
|
11
|
+
const fieldMap = fieldMapFromAst(options.ast, context.schema);
|
|
12
|
+
const collections = collectionsInFieldMap(fieldMap);
|
|
13
|
+
if (!options.accountability || options.accountability.admin) {
|
|
14
|
+
// Validate the field existence, even if no permissions apply to the current accountability
|
|
15
|
+
for (const [path, { collection, fields }] of [...fieldMap.read.entries(), ...fieldMap.other.entries()]) {
|
|
16
|
+
validatePathExistence(path, collection, fields, context.schema);
|
|
17
|
+
}
|
|
18
|
+
return options.ast;
|
|
19
|
+
}
|
|
20
|
+
const policies = await fetchPolicies(options.accountability, context);
|
|
21
|
+
const permissions = await fetchPermissions({ action: options.action, policies, collections, accountability: options.accountability }, context);
|
|
22
|
+
const readPermissions = options.action === 'read'
|
|
23
|
+
? permissions
|
|
24
|
+
: await fetchPermissions({ action: 'read', policies, collections, accountability: options.accountability }, context);
|
|
25
|
+
// Validate field existence first
|
|
26
|
+
for (const [path, { collection, fields }] of [...fieldMap.read.entries(), ...fieldMap.other.entries()]) {
|
|
27
|
+
validatePathExistence(path, collection, fields, context.schema);
|
|
28
|
+
}
|
|
29
|
+
// Validate permissions for the fields
|
|
30
|
+
for (const [path, { collection, fields }] of fieldMap.other.entries()) {
|
|
31
|
+
validatePathPermissions(path, permissions, collection, fields);
|
|
32
|
+
}
|
|
33
|
+
// Validate permission for read only fields
|
|
34
|
+
for (const [path, { collection, fields }] of fieldMap.read.entries()) {
|
|
35
|
+
validatePathPermissions(path, readPermissions, collection, fields);
|
|
36
|
+
}
|
|
37
|
+
injectCases(options.ast, permissions);
|
|
38
|
+
return options.ast;
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type CollectionKey = string;
|
|
2
|
+
export type FieldKey = string;
|
|
3
|
+
export type QueryPath = string[];
|
|
4
|
+
/**
|
|
5
|
+
* Key is dot-notation QueryPath, f.e. `category.created_by`.
|
|
6
|
+
* Value contains collection context for that path, and fields fetched within
|
|
7
|
+
*/
|
|
8
|
+
export type FieldMapEntries = Map<string, {
|
|
9
|
+
collection: CollectionKey;
|
|
10
|
+
fields: Set<FieldKey>;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* FieldMapEntries that require only read permissions and those that require action specific permissions
|
|
14
|
+
*/
|
|
15
|
+
export type FieldMap = {
|
|
16
|
+
read: FieldMapEntries;
|
|
17
|
+
other: FieldMapEntries;
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Filter, Permission } from '@directus/types';
|
|
2
|
+
/**
|
|
3
|
+
* Deduplicate the permissions sets by merging the field sets based on the access control rules
|
|
4
|
+
* (`permissions` in Permission rows)
|
|
5
|
+
*
|
|
6
|
+
* This allows the cases injection to be more efficient by not having to generate duplicate
|
|
7
|
+
* case/when clauses for permission sets where the rule access is identical
|
|
8
|
+
*/
|
|
9
|
+
export declare function dedupeAccess(permissions: Permission[]): {
|
|
10
|
+
rule: Filter;
|
|
11
|
+
fields: Set<string>;
|
|
12
|
+
}[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import hash from 'object-hash';
|
|
2
|
+
/**
|
|
3
|
+
* Deduplicate the permissions sets by merging the field sets based on the access control rules
|
|
4
|
+
* (`permissions` in Permission rows)
|
|
5
|
+
*
|
|
6
|
+
* This allows the cases injection to be more efficient by not having to generate duplicate
|
|
7
|
+
* case/when clauses for permission sets where the rule access is identical
|
|
8
|
+
*/
|
|
9
|
+
export function dedupeAccess(permissions) {
|
|
10
|
+
// Map of `ruleHash: fields[]`
|
|
11
|
+
const map = new Map();
|
|
12
|
+
for (const permission of permissions) {
|
|
13
|
+
const rule = permission.permissions ?? {};
|
|
14
|
+
// Two JS objects can't be equality checked. Object-hash will resort any nested arrays
|
|
15
|
+
// deterministically meaning that this can be used to compare two rule sets where the array
|
|
16
|
+
// order does not matter
|
|
17
|
+
const ruleHash = hash(rule, {
|
|
18
|
+
algorithm: 'passthrough',
|
|
19
|
+
unorderedArrays: true,
|
|
20
|
+
});
|
|
21
|
+
if (map.has(ruleHash) === false) {
|
|
22
|
+
map.set(ruleHash, { rule, fields: new Set() });
|
|
23
|
+
}
|
|
24
|
+
const info = map.get(ruleHash);
|
|
25
|
+
for (const field of permission.fields ?? []) {
|
|
26
|
+
info.fields.add(field);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return Array.from(map.values());
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Query } from '@directus/types';
|
|
2
|
+
/**
|
|
3
|
+
* Converts the passed Query object into a unique list of path arrays, for example:
|
|
4
|
+
*
|
|
5
|
+
* ```
|
|
6
|
+
* [
|
|
7
|
+
* ['author', 'age'],
|
|
8
|
+
* ['category']
|
|
9
|
+
* ]
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare function extractPathsFromQuery(query: Query): {
|
|
13
|
+
paths: string[][];
|
|
14
|
+
readOnlyPaths: string[][];
|
|
15
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { isEqual, uniqWith } from 'lodash-es';
|
|
2
|
+
import { flattenFilter } from './flatten-filter.js';
|
|
3
|
+
/**
|
|
4
|
+
* Converts the passed Query object into a unique list of path arrays, for example:
|
|
5
|
+
*
|
|
6
|
+
* ```
|
|
7
|
+
* [
|
|
8
|
+
* ['author', 'age'],
|
|
9
|
+
* ['category']
|
|
10
|
+
* ]
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export function extractPathsFromQuery(query) {
|
|
14
|
+
/**
|
|
15
|
+
* All nested paths used in the current query scope.
|
|
16
|
+
* This is generated by flattening the filters and adding in the used sort/aggregate fields.
|
|
17
|
+
*/
|
|
18
|
+
const paths = [];
|
|
19
|
+
const readOnlyPaths = [];
|
|
20
|
+
if (query.filter) {
|
|
21
|
+
flattenFilter(readOnlyPaths, query.filter);
|
|
22
|
+
}
|
|
23
|
+
if (query.sort) {
|
|
24
|
+
for (const field of query.sort) {
|
|
25
|
+
// Sort can have dot notation fields for sorting on m2o values Sort fields can start with
|
|
26
|
+
// `-` to indicate descending order, which should be dropped for permissions checks
|
|
27
|
+
const parts = field.split('.').map((field) => (field.startsWith('-') ? field.substring(1) : field));
|
|
28
|
+
if (query.aggregate && parts.length > 0 && parts[0] in query.aggregate) {
|
|
29
|
+
// If query is an aggregate query and the first part is a requested aggregate operation, ignore the whole field.
|
|
30
|
+
// The correct field is extracted into the field map when processing the `query.aggregate` fields.
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
readOnlyPaths.push(parts);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (query.aggregate) {
|
|
37
|
+
for (const fields of Object.values(query.aggregate)) {
|
|
38
|
+
for (const field of fields) {
|
|
39
|
+
if (field === '*') {
|
|
40
|
+
// Don't add wildcard field to the paths
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
// Aggregate doesn't currently support aggregating on nested fields, but it doesn't hurt
|
|
44
|
+
// to standardize it in the validation layer
|
|
45
|
+
paths.push(field.split('.'));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (query.group) {
|
|
50
|
+
for (const field of query.group) {
|
|
51
|
+
// Grouping doesn't currently support grouping on nested fields, but it doesn't hurt to
|
|
52
|
+
// standardize it in the validation layer
|
|
53
|
+
paths.push(field.split('.'));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
paths: uniqWith(paths, isEqual),
|
|
58
|
+
readOnlyPaths: uniqWith(readOnlyPaths, isEqual),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getRelationInfo } from '../../../../utils/get-relation-info.js';
|
|
2
|
+
export function findRelatedCollection(collection, field, schema) {
|
|
3
|
+
const { relation } = getRelationInfo(schema.relations, collection, field);
|
|
4
|
+
if (!relation)
|
|
5
|
+
return null;
|
|
6
|
+
const isO2m = relation.related_collection === collection;
|
|
7
|
+
const relatedCollectionName = isO2m ? relation.collection : relation.related_collection;
|
|
8
|
+
return relatedCollectionName;
|
|
9
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export function flattenFilter(paths, filter) {
|
|
2
|
+
if (!filter)
|
|
3
|
+
return;
|
|
4
|
+
const stack = [{ current: filter, path: [] }];
|
|
5
|
+
while (stack.length > 0) {
|
|
6
|
+
const { current, path } = stack.pop();
|
|
7
|
+
if (typeof current === 'object' && current !== null) {
|
|
8
|
+
// If the current nested value is an array, we ignore the array order and flatten all
|
|
9
|
+
// nested objects
|
|
10
|
+
const isArray = Array.isArray(current);
|
|
11
|
+
for (const key in current) {
|
|
12
|
+
if (!key.startsWith('_') || key === '_and' || key === '_or' || key === '_some' || key === '_none') {
|
|
13
|
+
// Only deepen the path if the current value can contain more keys
|
|
14
|
+
stack.push({
|
|
15
|
+
current: current[key],
|
|
16
|
+
path: isArray ? path : [...path, key],
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
// Ignore all operators and logical grouping in the field paths
|
|
21
|
+
const parts = path.filter((part) => part.startsWith('_') === false);
|
|
22
|
+
if (parts.length > 0)
|
|
23
|
+
paths.push(parts);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
// Ignore all operators and logical grouping in the field paths
|
|
29
|
+
const parts = path.filter((part) => part.startsWith('_') === false);
|
|
30
|
+
if (parts.length > 0)
|
|
31
|
+
paths.push(parts);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function formatA2oKey(fieldKey: string, collection: string): string;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type DirectusError } from '@directus/errors';
|
|
2
|
+
export declare function createCollectionForbiddenError(path: string, collection: string): DirectusError<any>;
|
|
3
|
+
export declare function createFieldsForbiddenError(path: string, collection: string, fields: string[]): DirectusError<any>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ForbiddenError } from '@directus/errors';
|
|
2
|
+
export function createCollectionForbiddenError(path, collection) {
|
|
3
|
+
const pathSuffix = path === '' ? 'root' : `"${path}"`;
|
|
4
|
+
return new ForbiddenError({
|
|
5
|
+
reason: `You don't have permission to access collection "${collection}" or it does not exist. Queried in ${pathSuffix}.`,
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
export function createFieldsForbiddenError(path, collection, fields) {
|
|
9
|
+
const pathSuffix = path === '' ? 'root' : `"${path}"`;
|
|
10
|
+
const fieldStr = fields.map((field) => `"${field}"`).join(', ');
|
|
11
|
+
return new ForbiddenError({
|
|
12
|
+
reason: fields.length === 1
|
|
13
|
+
? `You don't have permission to access field ${fieldStr} in collection "${collection}" or it does not exist. Queried in ${pathSuffix}.`
|
|
14
|
+
: `You don't have permission to access fields ${fieldStr} in collection "${collection}" or they do not exist. Queried in ${pathSuffix}.`,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createCollectionForbiddenError, createFieldsForbiddenError } from './create-error.js';
|
|
2
|
+
export function validatePathExistence(path, collection, fields, schema) {
|
|
3
|
+
const collectionInfo = schema.collections[collection];
|
|
4
|
+
if (collectionInfo === undefined) {
|
|
5
|
+
throw createCollectionForbiddenError(path, collection);
|
|
6
|
+
}
|
|
7
|
+
const requestedFields = Array.from(fields);
|
|
8
|
+
const nonExistentFields = requestedFields.filter((field) => collectionInfo.fields[field] === undefined);
|
|
9
|
+
if (nonExistentFields.length > 0) {
|
|
10
|
+
throw createFieldsForbiddenError(path, collection, nonExistentFields);
|
|
11
|
+
}
|
|
12
|
+
}
|