@superawesome/permissions
Version:
Fine grained permissions / access control with ownerships & attribute picking, done right.
917 lines • 59.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ECrudActions = void 0;
// 3rd party
const _ = require("lodash");
const _f = require("lodash/fp");
const test_console_1 = require("test-console");
// own
const logger_1 = require("../logger");
const Permissions_1 = require("../Permissions");
const data_fixtures_1 = require("./data.fixtures");
const Permit_class_1 = require("../Permit.class");
const permissionDefinitions_fixtures_1 = require("./permissionDefinitions.fixtures");
var ECrudActions;
(function (ECrudActions) {
ECrudActions["create"] = "create";
ECrudActions["read"] = "read";
ECrudActions["update"] = "update";
ECrudActions["delete"] = "delete";
})(ECrudActions = exports.ECrudActions || (exports.ECrudActions = {}));
let permit;
const nonOwnResourceId = 99999;
/**
* @todo: restructure tests around features, instead of the naive user-can-do-that syntax :-)
*/
const permissions = new Permissions_1.Permissions({
permissionDefinitions: permissionDefinitions_fixtures_1.permissionDefinitions,
permissionDefinitionDefaults: permissionDefinitions_fixtures_1.permissionDefinitionDefaults,
}).build();
const permissions_limitOwned = new Permissions_1.Permissions({
permissionDefinitions: permissionDefinitions_fixtures_1.permissionDefinitions_limitOwned,
permissionDefinitionDefaults: permissionDefinitions_fixtures_1.permissionDefinitionDefaults,
limitOwnReduce: ({ user, limitOwneds }) => _.overSome(limitOwneds.map((limitOwned) => limitOwned({ user }))),
}).build();
describe('SuperAwesome Permissions', () => {
describe('Basic usage & error handling', () => {
describe('PermissionsDefinitions validations at addDefinitions phase', () => {
it('throws error when PD lacks "resource"', () => {
const errRE = /InvalidPermissionDefinitionError: missing "resource"/;
expect(() => new Permissions_1.Permissions({ permissionDefinitions: { roles: ['ADMIN'], grant: ['foo'] } })).toThrow(errRE);
expect(() => {
const p = new Permissions_1.Permissions();
p.addDefinitions({ roles: ['ADMIN'], grant: ['foo'] });
}).toThrow(errRE);
});
it('throws error when PD lacks "role"', () => {
const errRE = /InvalidPermissionDefinitionError: missing "roles"/;
expect(() => new Permissions_1.Permissions({ permissionDefinitions: { resource: 'document', grant: ['foo'] } })).toThrow(errRE);
expect(() => {
const p = new Permissions_1.Permissions();
p.addDefinitions({ resource: 'document', grant: ['foo'] });
}).toThrow(errRE);
});
it('throws error when PD lacks "grant"', () => {
const errRE = /InvalidPermissionDefinitionError: missing or empty "grant"/;
expect(() => new Permissions_1.Permissions({
permissionDefinitions: { resource: 'document', roles: ['someRole'] },
})).toThrow(errRE);
});
describe('redefining actions attributes', () => {
it('throws error when redefined attributes are DIFFERENT for same role & resource', () => expect(() => new Permissions_1.Permissions({
permissionDefinitions: [
{
resource: 'document',
roles: ['someRole'],
grant: {
'list:any': ['foo', 'bar'],
},
},
{
resource: 'document',
roles: ['someRole'],
grant: {
'list:any': ['bar', 'foo'],
},
},
],
})).toThrow('InvalidPermissionDefinitionError: addDefinitions() redefining action error'));
it('just warns when redefined attributes are SAME for same role & resource', () => {
const oldLogger = logger_1.getLogger();
logger_1.setLogger({
warn(msg, data) {
process.stdout.write(msg, data);
},
debug: _.noop,
});
const output = test_console_1.stdout.inspectSync(() => new Permissions_1.Permissions({
permissionDefinitions: [
{
resource: 'document',
roles: ['matchingRole', 'someOtherRole'],
grant: {
'list:any': ['foo', 'bar'],
},
},
{
resource: 'document',
roles: ['matchingRole', 'anotherRole'],
grant: {
'list:any': ['foo', 'bar'],
},
},
],
}));
expect(output[0]).toMatch('addDefinitions() redefining action in a PD with same attributes is obsolete:');
logger_1.setLogger(oldLogger);
});
it('all is good for different roles', () => {
const output = test_console_1.stdout.inspectSync(() => new Permissions_1.Permissions({
permissionDefinitions: [
{
resource: 'document',
roles: ['matchingRole', 'someOtherRole'],
grant: {
'list:any': ['foo', 'bar'],
},
},
{
resource: 'document',
roles: ['nonMatchingRole', 'anotherRole'],
grant: {
'list:any': ['foo', 'bar'],
},
},
],
}));
expect(output[0]).toBeUndefined();
});
});
describe('Validating ownership hooks', () => {
describe('In the current added PD', () => {
const permissionDefinition = {
roles: ['ADMIN'],
resource: 'document',
grant: ['foo:own'],
};
it.each([
[Object.assign({}, permissionDefinition)],
[Object.assign(Object.assign({}, permissionDefinition), { isOwner: _.noop })],
[Object.assign(Object.assign({}, permissionDefinition), { listOwner: _.noop })],
[Object.assign(Object.assign({}, permissionDefinition), { limitOwner: _.noop })],
])(`throws error @addDefinitions if PD has 'own' action but NO 'isOwner' or 'listOwned' for this role can be found`, (pd) => {
expect(() => new Permissions_1.Permissions({ permissionDefinitions: pd })).toThrow(/SA-Permissions: in addDefinitions\(\) PermissionDefinition has 'own' action but no/);
});
});
it(`doesnt throw error @addDefinitions if PD has no 'own' actions`, () => {
expect(() => {
new Permissions_1.Permissions({
permissionDefinitions: [
{
roles: ['EMPLOYEE', 'ADMIN', 'SOME_ROLE'],
resource: 'document',
grant: ['foo:any'],
},
],
}).build();
}).not.toThrow(/./);
});
it(`throws error @addDefinitions if PD has 'own' action and has both 'listOwned' & 'limitOwned' in the same PD`, () => {
expect(() => {
new Permissions_1.Permissions({
permissionDefinitions: [
{
roles: ['EMPLOYEE', 'ADMIN', 'SOME_ROLE'],
resource: 'document',
grant: ['foo:own'],
isOwner: _.noop,
limitOwned: _.noop,
listOwned: _.noop,
},
{
roles: ['ADMIN', 'EMPLOYEE'],
resource: 'document',
grant: ['bar:own'],
},
],
}).build();
}).toThrow(/SA-Permissions: in addDefinitions\(\) found BOTH "listOwned" & "limitOwned" callbacks in the added PermissionDefinition/);
});
it(`throws error @addDefinitions if PD has 'own' action and can find PDs resulting having both 'listOwned' & 'limitOwned' for this resource`, () => {
expect(() => {
new Permissions_1.Permissions({
permissionDefinitions: [
{
roles: ['EMPLOYEE', 'ADMIN', 'SOME_ROLE'],
resource: 'document',
grant: ['foo:own'],
isOwner: _.noop,
limitOwned: _.noop,
},
{
roles: ['ADMIN', 'EMPLOYEE'],
resource: 'document',
grant: ['bar:own'],
isOwner: _.noop,
listOwned: _.noop,
},
],
}).build();
}).toThrow(/SA-Permissions: in addDefinitions\(\) found BOTH "listOwned" & "limitOwned" callbacks in some PermissionDefinition/);
});
});
});
describe(`grantPermit() phase / after build()`, () => {
it('throws error @addDefinitions if addDefinitions is called after build()', () => {
const permissions2 = new Permissions_1.Permissions({
permissionDefinitions: {
roles: ['ADMIN'],
resource: 'document',
grant: ['foo'],
},
});
permissions2.build();
expect(() => permissions2.addDefinitions({
resource: 'document',
roles: ['SOME_ROLE'],
grant: ['foo'],
})).toThrow(/InvalidInvocation: calling addDefinitions\(\) after having build\(\)/);
});
it('doesnt throw error on grantPermit with empty user.roles', async () => {
const permissions2 = new Permissions_1.Permissions({
permissionDefinitions: {
roles: ['ADMIN'],
resource: 'document',
grant: ['foo'],
},
});
permissions2.build();
const permit2 = await permissions2.grantPermit({
resource: 'document',
user: { roles: [], id: 1 },
action: 'foo',
});
expect(permit2).toBeInstanceOf(Permit_class_1.Permit);
expect(permit2.granted).toBe(false);
expect(permit2.anyGranted).toBe(false);
expect(permit2.anyAttributes).toStrictEqual([]);
expect(permit2.ownGranted).toBe(false);
expect(permit2.ownAttributes).toStrictEqual([]);
expect(await permit2.attributes()).toStrictEqual([]);
});
it('throws error on grantPermit with invalid resource', async () => {
const permissions2 = new Permissions_1.Permissions({
permissionDefinitions: {
roles: ['ADMIN'],
resource: 'document',
grant: ['foo'],
},
});
permissions2.build();
await expect(permissions2.grantPermit({
resource: 'invalidResource',
user: { roles: ['ADMIN'], id: 1 },
action: 'foo',
})).rejects.toThrow(/Invalid resource: "invalidResource"/);
});
describe('Invalid actions', () => {
it('throws error on grantPermit with missing action (from AccessControl::permission()', async () => {
const permissions2 = new Permissions_1.Permissions({
permissionDefinitions: {
roles: ['ADMIN'],
resource: 'document',
grant: ['foo'],
},
});
permissions2.build();
await expect(permissions2.grantPermit({
resource: 'document',
user: { roles: ['ADMIN'], id: 1 },
action: 'invalidAction',
})).rejects.toThrow(/Invalid action: invalidAction/);
});
it('throws error on grantPermit with invalid action with colon (from SA-Permissions)', async () => {
const permissions2 = new Permissions_1.Permissions({
permissionDefinitions: {
roles: ['ADMIN'],
resource: 'document',
grant: ['foo:any'],
},
});
permissions2.build();
await expect(permissions2.grantPermit({
resource: 'document',
user: { roles: ['ADMIN'], id: 1 },
action: 'foo:any',
})).rejects.toThrow(/Invalid action structure: "foo:any"/);
});
});
describe('Ownership hooks', () => {
it('are called once per isOwn()/listOwn() call (they are added as _.uniq to Permit)', async () => {
const isOwner = jest.fn().mockResolvedValue(false);
const isOwner2 = jest.fn().mockResolvedValue(false);
const listOwned = jest.fn().mockResolvedValue([1, 2, 3]);
const listOwned2 = jest.fn().mockResolvedValue([3, 4, 5]);
const permissions2 = new Permissions_1.Permissions({
permissionDefinitions: [
{
roles: ['ROLE1'],
resource: 'document',
grant: ['someOwnAction:own'],
isOwner,
listOwned,
},
{
roles: ['ROLE2'],
resource: 'document',
grant: ['someOwnAction:own'],
isOwner,
listOwned,
},
{
roles: ['ROLE3'],
resource: 'document',
grant: ['someOwnAction:own'],
isOwner: isOwner2,
listOwned: listOwned2,
},
],
}).build();
const permit2 = await permissions2.grantPermit({
action: 'someOwnAction',
resource: 'document',
user: { id: 1, roles: ['ROLE1', 'ROLE2', 'ROLE3'] },
});
const isOwnDocumentId = await permit2.isOwn(999);
expect(isOwner.mock.calls.length).toBe(1);
expect(isOwner2.mock.calls.length).toBe(1);
expect(permit2._isOwners.length).toBe(2);
expect(isOwnDocumentId).toBe(false);
const ownResourceIds = await permit2.listOwn();
expect(listOwned.mock.calls.length).toBe(1);
expect(listOwned2.mock.calls.length).toBe(1);
expect(permit2._listOwneds.length).toBe(2);
expect(ownResourceIds).toEqual([1, 2, 3, 4, 5]);
});
});
});
});
describe('example permissionDefinitions based specs', () => {
describe(`Possession 'any' permission checks`, () => {
_.each([undefined, 100, 200, data_fixtures_1.USERS.superAdmin5.id * 100, nonOwnResourceId], (resourceId) => it(`grants SUPER_ADMIN to read ANY document: ${resourceId}`, async () => {
expect((await permissions.grantPermit({
user: data_fixtures_1.USERS.superAdmin5,
resource: 'document',
action: 'read',
resourceId,
})).granted).toEqual(true);
}));
});
describe(`Possession 'own' permission checks:`, () => {
describe('listOwn() & limitOwn() for owned resources', () => {
describe(`simple role - an employee`, () => {
const authQuery = {
user: data_fixtures_1.USERS.employee1,
resource: 'document',
action: 'browse',
};
it('listOwn()', async () => {
const permit2 = await permissions.grantPermit(authQuery);
expect(await permit2.listOwn()).toEqual([1, 10, 100]);
});
it('limitOwn()', async () => {
const permit2 = await permissions_limitOwned.grantPermit(authQuery);
expect(data_fixtures_1.ALL_DOCUMENTS.filter(permit2.limitOwn()).map((doc) => doc.id)).toIncludeSameMembers([1, 10, 100]);
});
});
describe(`inherit from multiple roles - a Manager & CompanyAdmin`, () => {
const user = data_fixtures_1.USERS.managerAndCompanyAdmin7;
const authQuery = {
user,
resource: 'document',
action: 'read',
};
let allowedDocIds;
beforeAll(async () => {
allowedDocIds = _.uniq([
...(await data_fixtures_1.listOwned_DocsOfMeAndMyManagedUsers(user)),
...(await data_fixtures_1.listOwned_DocsOfMeAndMyCompanyUsers(user)),
]);
});
it('listOwn()', async () => {
const permit2 = await permissions.grantPermit(authQuery);
expect(await permit2.listOwn()).toIncludeSameMembers(allowedDocIds);
});
it('limitOwn()', async () => {
const permit2 = await permissions_limitOwned.grantPermit(authQuery);
expect(data_fixtures_1.ALL_DOCUMENTS.filter(permit2.limitOwn()).map((doc) => doc.id)).toIncludeSameMembers(allowedDocIds);
});
});
});
describe(`Denies or grants based on possession & ownership of known/unknown resourceId:`, () => {
// eslint-disable-next-line guard-for-in
for (const crudAction in ECrudActions) {
it(`denies EMPLOYEE to CRUD ${crudAction} ANY document (of unknown resourceId), but allows OWN resourceId later on`, async () => {
const user = data_fixtures_1.USERS.employee1;
const permit2 = await permissions.grantPermit({
user,
resource: 'document',
action: crudAction,
});
expect(permit2.granted).toBe(true);
expect(permit2.ownGranted).toBe(true);
// any is NOT granted
expect(permit2.anyGranted).toBe(false);
// but own resourceId is granted
expect(await permit2.isOwn(data_fixtures_1.USERS.employee1.id * 100)).toBe(true);
});
it(`denies EMPLOYEE to CRUD ${crudAction} NOT OWNED document`, async () => {
const user = data_fixtures_1.USERS.employee1;
const permit2 = await permissions.grantPermit({
user,
resource: 'document',
action: crudAction,
resourceId: nonOwnResourceId,
});
expect(permit2.granted).toBe(false);
expect(permit2.anyGranted).toBe(false);
expect(permit2.ownGranted).toBe(false);
});
it(`grants EMPLOYEE to CRUD ${crudAction} OWNED document`, async () => {
const user = data_fixtures_1.USERS.employee1;
const ownResourceId = data_fixtures_1.USERS.employee1.id * 100;
const permit2 = await permissions.grantPermit({
user,
resource: 'document',
action: crudAction,
resourceId: ownResourceId,
});
expect(permit2.granted).toBe(true);
expect(permit2.ownGranted).toBe(true);
expect(permit2.anyGranted).toBe(false);
});
it(`grants SUPER_ADMIN to CRUD ${crudAction} NOT OWNED document`, async () => {
const user = data_fixtures_1.USERS.superAdmin5;
const permit2 = await permissions.grantPermit({
user,
resource: 'document',
action: crudAction,
resourceId: nonOwnResourceId,
});
expect(permit2.granted).toBe(true);
expect(permit2.anyGranted).toBe(true);
// if anyGranted, we always have ownGranted
expect(permit2.ownGranted).toBe(true);
// we still know it's not an own resourceId
expect(await permit2.isOwn(nonOwnResourceId)).toBe(false);
// but recognise our own resourceId
expect(await permit2.isOwn(user.id * 100)).toBe(true);
});
}
it(`if isOnwer or listOwned is defined in PD, but the grant was for 'any', we should still be able to use isOwn & listOwn.`, async () => {
const permit2 = await permissions.grantPermit({
user: data_fixtures_1.USERS.employee1,
resource: 'document',
action: 'browse',
});
expect(permit2.granted).toEqual(true);
expect(permit2.anyGranted).toEqual(true);
expect(permit2.ownGranted).toEqual(true);
expect(await permit2.isOwn(100)).toEqual(true);
expect(await permit2.isOwn(200)).toEqual(false);
expect(await permit2.listOwn()).toEqual([1, 10, 100]);
});
});
describe(`Can override default possession:`, () => {
it(`grants EMPLOYEE to 'list' ANY document`, async () => {
expect((await permissions.grantPermit({
user: data_fixtures_1.USERS.employee1,
resource: 'document',
action: 'list',
})).granted).toEqual(true);
});
});
_.each([1, 10, 100, 2, 20, 200, 4, 40, 400], (resourceId) => it('grants EMPLOYEE_MANAGER to read OWNED documents of managed users + created by self', async () => {
expect((await permissions.grantPermit({
user: data_fixtures_1.USERS.employeeManager2,
resource: 'document',
action: 'read',
resourceId,
})).granted).toEqual(true);
}));
_.each([3, 5, 6], (resourceId) => it('denies EMPLOYEE_MANAGER to read:own NON-OWNED documents (of non-managed users)', async () => {
expect((await permissions.grantPermit({
user: data_fixtures_1.USERS.employeeManager2,
resource: 'document',
action: 'read',
resourceId,
})).granted).toEqual(false);
}));
});
describe(`Multiple Roles:`, () => {
it(`respects widening granting Role permissions (admin's grant should win)`, async () => {
expect((await permissions.grantPermit({
user: data_fixtures_1.USERS.employeeAndSuperAdmin8,
resource: 'document',
action: 'delete',
})).granted).toEqual(true);
});
});
describe('retrieves correct PD data (roles, actions, resources) without polluting other instances', () => {
let permissions2; // the other instance, should not pollute ;-)
beforeEach(() => {
permissions2 = new Permissions_1.Permissions({
permissionDefinitions: [
{
roles: ['FOO_ROLE'],
resource: 'fooResource',
grant: { fooAction: ['*'] },
},
],
}).build();
});
it(`returns all known getResources()`, () => {
expect(permissions.getResources()).toEqual(['comment', 'document', 'securityHole']);
expect(permissions2.getResources()).toEqual(['fooResource']);
});
it(`returns all known getRoles()`, () => {
expect(permissions.getRoles()).toEqual([
'EMPLOYEE',
'EMPLOYEE_MANAGER',
'QA_MANAGER',
'COMPANY_ADMIN',
'SUPER_ADMIN',
'GOD',
].sort());
expect(permissions2.getRoles()).toEqual(['FOO_ROLE']);
});
it(`returns all known getResources()`, () => {
expect(permissions.getActions()).toEqual([
'publish',
'browse',
'create',
'read',
'update',
'delete',
'like',
'list',
'preview',
'share',
].sort());
expect(permissions2.getActions()).toEqual(['create', 'read', 'update', 'delete', 'fooAction'].sort());
});
});
describe(`Supports wildcards '*':`, () => {
describe(`wildcard '*' for Action & Resource, for God like roles`, () => {
for (const action of permissions.getActions()) {
for (const resource of permissions.getResources()) {
for (const resourceId of [
undefined,
100,
200,
data_fixtures_1.USERS.superAdmin5.id * 100,
nonOwnResourceId,
]) {
it(`grants GOD to ANY *Action (${action}) to ANY *Resource (${resource}): ${resourceId}`, async () => {
expect((await permissions.grantPermit({
user: data_fixtures_1.USERS.god6,
resource,
action,
resourceId,
})).granted).toEqual(true);
});
}
}
}
});
describe(`wildcard '*' for Roles, for "permit everyone" like Resources / Routes`, () => {
for (const role of permissions.getRoles()) {
for (const resourceId of [
undefined,
100,
200,
data_fixtures_1.USERS.superAdmin5.id * 100,
nonOwnResourceId,
]) {
it(`grants ${role} to preview to ANY securityHole:`, async () => {
expect((await permissions.grantPermit({
user: { id: 999, roles: [role] },
resource: 'securityHole',
action: 'preview',
resourceId,
})).granted).toEqual(true);
});
}
}
});
});
describe(`Error handling`, () => {
it(`filters out unknown roles but it does "logger.warn" about them once per instance`, async () => {
const user = Object.assign(Object.assign({}, data_fixtures_1.USERS.employee1), { roles: [...data_fixtures_1.USERS.employee1.roles, 'UNKNOWN_ROLE'] });
// const inspect = stdout.inspect();
const oldLogger = logger_1.getLogger();
const mockLogger = Object.create(oldLogger.constructor.prototype);
mockLogger.warn = jest.fn();
logger_1.setLogger(mockLogger);
for (let i = 1; i < 3; i++) {
const { granted } = await permissions.grantPermit({
user,
resource: 'document',
action: 'list',
});
expect(granted).toEqual(true);
}
expect(mockLogger.warn.mock.calls.length).toBe(1);
expect(mockLogger.warn.mock.calls[0][0]).toBe(`SA-Permissions(): at grantPermit(), role not found: UNKNOWN_ROLE (will not warn again about this role)`);
logger_1.setLogger(oldLogger);
});
});
describe('Multiple roles', () => {
_.each([100, 600], (resourceId) => it(`denies QA_MANAGER to read non owned documents ${resourceId} (of non-managed users)`, async () => {
expect((await permissions.grantPermit({
user: data_fixtures_1.USERS.qaManager3,
resource: 'document',
action: 'read',
resourceId,
})).granted).toEqual(false);
}));
describe('Inheriting multiple isOwner / listOwned from multiple roles', () => {
const user = data_fixtures_1.USERS.managerAndCompanyAdmin7;
let allowedDocIds;
beforeAll(async () => {
allowedDocIds = _.uniq([
...(await data_fixtures_1.listOwned_DocsOfMeAndMyManagedUsers(user)),
...(await data_fixtures_1.listOwned_DocsOfMeAndMyCompanyUsers(user)),
]);
});
it(`grants managerAndCompanyAdmin7 to read:own all OWNED documents
(created by user + of managed users + of company users)`, async () => {
for (const resourceId of allowedDocIds) {
const permit2 = await permissions.grantPermit({
user,
resource: 'document',
action: 'read',
resourceId,
});
expect(permit2.granted).toBeTrue();
}
});
it(`returns a Permit::listOwn() produces a list of owned docs
(created by user + of managed users + of company users) even if grant is denied because of resourceId`, async () => {
for (const resourceId of data_fixtures_1.ALL_DOCUMENTS_IDS) {
const permit2 = await permissions.grantPermit({
user,
resource: 'document',
action: 'read',
resourceId,
});
expect(await permit2.listOwn()).toIncludeSameMembers(allowedDocIds);
}
});
it(`denies managerAndCompanyAdmin7 to read NOT OWNED document
(NOT created by user + of managed users + of company users)`, async () => {
for (const resourceId of _.difference(data_fixtures_1.ALL_DOCUMENTS_IDS, allowedDocIds)) {
const permit2 = await permissions.grantPermit({
user,
resource: 'document',
action: 'read',
resourceId,
});
expect(permit2.granted).toBeFalse();
}
});
});
});
describe(`Permit::attributes(), pick() & filterPick():`, () => {
const createItemWithId = (resourceItemKeys, id, addId = true) => _.reduce(resourceItemKeys, (item, key) => {
item[key] = key + id;
return item;
}, addId ? { id: Number(id) } : {});
const pickAndCheckEqualSet = async (permit2, resourceItem, resourceId, expectedKeys) => {
const pickedItem = resourceId
? await permit2.pick(resourceItem, resourceId)
: await permit2.pick(resourceItem);
expect(_.keys(pickedItem)).toIncludeSameMembers(expectedKeys);
expect(_.values(_.pickBy(pickedItem, (v, key) => key !== 'id'))).toIncludeSameMembers(expectedKeys.filter((key) => key !== 'id'));
};
const filterPickAndCheckEqualSet = async (permit2, resourceItems, expectedItems) => {
const pickedItems = await permit2.filterPick(resourceItems);
expect(pickedItems).toIncludeSameMembers(expectedItems);
};
const resourceItemKeys = [
'title',
'content',
'views',
'likes',
'publishDate',
'createDate',
'revision',
'deletedAt',
'confidential',
'withLimits',
];
const aResourceItem = createItemWithId(resourceItemKeys, '', false);
describe(`granted to a user having a single role:`, () => {
const user = data_fixtures_1.USERS.employee1;
const ownResourceId = user.id * 100;
describe(`on action granted with both 'own' & 'any' possession:`, () => {
const action = 'list';
const anyAttributes = ['title', 'createDate'];
const ownAttributes = ['*'];
const ownPickedAttributes = [
// from EMPLOYEE
'title',
'content',
'views',
'likes',
'publishDate',
'createDate',
'revision',
'deletedAt',
'withLimits',
'confidential',
];
beforeAll(async () => {
permit = await permissions.grantPermit({
user,
action,
resource: 'document',
});
// for sanity
expect(await permit.isOwn(ownResourceId)).toEqual(true);
expect(await permit.isOwn(nonOwnResourceId)).toEqual(false);
});
describe(`Permit::attributes():`, () => {
it(`returns anyAttributes if no resourceId passed`, async () => {
expect(await permit.attributes()).toEqual(anyAttributes);
expect(permit.anyAttributes).toEqual(anyAttributes);
});
it(`returns anyAttributes if NOT own resourceId passed`, async () => {
expect(await permit.attributes(nonOwnResourceId)).toEqual(anyAttributes);
});
it(`returns ownAttributes if own resourceId passed`, async () => {
expect(await permit.attributes(ownResourceId)).toEqual(ownAttributes);
expect(permit.ownAttributes).toEqual(ownAttributes);
});
});
describe(`Permit::pick():`, () => {
it(`returns anyAttributes if when no resourceId passed`, async () => {
await pickAndCheckEqualSet(permit, aResourceItem, null, anyAttributes);
});
it(`returns ownAttributes if when no resourceId passed`, async () => {
await pickAndCheckEqualSet(permit, aResourceItem, ownResourceId, ownPickedAttributes);
});
});
describe(`Permit::filterPick():`, () => { });
});
describe(`on action granted only with 'own' possession:`, () => {
const action = 'read';
const ownAttributes = ['*', '!price', '!confidential'];
beforeAll(async () => {
permit = await permissions.grantPermit({
user,
action,
resource: 'document',
});
// sanity
expect(await permit.isOwn(ownResourceId)).toEqual(true);
expect(await permit.isOwn(nonOwnResourceId)).toEqual(false);
expect(await permit.anyGranted).toEqual(false);
expect(await permit.ownGranted).toEqual(true);
});
describe(`Permit::attributes():`, () => {
it(`returns [] if no resourceId passed`, async () => {
expect(await permit.attributes()).toEqual([]);
});
it(`returns [] if NOT own resourceId passed`, async () => {
expect(await permit.isOwn(nonOwnResourceId)).toEqual(false);
expect(await permit.attributes(nonOwnResourceId)).toEqual([]);
expect(permit.anyAttributes).toEqual([]);
});
it(`returns ownAttributes if own resourceId passed`, async () => {
expect(await permit.isOwn(ownResourceId)).toEqual(true);
expect(await permit.attributes(ownResourceId)).toEqual(ownAttributes);
expect(permit.ownAttributes).toEqual(ownAttributes);
});
});
describe(`Permit::pick():`, () => { });
describe(`Permit::filterPick():`, () => { });
});
describe(`on action granted only with 'any' possession:`, () => {
let permit2;
const action = 'browse';
const anyAttributes = ['title', 'content'];
beforeAll(async () => {
permit2 = await permissions.grantPermit({
user,
resource: 'document',
action,
});
});
describe(`Permit::attributes():`, () => {
it(`permit.ownAttributes is equal to anyAttributes`, () => {
expect(permit2.ownAttributes).toEqual(anyAttributes);
});
it(`returns anyAttributes if no resourceId passed`, async () => {
expect(await permit2.attributes()).toEqual(anyAttributes);
});
it(`returns anyAttributes if NOT own resourceId passed`, async () => {
expect(await permit2.isOwn(nonOwnResourceId)).toEqual(false);
expect(await permit2.attributes(nonOwnResourceId)).toEqual(anyAttributes);
});
it(`returns anyAttributes even if own resourceId passed`, async () => {
expect(await permit2.isOwn(ownResourceId)).toEqual(true);
expect(await permit2.attributes(ownResourceId)).toEqual(anyAttributes);
});
});
describe(`Permit::pick():`, () => { });
describe(`Permit::filterPick():`, () => { });
});
});
describe(`granted to a user having multiple roles:`, () => {
const user = data_fixtures_1.USERS.employeeAndSuperAdmin8;
const ownResourceId = user.id * 100;
describe(`on action granted with both 'own' & 'any' possession, by diff roles:`, () => {
const action = 'delete';
const anyAttributes = ['deletedAt']; // from SUPER_ADMIN
const ownAttributes = ['*']; // from EMPLOYEE
const ownPickedAttributes = [
// from EMPLOYEE
'title',
'content',
'views',
'likes',
'publishDate',
'createDate',
'revision',
'deletedAt',
'withLimits',
'confidential',
];
beforeAll(async () => {
permit = await permissions.grantPermit({
user,
action,
resource: 'document',
});
});
describe(`Permit::attributes():`, () => {
it(`returns anyAttributes if no resourceId passed`, async () => {
expect(await permit.attributes()).toEqual(anyAttributes);
});
it(`returns anyAttributes if NOT own resourceId passed`, async () => {
expect(await permit.isOwn(nonOwnResourceId)).toEqual(false);
expect(await permit.attributes(nonOwnResourceId)).toEqual(anyAttributes);
});
it(`returns ownAttributes if own resourceId passed`, async () => {
expect(await permit.isOwn(ownResourceId)).toEqual(true);
expect(await permit.attributes(ownResourceId)).toEqual(ownAttributes);
});
});
describe(`Permit::pick():`, () => {
describe(`picks anyAttributes`, () => {
it(`when no resourceId passed`, async () => {
await pickAndCheckEqualSet(permit, aResourceItem, null, anyAttributes);
});
it(`when an non own resourceId passed as 2nd param`, async () => {
await pickAndCheckEqualSet(permit, aResourceItem, nonOwnResourceId, anyAttributes);
});
it(`when an non own resourceId passed as resourceItem.id`, async () => {
await pickAndCheckEqualSet(permit, Object.assign(Object.assign({}, aResourceItem), { id: nonOwnResourceId }), null, anyAttributes);
});
it(`when an non own resourceId passed as 2nd param, even if resourceItem.id is own`, async () => {
await pickAndCheckEqualSet(permit, Object.assign(Object.assign({}, aResourceItem), { id: ownResourceId }), nonOwnResourceId, anyAttributes);
});
});
describe(`picks ownAttributes`, () => {
it(`when own resourceId passed as 2nd param`, async () => {
await pickAndCheckEqualSet(permit, aResourceItem, ownResourceId, ownPickedAttributes);
});
it(`when own resourceId passed as resourceItem.id`, async () => {
await pickAndCheckEqualSet(permit, Object.assign(Object.assign({}, aResourceItem), { id: ownResourceId }), null, [
...ownPickedAttributes,
'id',
]);
});
it(`when own resourceId passed as 2nd param, even if resourceItem.id isnt own`, async () => {
await pickAndCheckEqualSet(permit, Object.assign(Object.assign({}, aResourceItem), { id: nonOwnResourceId }), ownResourceId, [...ownPickedAttributes, 'id']);
});
});
});
describe(`Permit::filterPick():`, () => {
it(`returns a mapped array of all items, picking the right attributes for each item, based on ownership passed as resourceItem.id`, async () => {
await filterPickAndCheckEqualSet(permit, [
createItemWithId(resourceItemKeys, ownResourceId / 10),
createItemWithId(resourceItemKeys, nonOwnResourceId),
createItemWithId(resourceItemKeys, ownResourceId),
createItemWithId(resourceItemKeys, nonOwnResourceId * 10),
], [
createItemWithId(ownPickedAttributes, ownResourceId / 10),
createItemWithId(anyAttributes, nonOwnResourceId, false),
createItemWithId(ownPickedAttributes, ownResourceId),
createItemWithId(anyAttributes, nonOwnResourceId * 10, false),
]);
});
});
});
describe(`on action granted only with 'own' possession, only on one Role:`, () => {
const action = 'publish';
const ownAttributes = ['title', 'content', 'createDate'].sort(); // from EMPLOYEE
beforeAll(async () => {
permit = await permissions.grantPermit({
user,
action,
resource: 'document',
});
});
describe(`Permit::pick():`, () => {
it(`returns [] if no resourceId passed`, async () => {
expect(await permit.attributes()).toEqual([]);
});
it(`returns [] if NOT own resourceId passed`, async () => {
expect(await permit.isOwn(nonOwnResourceId)).toEqual(false);
expect(await permit.attributes(nonOwnResourceId)).toEqual([]);
});
it(`returns ownAttributes if own resourceId passed`, async () => {
expect(await permit.isOwn(ownResourceId)).toEqual(true);
expect