diff --git a/package.json b/package.json index c07b944..19d8128 100644 --- a/package.json +++ b/package.json @@ -32,36 +32,36 @@ "cors": "^2.8.5", "express": "^4.18.2", "express-basic-auth": "^1.2.1", - "express-rate-limit": "^6.7.0", + "express-rate-limit": "^6.7.1", "express-validator": "^7.0.1", "graphql": "^16.7.1", - "graphql-http": "^1.19.0", + "graphql-http": "^1.20.0", "graphql-tag": "^2.12.6", "helmet": "^7.0.0", - "jsonwebtoken": "^9.0.0", + "jsonwebtoken": "^9.0.1", "moment": "^2.29.4", - "mongodb": "^5.6.0", - "mongoose": "^7.3.1", + "mongodb": "^5.7.0", + "mongoose": "^7.3.2", "pino": "^8.14.1", - "pino-pretty": "^10.0.0", - "swagger-ui-express": "^4.6.3", + "pino-pretty": "^10.0.1", + "swagger-ui-express": "^5.0.0", "switcher-client": "^3.1.8", "validator": "^13.9.0" }, "devDependencies": { - "@babel/cli": "^7.22.5", - "@babel/core": "^7.22.5", - "@babel/node": "^7.22.5", - "@babel/preset-env": "^7.22.5", + "@babel/cli": "^7.22.6", + "@babel/core": "^7.22.8", + "@babel/node": "^7.22.6", + "@babel/preset-env": "^7.22.7", "@babel/register": "^7.22.5", - "babel-jest": "^29.5.0", + "babel-jest": "^29.6.1", "babel-polyfill": "^6.26.0", "env-cmd": "^10.1.0", "eslint": "^8.44.0", - "jest": "^29.5.0", + "jest": "^29.6.1", "jest-sonar-reporter": "^2.0.0", "node-notifier": "^10.0.1", - "nodemon": "^2.0.22", + "nodemon": "^3.0.1", "sinon": "^15.2.0", "supertest": "^6.3.3" }, diff --git a/src/helpers/index.js b/src/helpers/index.js index 13dfacf..11d3a43 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -119,16 +119,32 @@ export async function verifyOwnership(admin, element, domainId, action, routerTy } const teams = await getTeams({ _id: { $in: admin.teams }, domain: domain._id, active: true }); - if (teams.length && admin.teams.length) { - for (const team of teams) { - if (cascade) { - element = await verifyPermissionsCascade(team, element, action, routerType); - } else { - element = await verifyPermissions(team, element, action, routerType); - } - } - } else { + if (!teams.length || !admin.teams.length) { throw new PermissionError('It was not possible to find any team that allows you to proceed with this operation'); + } + + let hasPermission = []; + let allowedElement; + for (const team of teams) { + if (cascade) { + allowedElement = await verifyPermissionsCascade(team, element, action, routerType); + } else { + allowedElement = await verifyPermissions(team, element, action, routerType); + } + + if (allowedElement) { + hasPermission.push(allowedElement); + } + } + + if (!hasPermission.length) { + throw new PermissionError('Action forbidden'); + } + + if (Array.isArray(element)) { + hasPermission = hasPermission.flat(Infinity); + hasPermission = [...new Set(hasPermission)]; + return hasPermission; } return element; diff --git a/src/helpers/permission.js b/src/helpers/permission.js index 250faa4..4034681 100644 --- a/src/helpers/permission.js +++ b/src/helpers/permission.js @@ -1,4 +1,3 @@ -import { PermissionError } from '../exceptions'; import { ActionTypes, RouterTypes } from '../models/permission'; import { getPermission, getPermissions } from '../services/permission'; @@ -10,11 +9,11 @@ export async function verifyPermissions(team, element, action, routerType) { router: { $in: [routerType, RouterTypes.ALL] } }); - if (permission) { - return verifyIdentifiers(permission, element); - } else { - throw new PermissionError(`Permission not found for this operation: '${action}' - '${routerType}'`); + if (!permission) { + return undefined; } + + return verifyIdentifiers(permission, element); } export async function verifyPermissionsCascade(team, element, action, routerType) { @@ -66,13 +65,12 @@ function verifyIdentifiers(permission, element) { return element; } } - } else { - if (permission.values.includes(element[`${permission.identifiedBy}`])) { - return element; - } + } else if (permission.values.includes(element[`${permission.identifiedBy}`])) { + return element; } - } else { - return element; + + return undefined; } - throw new PermissionError('It was not possible to match the requiring element to the current permission'); + + return element; } \ No newline at end of file diff --git a/tests/fixtures/db_team_permission.js b/tests/fixtures/db_team_permission.js index db91ab4..3a82cbc 100644 --- a/tests/fixtures/db_team_permission.js +++ b/tests/fixtures/db_team_permission.js @@ -76,6 +76,24 @@ export const permission2 = { values: [groupConfig2Document.name] }; +export const permission21Id = new mongoose.Types.ObjectId(); +export const permission21 = { + _id: permission21Id, + action: ActionTypes.READ, + active: true, + router: RouterTypes.GROUP +}; + +export const permission22Id = new mongoose.Types.ObjectId(); +export const permission22 = { + _id: permission22Id, + action: ActionTypes.READ, + active: true, + router: RouterTypes.GROUP, + identifiedBy: KeyTypes.NAME, + values: ['RANDOM_VALUE'] +}; + export const permission3Id = new mongoose.Types.ObjectId(); export const permission3 = { _id: permission3Id, @@ -112,6 +130,15 @@ export const team2 = { permissions: [permission4Id] }; +export const team3Id = new mongoose.Types.ObjectId(); +export const team3 = { + _id: team3Id, + domain: domainId, + name: 'Team 3', + active: true, + permissions: [permission21Id, permission22Id] +}; + export const adminAccountId = new mongoose.Types.ObjectId(); export const adminAccount = { _id: adminAccountId, @@ -119,7 +146,7 @@ export const adminAccount = { email: 'member@admin.com', password: '123123123123', active: true, - teams: [team1Id] + teams: [team1Id, team3Id] }; export const adminAccount2Id = new mongoose.Types.ObjectId(); @@ -162,8 +189,11 @@ export const setupDatabase = async () => { await new Config(configDocument).save(); await new Permission(permission1).save(); await new Permission(permission2).save(); + await new Permission(permission21).save(); + await new Permission(permission22).save(); await new Permission(permission3).save(); await new Permission(permission4).save(); await new Team(team1).save(); await new Team(team2).save(); + await new Team(team3).save(); }; \ No newline at end of file diff --git a/tests/unit-test/verify-ownership.test.js b/tests/unit-test/verify-ownership.test.js index 8e82ee8..e0db7c4 100644 --- a/tests/unit-test/verify-ownership.test.js +++ b/tests/unit-test/verify-ownership.test.js @@ -18,7 +18,10 @@ import { permission3Id, adminAccount2, team1Id, - adminAccount3 + adminAccount3, + permission21Id, + permission4Id, + permission22Id } from '../fixtures/db_team_permission'; import { PermissionError } from '../../src/exceptions'; @@ -48,6 +51,15 @@ afterAll(async () => { describe('Success tests', () => { beforeAll(setupDatabase); + beforeEach(async () => { + await changePermissionStatus(permission1Id, false); + await changePermissionStatus(permission2Id, false); + await changePermissionStatus(permission21Id, false); + await changePermissionStatus(permission22Id, false); + await changePermissionStatus(permission3Id, false); + await changePermissionStatus(permission4Id, false); + }); + test('UNIT_TEAM_PERMISSION_SUITE - Should allow access - Element owner', async () => { try { const element = await verifyOwnership( @@ -64,11 +76,11 @@ describe('Success tests', () => { }); test('UNIT_TEAM_PERMISSION_SUITE - Should allow access - Member has permission to select group', async () => { - // Given - // Enabled Read - Group + //given + //enabled Read - Group (by name) await changePermissionStatus(permission2Id, true); - // Test + //test try { const element = await verifyOwnership( adminAccount, @@ -84,14 +96,14 @@ describe('Success tests', () => { }); test('UNIT_TEAM_PERMISSION_SUITE - Should allow access - Member has permission to select group - Cascade', async () => { - // Given - // Disabled Read - Group + //given + //disabled Read - Group (by name) await changePermissionStatus(permission2Id, false); - // Enabled Read - Config + //enabled Read - Config (by name) await changePermissionStatus(permission3Id, true); - // Test + //test try { const element = await verifyOwnership( adminAccount, @@ -107,30 +119,53 @@ describe('Success tests', () => { } }); - test('UNIT_TEAM_PERMISSION_SUITE - Should allow access - Member has permission to select just one of those', async () => { - // Given - // Enabled Read - Group + test('UNIT_TEAM_PERMISSION_SUITE - Should allow access - Member has permission to select just one of 2 groups', async () => { + //given + //enabled Read - Group (by name) await changePermissionStatus(permission2Id, true); - // Test - try { - let groups = await GroupConfig.find({ domain: domainId }).exec(); - expect(groups.length).toEqual(2); + //disabled Read - Group (all) + await changePermissionStatus(permission21Id, false); - const element = await verifyOwnership( + let groups = await GroupConfig.find({ domain: domainId }).exec(); + expect(groups.length).toEqual(2); + + //test + const element = await verifyOwnership( adminAccount, groups, domainDocument, ActionTypes.READ, RouterTypes.GROUP); - expect(element.length).toEqual(1); - } catch (e) { - expect(e).toBeNull(); - } + expect(element.length).toEqual(1); + }); + + test('UNIT_TEAM_PERMISSION_SUITE - Should allow access and merge team permissions', async () => { + //given + //enabled Read - Group (by name) + await changePermissionStatus(permission2Id, true); + + //enabled Read - Group (all) + await changePermissionStatus(permission21Id, true); + + let groups = await GroupConfig.find({ domain: domainId }).exec(); + expect(groups.length).toEqual(2); + + //test + const element = await verifyOwnership( + adminAccount, + groups, + domainDocument, + ActionTypes.READ, + RouterTypes.GROUP); + + expect(element.length).toEqual(2); }); test('UNIT_TEAM_PERMISSION_SUITE - Should allow access - Member has permission to delete', async () => { + await changePermissionStatus(permission1Id, true); + try { const element = await verifyOwnership( adminAccount, @@ -146,6 +181,8 @@ describe('Success tests', () => { }); test('UNIT_TEAM_PERMISSION_SUITE - Should allow access - Member has permission to select config', async () => { + await changePermissionStatus(permission1Id, true); + await changePermissionStatus(permission3Id, true); await changePermissionAction(permission1Id, ActionTypes.READ); await changePermissionAction(permission3Id, ActionTypes.UPDATE); @@ -167,24 +204,26 @@ describe('Success tests', () => { }); test('UNIT_TEAM_PERMISSION_SUITE - Should allow access - Member has ALL select permissions', async () => { + await changePermissionStatus(permission4Id, true); + try { let element = await verifyOwnership( adminAccount3, - groupConfig2Document, + domainDocument, domainDocument, ActionTypes.READ, - RouterTypes.GROUP); + RouterTypes.DOMAIN); - expect(element._id).toEqual(groupConfig2Document._id); + expect(element).toMatchObject(domainDocument); element = await verifyOwnership( adminAccount3, - domainDocument, + groupConfig2Document, domainDocument, ActionTypes.READ, - RouterTypes.DOMAIN); + RouterTypes.GROUP); - expect(element._id).toEqual(domainDocument._id); + expect(element).toMatchObject(groupConfig2Document); element = await verifyOwnership( adminAccount3, @@ -193,7 +232,7 @@ describe('Success tests', () => { ActionTypes.READ, RouterTypes.CONFIG); - expect(element._id).toEqual(configDocument._id); + expect(element).toMatchObject(configDocument); let groups = await GroupConfig.find({ domain: domainId }).exec(); expect(groups.length).toEqual(2); @@ -214,88 +253,95 @@ describe('Success tests', () => { }); describe('Error tests', () => { + beforeAll(setupDatabase); + + beforeEach(async () => { + await changePermissionStatus(permission1Id, false); + await changePermissionStatus(permission2Id, false); + await changePermissionStatus(permission21Id, false); + await changePermissionStatus(permission22Id, false); + await changePermissionStatus(permission3Id, false); + await changePermissionStatus(permission4Id, false); + }); test('UNIT_TEAM_PERMISSION_SUITE - Should NOT allow access - Permission innactive', async () => { await changePermissionStatus(permission2Id, false); - - try { - const element = await verifyOwnership( + + expect(async () => { + await verifyOwnership( adminAccount, groupConfig2Document, domainDocument, ActionTypes.READ, RouterTypes.GROUP); - - expect(element).toBeNull(); - } catch (e) { - expect(e).toEqual(new PermissionError(`Permission not found for this operation: '${ActionTypes.READ}' - '${RouterTypes.GROUP}'`)); - } finally { - await changePermissionStatus(permission2Id, true); - } + }).rejects.toThrow(new PermissionError(`Action forbidden`)); }); test('UNIT_TEAM_PERMISSION_SUITE - Should NOT allow access - Permission not found', async () => { - try { - const element = await verifyOwnership( + expect(async () => { + await verifyOwnership( adminAccount, domainDocument, domainDocument, ActionTypes.CREATE, RouterTypes.GROUP); - - expect(element).toBeNull(); - } catch (e) { - expect(e).toEqual(new PermissionError(`Permission not found for this operation: '${ActionTypes.CREATE}' - '${RouterTypes.GROUP}'`)); - } + }).rejects.toThrow(new PermissionError(`Action forbidden`)); }); test('UNIT_TEAM_PERMISSION_SUITE - Should NOT allow access - Permission does not match', async () => { - try { - const element = await verifyOwnership( + expect(async () => { + await verifyOwnership( adminAccount, configDocument, domainDocument, ActionTypes.READ, RouterTypes.CONFIG); - - expect(element).toBeNull(); - } catch (e) { - expect(e).toEqual(new Error('It was not possible to match the requiring element to the current permission')); - } + }).rejects.toThrow(new PermissionError(`Action forbidden`)); }); test('UNIT_TEAM_PERMISSION_SUITE - Should NOT allow access - Member does not belong to a team', async () => { - try { - const element = await verifyOwnership( + expect(async () => { + await verifyOwnership( adminAccount2, domainDocument, domainDocument, ActionTypes.READ, RouterTypes.DOMAIN); - - expect(element).toBeNull(); - } catch (e) { - expect(e).toEqual(new Error('It was not possible to find any team that allows you to proceed with this operation')); - } + }).rejects.toThrow(new PermissionError(`It was not possible to find any team that allows you to proceed with this operation`)); }); test('UNIT_TEAM_PERMISSION_SUITE - Should NOT allow access - Team not active', async () => { await changeTeamStatus(team1Id, false); - try { - const element = await verifyOwnership( + expect(async () => { + await verifyOwnership( adminAccount, groupConfig2Document, domainDocument, ActionTypes.READ, RouterTypes.GROUP); + }).rejects.toThrow(new PermissionError(`Action forbidden`)); - expect(element).toBeNull(); - } catch (e) { - expect(e).toEqual(new Error('It was not possible to find any team that allows you to proceed with this operation')); - } finally { - await changeTeamStatus(team1Id, true); - } + // tearDown + await changeTeamStatus(team1Id, true); + }); + + test('UNIT_TEAM_PERMISSION_SUITE - Should NOT allow access - Group name does not exist', async () => { + //given + //enabled Read - Group (by name) + await changePermissionStatus(permission22Id, true); + + let groups = await GroupConfig.find({ domain: domainId }).exec(); + expect(groups.length).toEqual(2); + + expect(async () => { + await verifyOwnership( + adminAccount, + groups, + domainDocument, + ActionTypes.READ, + RouterTypes.GROUP); + }).rejects.toThrow(new PermissionError(`Action forbidden`)); }); }); \ No newline at end of file