mirror of
https://github.com/directus/directus.git
synced 2026-02-02 05:45:17 -05:00
* 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
115 lines
3.6 KiB
TypeScript
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;
|
|
});
|
|
}
|
|
}
|