UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

156 lines (154 loc) 5.63 kB
import { clearSystemCache } from "../cache.js"; import { withAppMinimalPermissions } from "../permissions/lib/with-app-minimal-permissions.js"; import { fetchPolicies } from "../permissions/lib/fetch-policies.js"; import { fetchPermissions } from "../permissions/lib/fetch-permissions.js"; import { validateAccess } from "../permissions/modules/validate-access/validate-access.js"; import { ItemsService } from "./items.js"; import { hasCustomRule, isRecommendedAppPermission } from "../license/entitlements/lib/custom-permission-rules-enabled.js"; import { getEntitlementManager } from "../license/entitlements/manager.js"; import "../license/index.js"; import { ForbiddenError, ResourceRestrictedError } from "@directus/errors"; import { omit, uniq } from "lodash-es"; //#region src/services/permissions.ts var PermissionsService = class extends ItemsService { constructor(options) { super("directus_permissions", options); } async clearCaches(opts) { await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache, autoPurgeSchema: false }); await getEntitlementManager().clearCache("custom_permission_rules_enabled"); if (this.cache && opts?.autoPurgeCache !== false) await this.cache.clear(); } async readByQuery(query, opts) { if (getEntitlementManager().isEntitled("custom_permission_rules_enabled")) { const result = await super.readByQuery(query, opts); return withAppMinimalPermissions(this.accountability, result, query.filter); } const requiredFields = [ "fields", "permissions", "validation", "presets" ]; let extraFields = []; if (query.fields && !query.fields.includes("*")) { extraFields = requiredFields.filter((f) => !query.fields?.includes(f)); query.fields = [...query.fields, ...extraFields]; } const filteredPermissions = (await super.readByQuery(query, opts)).filter((p) => !hasCustomRule(p) || isRecommendedAppPermission(p)); const mappedPermissions = extraFields.length > 0 ? filteredPermissions.map((p) => omit(p, extraFields)) : filteredPermissions; return withAppMinimalPermissions(this.accountability, mappedPermissions, query.filter); } async createOne(data, opts) { if (!getEntitlementManager().isEntitled("custom_permission_rules_enabled") && hasCustomRule(data) && !isRecommendedAppPermission(data)) throw new ResourceRestrictedError({ category: "custom_permission_rules_enabled" }); const res = await super.createOne(data, opts); await this.clearCaches(opts); return res; } async updateMany(keys, data, opts) { if (!getEntitlementManager().isEntitled("custom_permission_rules_enabled") && hasCustomRule(data) && !isRecommendedAppPermission(data)) throw new ResourceRestrictedError({ category: "custom_permission_rules_enabled" }); const res = await super.updateMany(keys, data, opts); await this.clearCaches(opts); return res; } async createMany(data, opts) { const res = await super.createMany(data, opts); await this.clearCaches(opts); return res; } async updateBatch(data, opts) { const res = await super.updateBatch(data, opts); await this.clearCaches(opts); return res; } async upsertMany(payloads, opts) { const res = await super.upsertMany(payloads, opts); await this.clearCaches(opts); return res; } async deleteMany(keys, opts) { const res = await super.deleteMany(keys, opts); await this.clearCaches(opts); return res; } async getItemPermissions(collection, primaryKey) { if (!this.accountability?.user) throw new ForbiddenError(); if (this.accountability?.admin) return { update: { access: true }, delete: { access: true }, share: { access: true } }; const itemPermissions = { update: { access: false }, delete: { access: false }, share: { access: false } }; let updateAction = "update"; const schema = this.schema.collections[collection]; if (schema?.singleton) { const itemsService = new ItemsService(collection, { knex: this.knex, schema: this.schema }); const query = { fields: [schema.primary], limit: 1 }; try { if (!(await itemsService.readByQuery(query))[0]) updateAction = "create"; } catch { updateAction = "create"; } } await Promise.all(Object.keys(itemPermissions).map((key) => { const action = key; const checkAction = action === "update" ? updateAction : action; if (!this.accountability) { itemPermissions[action].access = true; return Promise.resolve(); } const opts = { accountability: this.accountability, action: checkAction, collection }; if (primaryKey) opts.primaryKeys = [primaryKey]; return validateAccess(opts, { schema: this.schema, knex: this.knex }).then(() => itemPermissions[action].access = true).catch(() => {}); })); if (schema?.singleton && itemPermissions.update.access) { const context = { schema: this.schema, knex: this.knex }; const permissions = await fetchPermissions({ policies: await fetchPolicies(this.accountability, context), accountability: this.accountability, action: updateAction, collections: [collection] }, context); let fields = []; let presets = {}; for (const permission of permissions) { if (permission.fields && fields[0] !== "*") { fields = uniq([...fields, ...permission.fields]); if (fields.includes("*")) fields = ["*"]; } if (permission.presets) presets = { ...presets ?? {}, ...permission.presets }; } itemPermissions.update.fields = fields; itemPermissions.update.presets = presets; } return itemPermissions; } }; //#endregion export { PermissionsService };