fix(permissions): circular dependency (#18986)

* fix(permissions): circular dependency

* Apply suggestions from code review

Co-authored-by: Ben Irvin <ben@innerdvations.com>

* Apply suggestions from code review

Co-authored-by: Jean-Sébastien Herbaux <jean-sebastien.herbaux@epitech.eu>

---------

Co-authored-by: Ben Irvin <ben@innerdvations.com>
Co-authored-by: Jean-Sébastien Herbaux <jean-sebastien.herbaux@epitech.eu>
This commit is contained in:
Josh 2023-12-06 08:47:11 +00:00 committed by GitHub
parent 295178369f
commit e85a23d937
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 39 additions and 31 deletions

View File

@ -2,12 +2,11 @@ import * as sift from 'sift';
import qs from 'qs'; import qs from 'qs';
import { AbilityBuilder, Ability } from '@casl/ability'; import { AbilityBuilder, Ability } from '@casl/ability';
import { pick, isNil, isObject } from 'lodash/fp'; import { pick, isNil, isObject } from 'lodash/fp';
// eslint-disable-next-line import/no-extraneous-dependencies, node/no-extraneous-import import type { ParametrizedAction, PermissionRule } from '../../types';
import { Permissions as PermissionsTypes } from '@strapi/types';
export interface CustomAbilityBuilder { export interface CustomAbilityBuilder {
can(permission: PermissionsTypes.PermissionRule): ReturnType<AbilityBuilder<Ability>['can']>; can(permission: PermissionRule): ReturnType<AbilityBuilder<Ability>['can']>;
buildParametrizedAction: (parametrizedAction: PermissionsTypes.ParametrizedAction) => string; buildParametrizedAction: (parametrizedAction: ParametrizedAction) => string;
build(): Ability; build(): Ability;
} }
@ -32,7 +31,7 @@ const conditionsMatcher = (conditions: unknown) => {
return sift.createQueryTester(conditions, { operations }); return sift.createQueryTester(conditions, { operations });
}; };
const buildParametrizedAction = ({ name, params }: PermissionsTypes.ParametrizedAction) => { const buildParametrizedAction = ({ name, params }: ParametrizedAction) => {
return `${name}?${qs.stringify(params)}`; return `${name}?${qs.stringify(params)}`;
}; };
@ -43,7 +42,7 @@ export const caslAbilityBuilder = (): CustomAbilityBuilder => {
const { can, build, ...rest } = new AbilityBuilder(Ability); const { can, build, ...rest } = new AbilityBuilder(Ability);
return { return {
can(permission: PermissionsTypes.PermissionRule) { can(permission: PermissionRule) {
const { action, subject, properties = {}, condition } = permission; const { action, subject, properties = {}, condition } = permission;
const { fields } = properties; const { fields } = properties;
@ -57,7 +56,7 @@ export const caslAbilityBuilder = (): CustomAbilityBuilder => {
); );
}, },
buildParametrizedAction({ name, params }: PermissionsTypes.ParametrizedAction) { buildParametrizedAction({ name, params }: ParametrizedAction) {
return `${name}?${qs.stringify(params)}`; return `${name}?${qs.stringify(params)}`;
}, },

View File

@ -1,10 +1,9 @@
import { cloneDeep, has, isArray } from 'lodash/fp'; import { cloneDeep, has, isArray } from 'lodash/fp';
import { hooks } from '@strapi/utils'; import { hooks } from '@strapi/utils';
// eslint-disable-next-line node/no-extraneous-import
import type { Permissions as PermissionsTypes } from '@strapi/types';
import * as domain from '../domain'; import * as domain from '../domain';
import type { Permission } from '../domain/permission'; import type { Permission } from '../domain/permission';
import type { PermissionRule } from '../types';
export interface PermissionEngineHooks { export interface PermissionEngineHooks {
'before-format::validate.permission': ReturnType<typeof hooks.createAsyncBailHook>; 'before-format::validate.permission': ReturnType<typeof hooks.createAsyncBailHook>;
@ -52,7 +51,7 @@ const createBeforeEvaluateContext = (permission: Permission) => ({
}); });
interface WillRegisterContextParams { interface WillRegisterContextParams {
permission: PermissionsTypes.PermissionRule; permission: PermissionRule;
options: Record<string, unknown>; options: Record<string, unknown>;
} }

View File

@ -2,8 +2,6 @@ import _ from 'lodash/fp';
import qs from 'qs'; import qs from 'qs';
import { Ability } from '@casl/ability'; import { Ability } from '@casl/ability';
import { providerFactory } from '@strapi/utils'; import { providerFactory } from '@strapi/utils';
// eslint-disable-next-line import/no-extraneous-dependencies, node/no-extraneous-import
import { Permissions as PermissionsTypes } from '@strapi/types';
import { import {
createEngineHooks, createEngineHooks,
@ -15,6 +13,7 @@ import type { PermissionEngineHooks, HookName } from './hooks';
import * as abilities from './abilities'; import * as abilities from './abilities';
import { Permission } from '../domain/permission'; import { Permission } from '../domain/permission';
import type { PermissionRule } from '../types';
export { abilities }; export { abilities };
@ -30,9 +29,9 @@ export interface Engine {
on(hook: HookName, handler: (...args: any[]) => any): Engine; on(hook: HookName, handler: (...args: any[]) => any): Engine;
generateAbility(permissions: Permission[], options?: object): Promise<Ability>; generateAbility(permissions: Permission[], options?: object): Promise<Ability>;
createRegisterFunction( createRegisterFunction(
can: (permission: PermissionsTypes.PermissionRule) => unknown, can: (permission: PermissionRule) => unknown,
options: Record<string, unknown> options: Record<string, unknown>
): (permission: PermissionsTypes.PermissionRule) => Promise<unknown>; ): (permission: PermissionRule) => Promise<unknown>;
} }
export interface EngineParams { export interface EngineParams {
@ -42,7 +41,7 @@ export interface EngineParams {
interface EvaluateParams { interface EvaluateParams {
options: Record<string, unknown>; options: Record<string, unknown>;
register: (permission: PermissionsTypes.PermissionRule) => Promise<unknown>; register: (permission: PermissionRule) => Promise<unknown>;
permission: Permission; permission: Permission;
} }
@ -179,7 +178,7 @@ const newEngine = (params: EngineParams): Engine => {
* used to register a permission in the ability builder * used to register a permission in the ability builder
*/ */
createRegisterFunction(can, options: Record<string, unknown>) { createRegisterFunction(can, options: Record<string, unknown>) {
return async (permission: PermissionsTypes.PermissionRule) => { return async (permission: PermissionRule) => {
const hookContext = createWillRegisterContext({ options, permission }); const hookContext = createWillRegisterContext({ options, permission });
await state.hooks['before-register.permission'].call(hookContext); await state.hooks['before-register.permission'].call(hookContext);

View File

@ -0,0 +1,19 @@
/**
* These were imported from `@strapi/types` but if we do that
* it becomes a circular dependency. This is the source of truth,
* they're re-exported from `@strapi/types` for convenience.
*/
import type { Subject } from '@casl/ability';
export interface ParametrizedAction {
name: string;
params: Record<string, unknown>;
}
export interface PermissionRule {
action: string | ParametrizedAction;
subject?: Subject | null;
properties?: {
fields?: string[];
};
condition?: Record<string, unknown>;
}

View File

@ -50,11 +50,9 @@ export {
}; };
declare global { declare global {
// @ts-expect-error - global strapi variable is also defined in the index.d.ts file
var strapi: LoadedStrapi; var strapi: LoadedStrapi;
namespace NodeJS { namespace NodeJS {
interface Global { interface Global {
// @ts-expect-error - global strapi variable is also defined in the index.d.ts file
strapi: LoadedStrapi; strapi: LoadedStrapi;
} }
} }

View File

@ -1,14 +1,8 @@
import { Subject } from '@casl/ability'; import type { engine } from '@strapi/permissions';
export interface ParametrizedAction { type PermissionRule = Parameters<engine.abilities.CustomAbilityBuilder['can']>[0];
name: string; type ParametrizedAction = Parameters<
params: Record<string, unknown>; engine.abilities.CustomAbilityBuilder['buildParametrizedAction']
} >[0];
export interface PermissionRule {
action: string | ParametrizedAction; export type { PermissionRule, ParametrizedAction };
subject?: Subject | null;
properties?: {
fields?: string[];
};
condition?: Record<string, unknown>;
}