@strapi/permissions 5.9.0 → 5.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,275 +1,323 @@
1
- import _, { cloneDeep, isArray, has, pick, isNil, isObject } from "lodash/fp";
2
- import qs from "qs";
3
- import { hooks } from "@strapi/utils";
4
- import * as sift from "sift";
5
- import { AbilityBuilder, Ability } from "@casl/ability";
6
- const PERMISSION_FIELDS = ["action", "subject", "properties", "conditions"];
1
+ import _, { cloneDeep, isArray, has, pick, isNil, isObject } from 'lodash/fp';
2
+ import qs from 'qs';
3
+ import { hooks } from '@strapi/utils';
4
+ import * as sift from 'sift';
5
+ import { AbilityBuilder, Ability } from '@casl/ability';
6
+
7
+ const PERMISSION_FIELDS = [
8
+ 'action',
9
+ 'subject',
10
+ 'properties',
11
+ 'conditions'
12
+ ];
7
13
  const sanitizePermissionFields = _.pick(PERMISSION_FIELDS);
8
- const getDefaultPermission = () => ({
9
- conditions: [],
10
- properties: {},
11
- subject: null
12
- });
13
- const create = _.pipe(_.pick(PERMISSION_FIELDS), _.merge(getDefaultPermission()));
14
- const addCondition = _.curry((condition, permission) => {
15
- const { conditions } = permission;
16
- const newConditions = Array.isArray(conditions) ? _.uniq(conditions.concat(condition)) : [condition];
17
- return _.set("conditions", newConditions, permission);
14
+ /**
15
+ * Creates a permission with default values for optional properties
16
+ */ const getDefaultPermission = ()=>({
17
+ conditions: [],
18
+ properties: {},
19
+ subject: null
20
+ });
21
+ /**
22
+ * Create a new permission based on given attributes
23
+ *
24
+ * @param {object} attributes
25
+ */ const create = _.pipe(_.pick(PERMISSION_FIELDS), _.merge(getDefaultPermission()));
26
+ /**
27
+ * Add a condition to a permission
28
+ */ const addCondition = _.curry((condition, permission)=>{
29
+ const { conditions } = permission;
30
+ const newConditions = Array.isArray(conditions) ? _.uniq(conditions.concat(condition)) : [
31
+ condition
32
+ ];
33
+ return _.set('conditions', newConditions, permission);
18
34
  });
19
- const getProperty = _.curry(
20
- (property, permission) => _.get(`properties.${property}`, permission)
21
- );
22
- const index$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
35
+ /**
36
+ * Gets a property or a part of a property from a permission.
37
+ */ const getProperty = _.curry((property, permission)=>_.get(`properties.${property}`, permission));
38
+
39
+ var index$3 = /*#__PURE__*/Object.freeze({
23
40
  __proto__: null,
24
- addCondition,
25
- create,
26
- getProperty,
27
- sanitizePermissionFields
28
- }, Symbol.toStringTag, { value: "Module" }));
29
- const index$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
41
+ addCondition: addCondition,
42
+ create: create,
43
+ getProperty: getProperty,
44
+ sanitizePermissionFields: sanitizePermissionFields
45
+ });
46
+
47
+ var index$2 = /*#__PURE__*/Object.freeze({
30
48
  __proto__: null,
31
49
  permission: index$3
32
- }, Symbol.toStringTag, { value: "Module" }));
33
- const createEngineHooks = () => ({
34
- "before-format::validate.permission": hooks.createAsyncBailHook(),
35
- "format.permission": hooks.createAsyncSeriesWaterfallHook(),
36
- "after-format::validate.permission": hooks.createAsyncBailHook(),
37
- "before-evaluate.permission": hooks.createAsyncSeriesHook(),
38
- "before-register.permission": hooks.createAsyncSeriesHook()
39
- });
40
- const createValidateContext = (permission) => ({
41
- get permission() {
42
- return cloneDeep(permission);
43
- }
44
50
  });
45
- const createBeforeEvaluateContext = (permission) => ({
46
- get permission() {
47
- return cloneDeep(permission);
48
- },
49
- addCondition(condition) {
50
- Object.assign(permission, addCondition(condition, permission));
51
- return this;
52
- }
53
- });
54
- const createWillRegisterContext = ({ permission, options }) => ({
55
- ...options,
56
- get permission() {
57
- return cloneDeep(permission);
58
- },
59
- condition: {
60
- and(rawConditionObject) {
61
- if (!permission.condition) {
62
- permission.condition = { $and: [] };
63
- }
64
- if (isArray(permission.condition.$and)) {
65
- permission.condition.$and.push(rawConditionObject);
66
- }
67
- return this;
68
- },
69
- or(rawConditionObject) {
70
- if (!permission.condition) {
71
- permission.condition = { $and: [] };
72
- }
73
- if (isArray(permission.condition.$and)) {
74
- const orClause = permission.condition.$and.find(has("$or"));
75
- if (orClause) {
76
- orClause.$or.push(rawConditionObject);
77
- } else {
78
- permission.condition.$and.push({ $or: [rawConditionObject] });
51
+
52
+ /**
53
+ * Create a hook map used by the permission Engine
54
+ */ const createEngineHooks = ()=>({
55
+ 'before-format::validate.permission': hooks.createAsyncBailHook(),
56
+ 'format.permission': hooks.createAsyncSeriesWaterfallHook(),
57
+ 'after-format::validate.permission': hooks.createAsyncBailHook(),
58
+ 'before-evaluate.permission': hooks.createAsyncSeriesHook(),
59
+ 'before-register.permission': hooks.createAsyncSeriesHook()
60
+ });
61
+ /**
62
+ * Create a context from a domain {@link Permission} used by the validate hooks
63
+ */ const createValidateContext = (permission)=>({
64
+ get permission () {
65
+ return cloneDeep(permission);
79
66
  }
80
- }
81
- return this;
82
- }
83
- }
84
- });
67
+ });
68
+ /**
69
+ * Create a context from a domain {@link Permission} used by the before valuate hook
70
+ */ const createBeforeEvaluateContext = (permission)=>({
71
+ get permission () {
72
+ return cloneDeep(permission);
73
+ },
74
+ addCondition (condition) {
75
+ Object.assign(permission, addCondition(condition, permission));
76
+ return this;
77
+ }
78
+ });
79
+ /**
80
+ * Create a context from a casl Permission & some options
81
+ * @param caslPermission
82
+ */ const createWillRegisterContext = ({ permission, options })=>({
83
+ ...options,
84
+ get permission () {
85
+ return cloneDeep(permission);
86
+ },
87
+ condition: {
88
+ and (rawConditionObject) {
89
+ if (!permission.condition) {
90
+ permission.condition = {
91
+ $and: []
92
+ };
93
+ }
94
+ if (isArray(permission.condition.$and)) {
95
+ permission.condition.$and.push(rawConditionObject);
96
+ }
97
+ return this;
98
+ },
99
+ or (rawConditionObject) {
100
+ if (!permission.condition) {
101
+ permission.condition = {
102
+ $and: []
103
+ };
104
+ }
105
+ if (isArray(permission.condition.$and)) {
106
+ const orClause = permission.condition.$and.find(has('$or'));
107
+ if (orClause) {
108
+ orClause.$or.push(rawConditionObject);
109
+ } else {
110
+ permission.condition.$and.push({
111
+ $or: [
112
+ rawConditionObject
113
+ ]
114
+ });
115
+ }
116
+ }
117
+ return this;
118
+ }
119
+ }
120
+ });
121
+
85
122
  const allowedOperations = [
86
- "$or",
87
- "$and",
88
- "$eq",
89
- "$ne",
90
- "$in",
91
- "$nin",
92
- "$lt",
93
- "$lte",
94
- "$gt",
95
- "$gte",
96
- "$exists",
97
- "$elemMatch"
123
+ '$or',
124
+ '$and',
125
+ '$eq',
126
+ '$ne',
127
+ '$in',
128
+ '$nin',
129
+ '$lt',
130
+ '$lte',
131
+ '$gt',
132
+ '$gte',
133
+ '$exists',
134
+ '$elemMatch'
98
135
  ];
99
136
  const operations = pick(allowedOperations, sift);
100
- const conditionsMatcher = (conditions) => {
101
- return sift.createQueryTester(conditions, { operations });
137
+ const conditionsMatcher = (conditions)=>{
138
+ return sift.createQueryTester(conditions, {
139
+ operations
140
+ });
102
141
  };
103
- const buildParametrizedAction = ({ name, params }) => {
104
- return `${name}?${qs.stringify(params)}`;
142
+ const buildParametrizedAction = ({ name, params })=>{
143
+ return `${name}?${qs.stringify(params)}`;
105
144
  };
106
- const caslAbilityBuilder = () => {
107
- const { can, build, ...rest } = new AbilityBuilder(Ability);
108
- return {
109
- can(permission) {
110
- const { action, subject, properties = {}, condition } = permission;
111
- const { fields } = properties;
112
- const caslAction = typeof action === "string" ? action : buildParametrizedAction(action);
113
- return can(
114
- caslAction,
115
- isNil(subject) ? "all" : subject,
116
- fields,
117
- isObject(condition) ? condition : void 0
118
- );
119
- },
120
- buildParametrizedAction({ name, params }) {
121
- return `${name}?${qs.stringify(params)}`;
122
- },
123
- build() {
124
- const ability = build({ conditionsMatcher });
125
- function decorateCan(originalCan) {
126
- return function(...args) {
127
- const [action, ...rest2] = args;
128
- const caslAction = typeof action === "string" ? action : buildParametrizedAction(action);
129
- return originalCan.apply(ability, [caslAction, ...rest2]);
130
- };
131
- }
132
- ability.can = decorateCan(ability.can);
133
- return ability;
134
- },
135
- ...rest
136
- };
145
+ /**
146
+ * Casl Ability Builder.
147
+ */ const caslAbilityBuilder = ()=>{
148
+ const { can, build, ...rest } = new AbilityBuilder(Ability);
149
+ return {
150
+ can (permission) {
151
+ const { action, subject, properties = {}, condition } = permission;
152
+ const { fields } = properties;
153
+ const caslAction = typeof action === 'string' ? action : buildParametrizedAction(action);
154
+ return can(caslAction, isNil(subject) ? 'all' : subject, fields, isObject(condition) ? condition : undefined);
155
+ },
156
+ buildParametrizedAction ({ name, params }) {
157
+ return `${name}?${qs.stringify(params)}`;
158
+ },
159
+ build () {
160
+ const ability = build({
161
+ conditionsMatcher
162
+ });
163
+ function decorateCan(originalCan) {
164
+ return function(...args) {
165
+ const [action, ...rest] = args;
166
+ const caslAction = typeof action === 'string' ? action : buildParametrizedAction(action);
167
+ // Call the original `can` method
168
+ return originalCan.apply(ability, [
169
+ caslAction,
170
+ ...rest
171
+ ]);
172
+ };
173
+ }
174
+ ability.can = decorateCan(ability.can);
175
+ return ability;
176
+ },
177
+ ...rest
178
+ };
137
179
  };
138
- const index$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
180
+
181
+ var index$1 = /*#__PURE__*/Object.freeze({
139
182
  __proto__: null,
140
- caslAbilityBuilder
141
- }, Symbol.toStringTag, { value: "Module" }));
142
- const createEngineState = () => {
143
- const hooks2 = createEngineHooks();
144
- return { hooks: hooks2 };
145
- };
146
- const newEngine = (params) => {
147
- const { providers, abilityBuilderFactory = caslAbilityBuilder } = params;
148
- const state = createEngineState();
149
- const runValidationHook = async (hook, context) => state.hooks[hook].call(context);
150
- const evaluate = async (params2) => {
151
- const { options, register } = params2;
152
- const preFormatValidation = await runValidationHook(
153
- "before-format::validate.permission",
154
- createBeforeEvaluateContext(params2.permission)
155
- );
156
- if (preFormatValidation === false) {
157
- return;
158
- }
159
- const permission = await state.hooks["format.permission"].call(
160
- params2.permission
161
- );
162
- const afterFormatValidation = await runValidationHook(
163
- "after-format::validate.permission",
164
- createValidateContext(permission)
165
- );
166
- if (afterFormatValidation === false) {
167
- return;
168
- }
169
- await state.hooks["before-evaluate.permission"].call(createBeforeEvaluateContext(permission));
170
- const {
171
- action: actionName,
172
- subject,
173
- properties,
174
- conditions = [],
175
- actionParameters = {}
176
- } = permission;
177
- let action = actionName;
178
- if (actionParameters && Object.keys(actionParameters).length > 0) {
179
- action = `${actionName}?${qs.stringify(actionParameters)}`;
180
- }
181
- if (conditions.length === 0) {
182
- return register({ action, subject, properties });
183
- }
184
- const resolveConditions = _.map(providers.condition.get);
185
- const removeInvalidConditions = _.filter(
186
- (condition) => _.isFunction(condition.handler)
187
- );
188
- const evaluateConditions = (conditions2) => {
189
- return Promise.all(
190
- conditions2.map(async (condition) => ({
191
- condition,
192
- result: await condition.handler(
193
- _.merge(options, { permission: _.cloneDeep(permission) })
194
- )
195
- }))
196
- );
183
+ caslAbilityBuilder: caslAbilityBuilder
184
+ });
185
+
186
+ /**
187
+ * Create a default state object for the engine
188
+ */ const createEngineState = ()=>{
189
+ const hooks = createEngineHooks();
190
+ return {
191
+ hooks
197
192
  };
198
- const removeInvalidResults = _.filter(
199
- ({ result }) => _.isBoolean(result) || _.isObject(result)
200
- );
201
- const evaluatedConditions = await Promise.resolve(conditions).then(resolveConditions).then(removeInvalidConditions).then(evaluateConditions).then(removeInvalidResults);
202
- const resultPropEq = _.propEq("result");
203
- const pickResults = _.map(_.prop("result"));
204
- if (evaluatedConditions.every(resultPropEq(false))) {
205
- return;
206
- }
207
- if (_.isEmpty(evaluatedConditions) || evaluatedConditions.some(resultPropEq(true))) {
208
- return register({ action, subject, properties });
209
- }
210
- const results = pickResults(evaluatedConditions).filter(_.isObject);
211
- if (_.isEmpty(results)) {
212
- return register({ action, subject, properties });
213
- }
214
- return register({
215
- action,
216
- subject,
217
- properties,
218
- condition: { $and: [{ $or: results }] }
219
- });
220
- };
221
- return {
222
- get hooks() {
223
- return state.hooks;
224
- },
193
+ };
194
+ const newEngine = (params)=>{
195
+ const { providers, abilityBuilderFactory = caslAbilityBuilder } = params;
196
+ const state = createEngineState();
197
+ const runValidationHook = async (hook, context)=>state.hooks[hook].call(context);
225
198
  /**
199
+ * Evaluate a permission using local and registered behaviors (using hooks).
200
+ * Validate, format (add condition, etc...), evaluate (evaluate conditions) and register a permission
201
+ */ const evaluate = async (params)=>{
202
+ const { options, register } = params;
203
+ const preFormatValidation = await runValidationHook('before-format::validate.permission', createBeforeEvaluateContext(params.permission));
204
+ if (preFormatValidation === false) {
205
+ return;
206
+ }
207
+ const permission = await state.hooks['format.permission'].call(params.permission);
208
+ const afterFormatValidation = await runValidationHook('after-format::validate.permission', createValidateContext(permission));
209
+ if (afterFormatValidation === false) {
210
+ return;
211
+ }
212
+ await state.hooks['before-evaluate.permission'].call(createBeforeEvaluateContext(permission));
213
+ const { action: actionName, subject, properties, conditions = [], actionParameters = {} } = permission;
214
+ let action = actionName;
215
+ if (actionParameters && Object.keys(actionParameters).length > 0) {
216
+ action = `${actionName}?${qs.stringify(actionParameters)}`;
217
+ }
218
+ if (conditions.length === 0) {
219
+ return register({
220
+ action,
221
+ subject,
222
+ properties
223
+ });
224
+ }
225
+ const resolveConditions = _.map(providers.condition.get);
226
+ const removeInvalidConditions = _.filter((condition)=>_.isFunction(condition.handler));
227
+ const evaluateConditions = (conditions)=>{
228
+ return Promise.all(conditions.map(async (condition)=>({
229
+ condition,
230
+ result: await condition.handler(_.merge(options, {
231
+ permission: _.cloneDeep(permission)
232
+ }))
233
+ })));
234
+ };
235
+ const removeInvalidResults = _.filter(({ result })=>_.isBoolean(result) || _.isObject(result));
236
+ const evaluatedConditions = await Promise.resolve(conditions).then(resolveConditions).then(removeInvalidConditions).then(evaluateConditions).then(removeInvalidResults);
237
+ const resultPropEq = _.propEq('result');
238
+ const pickResults = _.map(_.prop('result'));
239
+ if (evaluatedConditions.every(resultPropEq(false))) {
240
+ return;
241
+ }
242
+ if (_.isEmpty(evaluatedConditions) || evaluatedConditions.some(resultPropEq(true))) {
243
+ return register({
244
+ action,
245
+ subject,
246
+ properties
247
+ });
248
+ }
249
+ const results = pickResults(evaluatedConditions).filter(_.isObject);
250
+ if (_.isEmpty(results)) {
251
+ return register({
252
+ action,
253
+ subject,
254
+ properties
255
+ });
256
+ }
257
+ return register({
258
+ action,
259
+ subject,
260
+ properties,
261
+ condition: {
262
+ $and: [
263
+ {
264
+ $or: results
265
+ }
266
+ ]
267
+ }
268
+ });
269
+ };
270
+ return {
271
+ get hooks () {
272
+ return state.hooks;
273
+ },
274
+ /**
226
275
  * Create a register function that wraps a `can` function
227
276
  * used to register a permission in the ability builder
228
- */
229
- createRegisterFunction(can, options) {
230
- return async (permission) => {
231
- const hookContext = createWillRegisterContext({ options, permission });
232
- await state.hooks["before-register.permission"].call(hookContext);
233
- return can(permission);
234
- };
235
- },
236
- /**
277
+ */ createRegisterFunction (can, options) {
278
+ return async (permission)=>{
279
+ const hookContext = createWillRegisterContext({
280
+ options,
281
+ permission
282
+ });
283
+ await state.hooks['before-register.permission'].call(hookContext);
284
+ return can(permission);
285
+ };
286
+ },
287
+ /**
237
288
  * Register a new handler for a given hook
238
- */
239
- on(hook, handler) {
240
- const validHooks = Object.keys(state.hooks);
241
- const isValidHook = validHooks.includes(hook);
242
- if (!isValidHook) {
243
- throw new Error(
244
- `Invalid hook supplied when trying to register an handler to the permission engine. Got "${hook}" but expected one of ${validHooks.join(
245
- ", "
246
- )}`
247
- );
248
- }
249
- state.hooks[hook].register(handler);
250
- return this;
251
- },
252
- /**
289
+ */ on (hook, handler) {
290
+ const validHooks = Object.keys(state.hooks);
291
+ const isValidHook = validHooks.includes(hook);
292
+ if (!isValidHook) {
293
+ throw new Error(`Invalid hook supplied when trying to register an handler to the permission engine. Got "${hook}" but expected one of ${validHooks.join(', ')}`);
294
+ }
295
+ state.hooks[hook].register(handler);
296
+ return this;
297
+ },
298
+ /**
253
299
  * Generate an ability based on the instance's
254
300
  * ability builder and the given permissions
255
- */
256
- async generateAbility(permissions, options = {}) {
257
- const { can, build } = abilityBuilderFactory();
258
- for (const permission of permissions) {
259
- const register = this.createRegisterFunction(can, options);
260
- await evaluate({ permission, options, register });
261
- }
262
- return build();
263
- }
264
- };
301
+ */ async generateAbility (permissions, options = {}) {
302
+ const { can, build } = abilityBuilderFactory();
303
+ for (const permission of permissions){
304
+ const register = this.createRegisterFunction(can, options);
305
+ await evaluate({
306
+ permission,
307
+ options,
308
+ register
309
+ });
310
+ }
311
+ return build();
312
+ }
313
+ };
265
314
  };
266
- const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
315
+
316
+ var index = /*#__PURE__*/Object.freeze({
267
317
  __proto__: null,
268
318
  abilities: index$1,
269
319
  new: newEngine
270
- }, Symbol.toStringTag, { value: "Module" }));
271
- export {
272
- index$2 as domain,
273
- index as engine
274
- };
320
+ });
321
+
322
+ export { index$2 as domain, index as engine };
275
323
  //# sourceMappingURL=index.mjs.map