mirror of
https://github.com/strapi/strapi.git
synced 2025-09-06 23:28:02 +00:00
364 lines
10 KiB
JavaScript
364 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
const _ = require('lodash');
|
|
const { subject } = require('@casl/ability');
|
|
const createConditionProvider = require('../permission/condition-provider');
|
|
const createPermissionsEngine = require('../permission/engine');
|
|
|
|
describe('Permissions Engine', () => {
|
|
let conditionProvider;
|
|
let engine;
|
|
|
|
const localTestData = {
|
|
users: {
|
|
bob: {
|
|
firstname: 'Bob',
|
|
title: 'guest',
|
|
roles: [{ id: 1 }, { id: 2 }],
|
|
},
|
|
alice: {
|
|
firstname: 'Alice',
|
|
title: 'admin',
|
|
roles: [{ id: 1 }],
|
|
},
|
|
john: {
|
|
firstname: 'John',
|
|
title: 'admin',
|
|
roles: [{ id: 3 }],
|
|
},
|
|
},
|
|
roles: {
|
|
1: {
|
|
permissions: [
|
|
{
|
|
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: ['plugins::test.isBob'],
|
|
},
|
|
],
|
|
},
|
|
3: {
|
|
permissions: [
|
|
{
|
|
action: 'read',
|
|
subject: 'user',
|
|
fields: ['title'],
|
|
conditions: ['plugins::test.isContainedIn'],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
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(() => {
|
|
global.strapi = {
|
|
isLoaded: false,
|
|
admin: {
|
|
services: {
|
|
permission: {
|
|
findUserPermissions: jest.fn(({ roles }) =>
|
|
_.reduce(
|
|
localTestData.roles,
|
|
(acc, { permissions: value }, key) => {
|
|
return roles.map(_.property('id')).includes(_.toNumber(key))
|
|
? [...acc, ...value]
|
|
: acc;
|
|
},
|
|
[]
|
|
)
|
|
),
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
conditionProvider = createConditionProvider();
|
|
conditionProvider.registerMany(localTestData.conditions);
|
|
|
|
engine = createPermissionsEngine(conditionProvider);
|
|
|
|
jest.spyOn(engine, 'evaluatePermission');
|
|
jest.spyOn(engine, 'createRegisterFunction');
|
|
jest.spyOn(engine, 'generateAbilityCreatorFor');
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe('GenerateUserAbility', () => {
|
|
test('Successfully creates an ability for Bob', async () => {
|
|
const user = getUser('bob');
|
|
|
|
const ability = await engine.generateUserAbility(user);
|
|
|
|
const expected = [
|
|
{
|
|
action: 'read',
|
|
fields: ['**'],
|
|
subject: 'article',
|
|
},
|
|
{
|
|
action: 'post',
|
|
fields: ['*'],
|
|
subject: 'article',
|
|
},
|
|
];
|
|
|
|
expect(engine.generateAbilityCreatorFor).toHaveBeenCalledWith(user);
|
|
expect(_.orderBy(ability.rules, ['subject'], ['asc'])).toMatchObject(expected);
|
|
|
|
expect(ability.can('post', 'article')).toBeTruthy();
|
|
expect(ability.can('post', 'article', 'user')).toBeTruthy();
|
|
expect(ability.can('post', 'article', 'user.nested')).toBeFalsy();
|
|
|
|
expect(ability.can('read', 'article')).toBeTruthy();
|
|
expect(ability.can('read', 'article', 'title')).toBeTruthy();
|
|
expect(ability.can('read', 'article', 'title.nested')).toBeTruthy();
|
|
|
|
expect(ability.can('read', 'user')).toBeFalsy();
|
|
expect(ability.can('read', 'user', 'firstname')).toBeFalsy();
|
|
expect(ability.can('read', 'user', 'title')).toBeFalsy();
|
|
expect(ability.can('read', 'user', 'title.nested')).toBeFalsy();
|
|
});
|
|
|
|
test('Successfully creates an ability for Alice', async () => {
|
|
const user = getUser('alice');
|
|
|
|
const ability = await engine.generateUserAbility(user);
|
|
|
|
const expected = [
|
|
{
|
|
action: 'read',
|
|
fields: ['title'],
|
|
subject: 'user',
|
|
},
|
|
];
|
|
|
|
expect(engine.generateAbilityCreatorFor).toHaveBeenCalledWith(user);
|
|
expect(_.orderBy(ability.rules, ['action'], ['asc'])).toMatchObject(expected);
|
|
|
|
expect(ability.can('post', 'article')).toBeFalsy();
|
|
expect(ability.can('post', 'article', 'user')).toBeFalsy();
|
|
expect(ability.can('post', 'article', 'user.nested')).toBeFalsy();
|
|
|
|
expect(ability.can('read', 'article')).toBeFalsy();
|
|
expect(ability.can('read', 'article', 'title')).toBeFalsy();
|
|
expect(ability.can('read', 'article', 'title.nested')).toBeFalsy();
|
|
|
|
expect(ability.can('read', 'user')).toBeTruthy();
|
|
expect(ability.can('read', 'user', 'firstname')).toBeFalsy();
|
|
expect(ability.can('read', 'user', 'title')).toBeTruthy();
|
|
expect(ability.can('read', 'user', 'title.nested')).toBeFalsy();
|
|
});
|
|
|
|
describe('Use objects as subject', () => {
|
|
let ability;
|
|
|
|
beforeAll(async () => {
|
|
const user = getUser('john');
|
|
ability = await engine.generateUserAbility(user);
|
|
});
|
|
|
|
test('Fails to validate the object condition', () => {
|
|
const args = ['read', subject('user', { firstname: 'Bar' }), 'title'];
|
|
|
|
expect(ability.can(...args)).toBeFalsy();
|
|
});
|
|
|
|
test('Fails to read a restricted field', () => {
|
|
const args = ['read', subject('user', { firstname: 'Foo' }), 'bar'];
|
|
|
|
expect(ability.can(...args)).toBeFalsy();
|
|
});
|
|
|
|
test('Successfully validate the permission', () => {
|
|
const args = ['read', subject('user', { firstname: 'Foo' }), 'title'];
|
|
|
|
expect(ability.can(...args)).toBeTruthy();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Generate Ability Creator For', () => {
|
|
test('Successfully generates an ability creator for Alice', async () => {
|
|
const user = getUser('alice');
|
|
|
|
const abilityCreator = engine.generateAbilityCreatorFor(user);
|
|
const ability = await abilityCreator([]);
|
|
|
|
expect(abilityCreator).not.toBeUndefined();
|
|
expect(typeof abilityCreator).toBe('function');
|
|
expect(ability.rules).toStrictEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('Evaluate Permission', () => {
|
|
test('It should register the permission (no conditions / true result)', async () => {
|
|
const permission = { action: 'read', subject: 'article', fields: ['title'] };
|
|
const user = getUser('alice');
|
|
const registerFn = jest.fn();
|
|
|
|
await engine.evaluatePermission({ permission, user, registerFn });
|
|
|
|
expect(registerFn).toHaveBeenCalledWith({ ...permission, condition: true });
|
|
});
|
|
|
|
test('It should register the permission (conditions / true result)', async () => {
|
|
const permission = {
|
|
action: 'read',
|
|
subject: 'article',
|
|
fields: ['title'],
|
|
conditions: ['plugins::test.isAdmin'],
|
|
};
|
|
const user = getUser('alice');
|
|
const registerFn = jest.fn();
|
|
|
|
await engine.evaluatePermission({ permission, user, registerFn });
|
|
|
|
const expected = {
|
|
..._.omit(permission, 'conditions'),
|
|
condition: true,
|
|
};
|
|
|
|
expect(registerFn).toHaveBeenCalledWith(expected);
|
|
});
|
|
|
|
test('It should not register the permission (conditions / false result)', async () => {
|
|
const permission = {
|
|
action: 'read',
|
|
subject: 'article',
|
|
fields: ['title'],
|
|
conditions: ['plugins::test.isBob'],
|
|
};
|
|
const user = getUser('alice');
|
|
const registerFn = jest.fn();
|
|
|
|
await engine.evaluatePermission({ permission, user, registerFn });
|
|
|
|
expect(registerFn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('It should register the permission (conditions / object result)', async () => {
|
|
const permission = {
|
|
action: 'read',
|
|
subject: 'article',
|
|
fields: ['title'],
|
|
conditions: ['plugins::test.isCreatedBy'],
|
|
};
|
|
const user = getUser('alice');
|
|
const registerFn = jest.fn();
|
|
|
|
await engine.evaluatePermission({ permission, user, registerFn });
|
|
|
|
const expected = {
|
|
..._.omit(permission, 'conditions'),
|
|
condition: { created_by: user.firstname },
|
|
};
|
|
|
|
expect(registerFn).toHaveBeenCalledWith(expected);
|
|
});
|
|
});
|
|
|
|
describe('Create Register Function', () => {
|
|
let can;
|
|
let registerFn;
|
|
|
|
beforeEach(() => {
|
|
can = jest.fn();
|
|
registerFn = engine.createRegisterFunction(can);
|
|
});
|
|
|
|
test('It should calls the can function without any condition', () => {
|
|
registerFn({ action: 'read', subject: 'article', fields: '*', condition: true });
|
|
|
|
expect(can).toHaveBeenCalledTimes(1);
|
|
expect(can).toHaveBeenCalledWith('read', 'article', '*', undefined);
|
|
});
|
|
|
|
test('It should calls the can function with a condition', () => {
|
|
registerFn({ action: 'read', subject: 'article', fields: '*', condition: { created_by: 1 } });
|
|
|
|
expect(can).toHaveBeenCalledTimes(1);
|
|
expect(can).toHaveBeenCalledWith('read', 'article', '*', { created_by: 1 });
|
|
});
|
|
});
|
|
|
|
describe('Check Many', () => {
|
|
let ability;
|
|
const permissions = [
|
|
{ action: 'read', subject: 'user', field: 'title' },
|
|
{ action: 'post', subject: 'article' },
|
|
];
|
|
|
|
beforeEach(() => {
|
|
ability = { can: jest.fn(() => true) };
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
test('Using curried version of checkMany', () => {
|
|
const checkMany = engine.checkMany(ability);
|
|
|
|
const res = checkMany(permissions);
|
|
|
|
expect(res).toHaveLength(permissions.length);
|
|
expect(ability.can).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
test('Using raw version of checkMany', () => {
|
|
const res = engine.checkMany(ability, permissions);
|
|
|
|
expect(res).toHaveLength(permissions.length);
|
|
expect(ability.can).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
});
|