@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
88 lines (87 loc) • 4.46 kB
JavaScript
import { InvalidPayloadError } from '@directus/errors';
import { UserIntegrityCheckFlag } from '@directus/types';
import { clearSystemCache } from '../cache.js';
import { fetchRolesTree } from '../permissions/lib/fetch-roles-tree.js';
import { transaction } from '../utils/transaction.js';
import { AccessService } from './access.js';
import { ItemsService } from './items.js';
import { PresetsService } from './presets.js';
import { UsersService } from './users.js';
export class RolesService extends ItemsService {
constructor(options) {
super('directus_roles', options);
}
// No need to check user integrity in createOne, as the creation of a role itself does not influence the number of
// users, as the role of a user is actually updated in the UsersService on the user, which will make sure to
// initiate a user integrity check if necessary. Same goes for role nesting check as well as cache clearing.
async updateMany(keys, data, opts = {}) {
if ('parent' in data) {
// If the parent of a role changed we need to make a full integrity check.
// Anything related to policies will be checked in the AccessService, where the policies are attached to roles
opts.userIntegrityCheckFlags = UserIntegrityCheckFlag.All;
opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
await this.validateRoleNesting(keys, data['parent']);
}
const result = await super.updateMany(keys, data, opts);
// Only clear the permissions cache if the parent role has changed
// If anything policies related has changed, the cache will be cleared in the AccessService as well
if ('parent' in data) {
await this.clearCaches();
}
return result;
}
async deleteMany(keys, opts = {}) {
opts.userIntegrityCheckFlags = UserIntegrityCheckFlag.All;
opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
await transaction(this.knex, async (trx) => {
const options = {
knex: trx,
accountability: this.accountability,
schema: this.schema,
};
const rolesItemsService = new ItemsService('directus_roles', options);
const rolesService = new RolesService(options);
const accessService = new AccessService(options);
const presetsService = new PresetsService(options);
const usersService = new UsersService(options);
// Delete permissions/presets for this role, suspend all remaining users in role
await accessService.deleteByQuery({
filter: { role: { _in: keys } },
}, { ...opts, bypassLimits: true });
await presetsService.deleteByQuery({
filter: { role: { _in: keys } },
}, { ...opts, bypassLimits: true });
await usersService.updateByQuery({
filter: { role: { _in: keys } },
}, {
status: 'suspended',
role: null,
}, { ...opts, bypassLimits: true });
// If the about to be deleted roles are the parent of other roles set those parents to null
// Use a newly created RolesService here that works within the current transaction
await rolesService.updateByQuery({
filter: { parent: { _in: keys } },
}, { parent: null });
await rolesItemsService.deleteMany(keys, opts);
});
// Since nested roles could be updated, clear caches
await this.clearCaches();
return keys;
}
async validateRoleNesting(ids, parent) {
if (ids.includes(parent)) {
throw new InvalidPayloadError({ reason: 'A role cannot be a parent of itself' });
}
const roles = await fetchRolesTree(parent, { knex: this.knex });
if (ids.some((id) => roles.includes(id))) {
// The role tree up from the parent already includes this role, so it would create a circular reference
throw new InvalidPayloadError({ reason: 'A role cannot have a parent that is already a descendant of itself' });
}
}
async clearCaches(opts) {
await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache });
if (this.cache && opts?.autoPurgeCache !== false) {
await this.cache.clear();
}
}
}