diff --git a/api/src/controllers/fields.ts b/api/src/controllers/fields.ts index 03aaab008c..d579d354d1 100644 --- a/api/src/controllers/fields.ts +++ b/api/src/controllers/fields.ts @@ -3,7 +3,7 @@ import asyncHandler from 'express-async-handler'; import FieldsService from '../services/fields'; import validateCollection from '../middleware/collection-exists'; import { schemaInspector } from '../database'; -import { FieldNotFoundException, InvalidPayloadException } from '../exceptions'; +import { FieldNotFoundException, InvalidPayloadException, ForbiddenException } from '../exceptions'; import Joi from 'joi'; import { Field } from '../types/field'; import useCollection from '../middleware/use-collection'; @@ -48,7 +48,7 @@ router.get( const service = new FieldsService({ accountability: req.accountability }); const exists = await schemaInspector.hasColumn(req.collection, req.params.field); - if (exists === false) throw new FieldNotFoundException(req.collection, req.params.field); + if (exists === false) throw new ForbiddenException(); const field = await service.readOne(req.params.collection, req.params.field); return res.json({ data: field || null }); @@ -122,8 +122,10 @@ router.patch( useCollection('directus_fields'), // @todo: validate field asyncHandler(async (req, res) => { - const service = new FieldsService({ accountability: req.accountability }); + const exists = await schemaInspector.hasColumn(req.collection, req.params.field); + if (exists === false) throw new ForbiddenException(); + const service = new FieldsService({ accountability: req.accountability }); const fieldData: Partial & { field: string; type: typeof types[number] } = req.body; if (!fieldData.field) fieldData.field = req.params.field; @@ -141,6 +143,9 @@ router.delete( validateCollection, useCollection('directus_fields'), asyncHandler(async (req, res) => { + const exists = await schemaInspector.hasColumn(req.collection, req.params.field); + if (exists === false) throw new ForbiddenException(); + const service = new FieldsService({ accountability: req.accountability }); await service.deleteField(req.params.collection, req.params.field, req.accountability); diff --git a/api/src/services/fields.ts b/api/src/services/fields.ts index aa0110f3be..0665ff2397 100644 --- a/api/src/services/fields.ts +++ b/api/src/services/fields.ts @@ -6,7 +6,7 @@ import ItemsService from '../services/items'; import { ColumnBuilder } from 'knex'; import getLocalType from '../utils/get-local-type'; import { types } from '../types'; -import { FieldNotFoundException } from '../exceptions'; +import { FieldNotFoundException, ForbiddenException } from '../exceptions'; import Knex, { CreateTableBuilder } from 'knex'; import PayloadService from '../services/payload'; import getDefaultValue from '../utils/get-default-value'; @@ -34,24 +34,16 @@ export default class FieldsService { this.payloadService = new PayloadService('directus_fields'); } - async fieldsInCollection(collection: string) { - const [fields, columns] = await Promise.all([ - this.itemsService.readByQuery({ filter: { collection: { _eq: collection } } }), - schemaInspector.columns(collection), - ]); - - return uniq([...fields.map(({ field }) => field), ...columns.map(({ column }) => column)]); - } - async readAll(collection?: string) { let fields: FieldMeta[]; + const nonAuthorizedItemsService = new ItemsService('directus_fields', { knex: this.knex }); if (collection) { - fields = (await this.itemsService.readByQuery({ + fields = (await nonAuthorizedItemsService.readByQuery({ filter: { collection: { _eq: collection } }, })) as FieldMeta[]; } else { - fields = (await this.itemsService.readByQuery({})) as FieldMeta[]; + fields = (await nonAuthorizedItemsService.readByQuery({})) as FieldMeta[]; } fields = (await this.payloadService.processValues('read', fields)) as FieldMeta[]; @@ -106,11 +98,50 @@ export default class FieldsService { return data; }); - return [...columnsWithSystem, ...aliasFieldsAsField]; + const result = [...columnsWithSystem, ...aliasFieldsAsField]; + + // Filter the result so we only return the fields you have read access to + if (this.accountability) { + const permissions = await this.knex.select('collection', 'fields').from('directus_permissions').where({ role: this.accountability.role, action: 'read' }); + const allowedFieldsInCollection: Record = {}; + + permissions.forEach((permission) => { + allowedFieldsInCollection[permission.collection] = permission.fields.split(','); + }); + + if (collection && allowedFieldsInCollection.hasOwnProperty(collection) === false) { + throw new ForbiddenException(); + } + + return result.filter((field) => { + if (allowedFieldsInCollection.hasOwnProperty(field.collection) === false) return false; + const allowedFields = allowedFieldsInCollection[field.collection]; + if (allowedFields[0] === '*') return true; + return allowedFields.includes(field.field); + }); + } + + return result; } - /** @todo add accountability */ async readOne(collection: string, field: string) { + if (this.accountability) { + const permissions = await this.knex + .select('fields') + .from('directus_permissions') + .where({ + role: this.accountability.role, + collection, + action: 'read' + }).first(); + + if (!permissions) throw new ForbiddenException(); + if (permissions.fields !== '*') { + const allowedFields = permissions.fields.split(','); + if (allowedFields.includes(field) === false) throw new ForbiddenException(); + } + } + let column; let fieldInfo = await this.knex .select('*')