diff --git a/src/services/items.ts b/src/services/items.ts index efc1e1e0f4..320457de92 100644 --- a/src/services/items.ts +++ b/src/services/items.ts @@ -44,15 +44,24 @@ export const createItem = async ( data: Record, accountability?: Accountability ): Promise => { - let payload = await PayloadService.processValues('create', collection, data); + let payload = data; + + if (accountability) { + payload = await PermissionsService.processValues( + 'create', + collection, + accountability?.role, + data + ); + } + + payload = await PayloadService.processValues('create', collection, payload); payload = await PayloadService.processM2O(collection, payload); const primaryKeyField = await schemaInspector.primary(collection); - // Only insert the values that actually save to an existing column. This ensures we ignore aliases etc const columns = await schemaInspector.columns(collection); - const payloadWithoutAlias = pick( payload, columns.map(({ column }) => column) @@ -130,7 +139,18 @@ export const updateItem = async ( data: Record, accountability?: Accountability ): Promise => { - let payload = await PayloadService.processValues('update', collection, data); + let payload = data; + + if (accountability) { + payload = await PermissionsService.processValues( + 'validate', + collection, + accountability?.role, + data + ); + } + + payload = await PayloadService.processValues('update', collection, data); payload = await PayloadService.processM2O(collection, payload); diff --git a/src/services/permissions.ts b/src/services/permissions.ts index d769c1eb01..b6891ef442 100644 --- a/src/services/permissions.ts +++ b/src/services/permissions.ts @@ -1,8 +1,18 @@ -import { Accountability, AST, NestedCollectionAST, FieldAST, Query, Permission } from '../types'; +import { + Accountability, + AST, + NestedCollectionAST, + FieldAST, + Query, + Permission, + Operation, +} from '../types'; import * as ItemsService from './items'; import database from '../database'; -import { ForbiddenException } from '../exceptions'; +import { ForbiddenException, InvalidPayloadException } from '../exceptions'; import { uniq } from 'lodash'; +import generateJoi from '../utils/generate-joi'; +import Joi from '@hapi/joi'; export const createPermission = async ( data: Record, @@ -165,3 +175,48 @@ export const processAST = async (ast: AST, role: string | null): Promise => return ast; } }; + +export const processValues = async ( + operation: Operation, + collection: string, + role: string | null, + data: Record +) => { + const permission = await database + .select('*') + .from('directus_permissions') + .where({ operation, collection, role }) + .first(); + + if (!permission) throw new ForbiddenException(); + + const allowedFields = permission.fields.split(','); + + if (allowedFields.includes('*') === false) { + const keysInData = Object.keys(data); + const invalidKeys = keysInData.filter( + (fieldKey) => allowedFields.includes(fieldKey) === false + ); + + if (invalidKeys.length > 0) { + throw new InvalidPayloadException(`Field "${invalidKeys[0]}" doesn't exist.`); + } + } + + const preset = permission.presets || {}; + + const payload = { + ...preset, + ...data, + }; + + const schema = generateJoi(permission.permissions); + + const { error } = schema.validate(payload); + + if (error) { + throw new InvalidPayloadException(error.message); + } + + return payload; +}; diff --git a/src/utils/generate-joi.ts b/src/utils/generate-joi.ts new file mode 100644 index 0000000000..f76ad3ac2b --- /dev/null +++ b/src/utils/generate-joi.ts @@ -0,0 +1,44 @@ +import { Filter } from '../types'; +import Joi, { AnySchema } from '@hapi/joi'; + +export default function generateJoi(filter: Filter) { + const schema: Record = {}; + + for (const [key, value] of Object.entries(filter)) { + const isField = key.startsWith('_') === false; + + if (isField) { + const operator = Object.keys(value)[0]; + + /** @TODO + * - Extend with all operators + */ + + if (operator === '_eq') { + schema[key] = Joi.any().equal(Object.values(value)[0]); + } + + if (operator === '_neq') { + schema[key] = Joi.any().not(Object.values(value)[0]); + } + + if (operator === '_in') { + schema[key] = Joi.any().equal(...(Object.values(value)[0] as (string | number)[])); + } + + if (operator === '_nin') { + schema[key] = Joi.any().not(...(Object.values(value)[0] as (string | number)[])); + } + + if (operator === '_gt') { + schema[key] = Joi.number().greater(Number(Object.values(value)[0])); + } + + if (operator === '_lt') { + schema[key] = Joi.number().less(Number(Object.values(value)[0])); + } + } + } + + return Joi.object(schema).unknown(); +}