mirror of
https://github.com/strapi/strapi.git
synced 2025-09-03 05:39:36 +00:00
Add category to conditions
Signed-off-by: Convly <jean-sebastien.herbaux@epitech.eu>
This commit is contained in:
parent
e745903112
commit
ed6a68d9be
@ -29,9 +29,14 @@ const cleanPermissionInDatabase = async () => {
|
||||
const registerAdminConditions = () => {
|
||||
const { conditionProvider } = strapi.admin.services.permission;
|
||||
|
||||
conditionProvider.registerMany({
|
||||
'strapi-admin::isOwner': user => ({ 'strapi_created_by.id': user.id }),
|
||||
});
|
||||
conditionProvider.registerMany([
|
||||
{
|
||||
name: 'isOwner',
|
||||
plugin: 'admin',
|
||||
category: 'default',
|
||||
handler: user => ({ 'strapi_created_by.id': user.id }),
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
module.exports = async () => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const { formatActionsBySections } = require('./formatters');
|
||||
const { validateCheckPermissionsInput } = require('../validation/permission');
|
||||
|
||||
@ -36,7 +37,9 @@ module.exports = {
|
||||
|
||||
ctx.body = {
|
||||
data: {
|
||||
conditions,
|
||||
conditions: conditions.map(condition =>
|
||||
_.pick(condition, 'id', 'name', 'plugin', 'category')
|
||||
),
|
||||
sections: formatActionsBySections(allActions),
|
||||
},
|
||||
};
|
||||
|
24
packages/strapi-admin/domain/condition.js
Normal file
24
packages/strapi-admin/domain/condition.js
Normal file
@ -0,0 +1,24 @@
|
||||
'use strict';
|
||||
|
||||
const getConditionId = ({ name, plugin }) => {
|
||||
let id;
|
||||
|
||||
if (plugin === 'admin') {
|
||||
id = `admin::${name}`;
|
||||
} else if (plugin) {
|
||||
id = `plugins::${plugin}.${name}`;
|
||||
} else {
|
||||
id = `application::${name}`;
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
||||
const createCondition = condition => ({
|
||||
...condition,
|
||||
id: getConditionId(condition),
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
getConditionId,
|
||||
createCondition,
|
||||
};
|
@ -1,11 +1,31 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const createConditionProvider = require('../permission/condition-provider');
|
||||
const { createCondition, getConditionId } = require('../../domain/condition');
|
||||
|
||||
describe('Condition Provider', () => {
|
||||
let provider;
|
||||
const localTestData = {
|
||||
conditions: [
|
||||
{
|
||||
name: 'foo',
|
||||
plugin: 'test',
|
||||
category: 'default',
|
||||
handler: jest.fn(() => true),
|
||||
},
|
||||
{
|
||||
name: 'john',
|
||||
plugin: 'test',
|
||||
category: 'default',
|
||||
handler: jest.fn(() => false),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
global.strapi = { isLoaded: false };
|
||||
|
||||
provider = createConditionProvider();
|
||||
|
||||
jest.spyOn(provider, 'register');
|
||||
@ -17,22 +37,33 @@ describe('Condition Provider', () => {
|
||||
});
|
||||
|
||||
describe('Register', () => {
|
||||
test('Cannot register if strapi is already loaded', () => {
|
||||
global.strapi.isLoaded = true;
|
||||
|
||||
const condition = localTestData.conditions[0];
|
||||
|
||||
const registerFn = () => provider.register(condition);
|
||||
|
||||
expect(registerFn).toThrowError();
|
||||
});
|
||||
|
||||
test('Successfully register a new condition', () => {
|
||||
const condition = { key: 'conditionName', value: jest.fn(() => true) };
|
||||
const condition = localTestData.conditions[0];
|
||||
|
||||
provider.register(condition.key, condition.value);
|
||||
provider.register(condition);
|
||||
|
||||
const res = provider.get(condition.key);
|
||||
const res = provider.get(condition.name, condition.plugin);
|
||||
|
||||
expect(provider.has).toHaveBeenCalledWith(condition.key);
|
||||
expect(res).toBe(condition.value);
|
||||
expect(res()).toBeTruthy();
|
||||
expect(condition.value).toHaveBeenCalled();
|
||||
expect(provider.has).toHaveBeenCalledWith(condition.name, condition.plugin);
|
||||
expect(res).toMatchObject(condition);
|
||||
expect(res.handler()).toBe(true);
|
||||
expect(condition.handler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('The condition already exists', () => {
|
||||
const key = 'conditionName';
|
||||
const registerFn = () => provider.register(key, {});
|
||||
const condition = localTestData.conditions[0];
|
||||
|
||||
const registerFn = () => provider.register(condition);
|
||||
|
||||
registerFn();
|
||||
|
||||
@ -43,34 +74,28 @@ describe('Condition Provider', () => {
|
||||
|
||||
describe('Registers Many', () => {
|
||||
test('Registers many conditions successfully', () => {
|
||||
const conditions = {
|
||||
foo: jest.fn(() => 'bar'),
|
||||
john: jest.fn(() => 'doe'),
|
||||
};
|
||||
const conditions = localTestData.conditions;
|
||||
|
||||
provider.registerMany(conditions);
|
||||
|
||||
const resFoo = provider.get('foo');
|
||||
const resJohn = provider.get('john');
|
||||
const resFoo = provider.get('foo', 'test');
|
||||
const resJohn = provider.get('john', 'test');
|
||||
|
||||
expect(provider.register).toHaveBeenCalledTimes(2);
|
||||
expect(provider.has).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(resFoo).toBe(conditions.foo);
|
||||
expect(resJohn).toBe(conditions.john);
|
||||
expect(resFoo).toMatchObject(createCondition(conditions[0]));
|
||||
expect(resJohn).toMatchObject(createCondition(conditions[1]));
|
||||
|
||||
expect(resFoo()).toBe('bar');
|
||||
expect(resJohn()).toBe('doe');
|
||||
expect(resFoo.handler()).toBe(true);
|
||||
expect(resJohn.handler()).toBe(false);
|
||||
|
||||
expect(conditions.foo).toHaveBeenCalled();
|
||||
expect(conditions.john).toHaveBeenCalled();
|
||||
expect(conditions[0].handler).toHaveBeenCalled();
|
||||
expect(conditions[1].handler).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('Fails to register already existing conditions', () => {
|
||||
const conditions = {
|
||||
foo: {},
|
||||
john: {},
|
||||
};
|
||||
const conditions = localTestData.conditions;
|
||||
|
||||
const registerFn = () => provider.registerMany(conditions);
|
||||
|
||||
@ -83,54 +108,46 @@ describe('Condition Provider', () => {
|
||||
|
||||
describe('Conditions', () => {
|
||||
test('Returns an array of all the conditions key', () => {
|
||||
const conditions = {
|
||||
foo: {},
|
||||
bar: {},
|
||||
};
|
||||
const expected = ['bar', 'foo'];
|
||||
const conditions = localTestData.conditions;
|
||||
|
||||
const expected = ['plugins::test.foo', 'plugins::test.john'];
|
||||
|
||||
provider.registerMany(conditions);
|
||||
|
||||
expect(provider.getAll().sort()).toMatchObject(expected);
|
||||
expect(
|
||||
provider
|
||||
.getAll()
|
||||
.map(_.property('id'))
|
||||
.sort()
|
||||
).toMatchObject(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Has', () => {
|
||||
test('The key exists', () => {
|
||||
const key = 'foo';
|
||||
provider.register(key, {});
|
||||
const condition = localTestData.conditions[0];
|
||||
|
||||
expect(provider.has(key)).toBeTruthy();
|
||||
provider.register(condition);
|
||||
|
||||
expect(provider.has(condition.name, condition.plugin)).toBeTruthy();
|
||||
});
|
||||
|
||||
test(`The key doesn't exists`, () => {
|
||||
const key = 'foo';
|
||||
const { name, plugin } = localTestData.conditions[1];
|
||||
|
||||
expect(provider.has(key)).toBeFalsy();
|
||||
expect(provider.has(name, plugin)).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Delete', () => {
|
||||
test('Delete existing condition', () => {
|
||||
const key = 'foo';
|
||||
describe('GetById', () => {
|
||||
test('Successfully get a condition by its ID', () => {
|
||||
const condition = localTestData.conditions[0];
|
||||
|
||||
provider.register(key);
|
||||
provider.register(condition);
|
||||
|
||||
expect(provider.getAll()).toHaveLength(1);
|
||||
const res = provider.getById(getConditionId(condition));
|
||||
|
||||
provider.delete(key);
|
||||
|
||||
expect(provider.has).toHaveBeenCalledWith(key);
|
||||
expect(provider.getAll()).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Do nothing when the key does not exists', () => {
|
||||
const key = 'foo';
|
||||
|
||||
provider.delete(key);
|
||||
|
||||
expect(provider.has).toHaveBeenCalledWith(key);
|
||||
expect(provider.getAll()).toHaveLength(0);
|
||||
expect(res).toMatchObject(createCondition(condition));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -30,40 +30,74 @@ describe('Permissions Engine', () => {
|
||||
roles: {
|
||||
1: {
|
||||
permissions: [
|
||||
{ action: 'read', subject: 'article', fields: ['**'], conditions: ['isBob'] },
|
||||
{ action: 'read', subject: 'user', fields: ['title'], conditions: ['isAdmin'] },
|
||||
{
|
||||
action: 'read',
|
||||
subject: 'article',
|
||||
fields: ['**'],
|
||||
conditions: ['plugins::test.isBob'],
|
||||
},
|
||||
{
|
||||
action: 'read',
|
||||
subject: 'user',
|
||||
fields: ['title'],
|
||||
conditions: ['plugins::test.isAdmin'],
|
||||
},
|
||||
],
|
||||
},
|
||||
2: {
|
||||
permissions: [{ action: 'post', subject: 'article', fields: ['*'], conditions: ['isBob'] }],
|
||||
permissions: [
|
||||
{
|
||||
action: 'post',
|
||||
subject: 'article',
|
||||
fields: ['*'],
|
||||
conditions: ['plugins::test.isBob'],
|
||||
},
|
||||
],
|
||||
},
|
||||
3: {
|
||||
permissions: [
|
||||
{ action: 'read', subject: 'user', fields: ['title'], conditions: ['isContainedIn'] },
|
||||
{
|
||||
action: 'read',
|
||||
subject: 'user',
|
||||
fields: ['title'],
|
||||
conditions: ['plugins::test.isContainedIn'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
conditions: {
|
||||
isBob: async user => new Promise(resolve => resolve(user.firstname === 'Bob')),
|
||||
isAdmin: user => user.title === 'admin',
|
||||
isCreatedBy: user => ({ created_by: user.firstname }),
|
||||
isContainedIn: { firstname: { $in: ['Alice', 'Foo'] } },
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
plugin: 'test',
|
||||
name: 'isBob',
|
||||
category: 'default',
|
||||
handler: async user => new Promise(resolve => resolve(user.firstname === 'Bob')),
|
||||
},
|
||||
{
|
||||
plugin: 'test',
|
||||
name: 'isAdmin',
|
||||
category: 'default',
|
||||
handler: user => user.title === 'admin',
|
||||
},
|
||||
{
|
||||
plugin: 'test',
|
||||
name: 'isCreatedBy',
|
||||
category: 'default',
|
||||
handler: user => ({ created_by: user.firstname }),
|
||||
},
|
||||
{
|
||||
plugin: 'test',
|
||||
name: 'isContainedIn',
|
||||
category: 'default',
|
||||
handler: { firstname: { $in: ['Alice', 'Foo'] } },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const getUser = name => localTestData.users[name];
|
||||
|
||||
beforeEach(() => {
|
||||
conditionProvider = createConditionProvider();
|
||||
conditionProvider.registerMany(localTestData.conditions);
|
||||
|
||||
engine = createPermissionsEngine(conditionProvider);
|
||||
|
||||
jest.spyOn(engine, 'evaluatePermission');
|
||||
jest.spyOn(engine, 'createRegisterFunction');
|
||||
jest.spyOn(engine, 'generateAbilityCreatorFor');
|
||||
|
||||
global.strapi = {
|
||||
isLoaded: false,
|
||||
admin: {
|
||||
services: {
|
||||
permission: {
|
||||
@ -82,6 +116,15 @@ describe('Permissions Engine', () => {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
conditionProvider = createConditionProvider();
|
||||
conditionProvider.registerMany(localTestData.conditions);
|
||||
|
||||
engine = createPermissionsEngine(conditionProvider);
|
||||
|
||||
jest.spyOn(engine, 'evaluatePermission');
|
||||
jest.spyOn(engine, 'createRegisterFunction');
|
||||
jest.spyOn(engine, 'generateAbilityCreatorFor');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -211,7 +254,7 @@ describe('Permissions Engine', () => {
|
||||
action: 'read',
|
||||
subject: 'article',
|
||||
fields: ['title'],
|
||||
conditions: ['isAdmin'],
|
||||
conditions: ['plugins::test.isAdmin'],
|
||||
};
|
||||
const user = getUser('alice');
|
||||
const registerFn = jest.fn();
|
||||
@ -231,7 +274,7 @@ describe('Permissions Engine', () => {
|
||||
action: 'read',
|
||||
subject: 'article',
|
||||
fields: ['title'],
|
||||
conditions: ['isBob'],
|
||||
conditions: ['plugins::test.isBob'],
|
||||
};
|
||||
const user = getUser('alice');
|
||||
const registerFn = jest.fn();
|
||||
@ -246,7 +289,7 @@ describe('Permissions Engine', () => {
|
||||
action: 'read',
|
||||
subject: 'article',
|
||||
fields: ['title'],
|
||||
conditions: ['isCreatedBy'],
|
||||
conditions: ['plugins::test.isCreatedBy'],
|
||||
};
|
||||
const user = getUser('alice');
|
||||
const registerFn = jest.fn();
|
||||
|
@ -1,70 +1,75 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const { getConditionId, createCondition } = require('../../domain/condition');
|
||||
|
||||
module.exports = () => {
|
||||
const _registry = new Map();
|
||||
|
||||
return {
|
||||
/**
|
||||
* Register a new condition with its associated unique key.
|
||||
* @throws Error if the key already exists
|
||||
* @param name
|
||||
* Register a new condition
|
||||
* @throws Error if the conditionId already exists
|
||||
* @param condition
|
||||
*/
|
||||
register(name, condition) {
|
||||
if (this.has(name)) {
|
||||
throw new Error(
|
||||
`Error while trying to add condition "${name}" to the registry. "${name}" already exists.`
|
||||
);
|
||||
register(condition) {
|
||||
const conditionId = getConditionId(condition);
|
||||
|
||||
if (strapi.isLoaded) {
|
||||
throw new Error(`You can't register new conditions outside of the bootstrap function.`);
|
||||
}
|
||||
|
||||
_registry.set(name, condition);
|
||||
if (this.has(condition.name, condition.plugin)) {
|
||||
throw new Error(`Duplicated condition id: ${getConditionId(condition)}.`);
|
||||
}
|
||||
|
||||
_registry.set(conditionId, createCondition(condition));
|
||||
},
|
||||
|
||||
/**
|
||||
* Shorthand for batch-register operations.
|
||||
* Internally calls `register` for each key/value couple.
|
||||
* @param conditionsMap
|
||||
* Internally calls `register` for each condition.
|
||||
* @param conditions
|
||||
*/
|
||||
registerMany(conditionsMap) {
|
||||
_.each(conditionsMap, (value, key) => this.register(key, value));
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a condition by its key
|
||||
* @param key
|
||||
*/
|
||||
delete(key) {
|
||||
if (this.has(key)) {
|
||||
_registry.delete(key);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the keys of the conditions registry.
|
||||
* @returns {string[]}
|
||||
*/
|
||||
conditions() {
|
||||
return Array.from(_registry.keys());
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a condition by its key
|
||||
* @param name
|
||||
* @returns {any}
|
||||
*/
|
||||
get(name) {
|
||||
return _registry.get(name);
|
||||
registerMany(conditions) {
|
||||
_.each(conditions, this.register.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a key is already present in the registry
|
||||
* @param name
|
||||
* @returns {boolean} true if the key is present in the registry, false otherwise.
|
||||
* @param plugin
|
||||
* @returns {boolean} true if the condition is present in the registry, false otherwise.
|
||||
*/
|
||||
has(name) {
|
||||
return _registry.has(name);
|
||||
has(name, plugin) {
|
||||
return _registry.has(getConditionId({ name, plugin }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a condition by its name and plugin
|
||||
* @param {string} name
|
||||
* @param {string} plugin
|
||||
* @returns {any}
|
||||
*/
|
||||
get(name, plugin) {
|
||||
return _registry.get(getConditionId({ name, plugin }));
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a condition by its id
|
||||
* @param {string} id
|
||||
* @returns {any}
|
||||
*/
|
||||
getById(id) {
|
||||
return _registry.get(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns all the registered conditions.
|
||||
* @returns {any[]}
|
||||
*/
|
||||
getAll() {
|
||||
return Array.from(_registry.values());
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -51,7 +51,10 @@ module.exports = conditionProvider => ({
|
||||
}
|
||||
|
||||
// Replace each condition name by its associated value
|
||||
const resolveConditions = map(conditionProvider.get);
|
||||
const resolveConditions = map(conditionProvider.getById);
|
||||
|
||||
// Only keep the handler of each condition
|
||||
const pickHandlers = map(_.property('handler'));
|
||||
|
||||
// Filter conditions, only keeps objects and functions
|
||||
const filterValidConditions = filter(_.isObject);
|
||||
@ -76,6 +79,7 @@ module.exports = conditionProvider => ({
|
||||
|
||||
await Promise.resolve(conditions)
|
||||
.then(resolveConditions)
|
||||
.then(pickHandlers)
|
||||
.then(filterValidConditions)
|
||||
.then(evaluateConditions)
|
||||
.then(filterValidResults)
|
||||
|
Loading…
x
Reference in New Issue
Block a user