Files
directus/api/src/services/relations.ts
Rijk van Zanten b7d87e581a System permissions for app access (#4004)
* Pass relations through schema, instead of individual reads

* Fetch field transforms upfront

* Fix length check

* List if user has app access or not in accountability

* Load permissions up front, merge app access minimal permissions

* Show app access required permissions in permissions overview

* Show system minimal permissions in permissions detail

* Fix app access check in authenticate for jwt use

* Fix minimal permissions for presets

* Remove /permissions/me in favor of root use w/ permissions

* Fix logical nested OR in an AND

* Use root permissions endpoint with filter instead of /me

* Allow filter query on /permissions

* Add system minimal app access permissions into result of /permissions

* Remove stray console log

* Remove stray console.dir

* Set current role as role for minimal permissions

* Fix no-permissions state for user detail

* Add filter items function that allows altering existing result set
2021-02-11 12:50:56 -05:00

115 lines
3.6 KiB
TypeScript

import { ItemsService } from './items';
import { AbstractServiceOptions, Query, PrimaryKey, PermissionsAction, Relation } from '../types';
import { PermissionsService } from './permissions';
import { toArray } from '../utils/to-array';
import { systemRelationRows } from '../database/system-data/relations';
/**
* @TODO update foreign key constraints when relations are updated
*/
type ParsedRelation = Relation & {
one_allowed_collections: string[] | null;
};
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 | Relation | Relation[]> {
const service = new ItemsService('directus_relations', {
knex: this.knex,
schema: this.schema,
});
const results = (await service.readByQuery(query)) as ParsedRelation | ParsedRelation[] | null;
if (results && Array.isArray(results)) {
results.push(...(systemRelationRows as ParsedRelation[]));
}
const filteredResults = await this.filterForbidden(results);
return filteredResults;
}
readByKey(keys: PrimaryKey[], query?: Query, action?: PermissionsAction): Promise<null | Relation[]>;
readByKey(key: PrimaryKey, query?: Query, action?: PermissionsAction): Promise<null | Relation>;
async readByKey(
key: PrimaryKey | PrimaryKey[],
query: Query = {},
action: PermissionsAction = 'read'
): Promise<null | Relation | Relation[]> {
const service = new ItemsService('directus_relations', {
knex: this.knex,
schema: this.schema,
});
const results = (await service.readByKey(key as any, query, action)) as ParsedRelation | ParsedRelation[] | null;
// No need to merge system relations here. They don't have PKs so can never be directly
// targetted
const filteredResults = await this.filterForbidden(results);
return filteredResults;
}
private async filterForbidden(relations: ParsedRelation | ParsedRelation[] | null) {
if (relations === null) return null;
if (this.accountability === null || this.accountability?.admin === true) return relations;
const allowedCollections = this.schema.permissions
.filter((permission) => {
return permission.action === 'read';
})
.map(({ collection }) => collection);
const allowedFields = this.permissionsService.getAllowedFields('read');
relations = toArray(relations);
return relations.filter((relation) => {
let collectionsAllowed = true;
let fieldsAllowed = true;
if (allowedCollections.includes(relation.many_collection) === false) {
collectionsAllowed = false;
}
if (relation.one_collection && allowedCollections.includes(relation.one_collection) === false) {
collectionsAllowed = false;
}
if (
relation.one_allowed_collections &&
relation.one_allowed_collections.every((collection) => allowedCollections.includes(collection)) === false
) {
collectionsAllowed = false;
}
if (
!allowedFields[relation.many_collection] ||
(allowedFields[relation.many_collection].includes('*') === false &&
allowedFields[relation.many_collection].includes(relation.many_field) === false)
) {
fieldsAllowed = false;
}
if (
relation.one_collection &&
relation.one_field &&
(!allowedFields[relation.one_collection] ||
(allowedFields[relation.one_collection].includes('*') === false &&
allowedFields[relation.one_collection].includes(relation.one_field) === false))
) {
fieldsAllowed = false;
}
return collectionsAllowed && fieldsAllowed;
});
}
}