Only return fields you're allowed to read

This commit is contained in:
rijkvanzanten
2020-08-28 16:12:32 -04:00
parent baaaa50049
commit 336cd65646
2 changed files with 53 additions and 17 deletions

View File

@@ -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> & { 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);

View File

@@ -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<string, string[]> = {};
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('*')