Merge pull request #549 from directus/relation-permissions

Only return relations for collections/fields you have access to read
This commit is contained in:
Rijk van Zanten
2020-10-06 14:45:03 -04:00
committed by GitHub
2 changed files with 102 additions and 2 deletions

View File

@@ -1,8 +1,40 @@
import { AbstractServiceOptions } from '../types';
import { AbstractServiceOptions, PermissionsAction } from '../types';
import { ItemsService } from '../services/items';
export class PermissionsService extends ItemsService {
constructor(options?: AbstractServiceOptions) {
super('directus_permissions', options);
}
async getAllowedCollections(role: string | null, action: PermissionsAction) {
const query = this.knex
.select('collection')
.from('directus_permissions')
.where({ role, action });
const results = await query;
return results.map((result) => result.collection);
}
async getAllowedFields(role: string | null, action: PermissionsAction, collection?: string) {
const query = this.knex
.select('collection', 'fields')
.from('directus_permissions')
.where({ role, action });
if (collection) {
query.andWhere({ collection });
}
const results = await query;
const fieldsPerCollection: Record<string, string[]> = {};
for (const result of results) {
const { collection, fields } = result;
if (!fieldsPerCollection[collection]) fieldsPerCollection[collection] = [];
fieldsPerCollection[collection].push(...(fields || '').split(','));
}
return fieldsPerCollection;
}
}

View File

@@ -1,12 +1,80 @@
import { ItemsService } from './items';
import { AbstractServiceOptions } from '../types';
import {
AbstractServiceOptions,
Query,
Item,
PrimaryKey,
PermissionsAction,
Relation,
} from '../types';
import { PermissionsService } from './permissions';
/**
* @TODO update foreign key constraints when relations are updated
*/
export class RelationsService extends ItemsService {
permissionsService: PermissionsService;
constructor(options?: AbstractServiceOptions) {
super('directus_relations', options);
this.permissionsService = new PermissionsService(options);
}
async readByQuery(query: Query): Promise<null | Item | Item[]> {
const results = (await super.readByQuery(query)) as Relation | Relation[] | null;
const filteredResults = await this.filterForbidden(results);
return filteredResults;
}
readByKey(
keys: PrimaryKey[],
query?: Query,
action?: PermissionsAction
): Promise<null | Item[]>;
readByKey(key: PrimaryKey, query?: Query, action?: PermissionsAction): Promise<null | Item>;
async readByKey(
key: PrimaryKey | PrimaryKey[],
query: Query = {},
action: PermissionsAction = 'read'
): Promise<null | Item | Item[]> {
const results = (await super.readByKey(key as any, query, action)) as
| Relation
| Relation[]
| null;
const filteredResults = await this.filterForbidden(results);
return filteredResults;
}
private async filterForbidden(relations: Relation | Relation[] | null) {
if (relations === null) return null;
if (this.accountability === null || this.accountability?.admin === true) return relations;
const allowedCollections = await this.permissionsService.getAllowedCollections(
this.accountability?.role || null,
'read'
);
const allowedFields = await this.permissionsService.getAllowedFields(
this.accountability?.role || null,
'read'
);
relations = Array.isArray(relations) ? relations : [relations];
return relations.filter((relation) => {
const collectionsAllowed =
allowedCollections.includes(relation.many_collection) &&
allowedCollections.includes(relation.one_collection);
const fieldsAllowed =
allowedFields[relation.one_collection] &&
allowedFields[relation.many_collection] &&
(allowedFields[relation.many_collection].includes('*') ||
allowedFields[relation.many_collection].includes(relation.many_field)) &&
(allowedFields[relation.one_collection].includes('*') ||
allowedFields[relation.one_collection].includes(relation.one_field));
return collectionsAllowed && fieldsAllowed;
});
}
}