UNPKG

@superawesome/permissions

Version:

Fine grained permissions / access control with ownerships & attribute picking, done right.

917 lines 59.5 kB
"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