From 813d610d21246173359186f123115682d266b18c Mon Sep 17 00:00:00 2001 From: drono Date: Fri, 4 Oct 2024 10:00:25 +0300 Subject: [PATCH 1/8] add a mechanism to migrate users from the old roles to new RBAC roles --- src/upgradeDB.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/upgradeDB.js b/src/upgradeDB.js index 7563a1a2..287672b3 100644 --- a/src/upgradeDB.js +++ b/src/upgradeDB.js @@ -9,6 +9,7 @@ import {KeystoreModel} from './model/keystore' import {UserModel} from './model/users' import {VisualizerModel} from './model/visualizer' import {PassportModel} from './model/passport' +import {RoleModel, roles} from './model/role' function dedupName(name, names, num) { let newName @@ -295,6 +296,51 @@ upgradeFuncs.push({ } }) +upgradeFuncs.push({ + description: 'Create default roles with permissions and update user groups', + func() { + return new Promise(async (resolve, reject) => { + try { + // Create default roles with permissions + for (const [roleName, roleData] of Object.entries(roles)) { + await RoleModel.findOneAndUpdate( + { name: roleName }, + roleData, + { upsert: true, new: true } + ); + logger.info(`Role ${roleName} created or updated`); + } + + const users = await UserModel.find(); + + const userPromises = users.map(async (user) => { + // Convert old boolean flags to new role system + let newGroup = 'manager'; + if ((user.groups && user.groups.includes('admin')) || user.superUser) { + newGroup = 'admin'; + } + + // Update user's groups + user.groups = [newGroup]; + + // Remove old superUser field + user.superUser = undefined; + + return user.save(); + }); + + await Promise.all(userPromises); + + logger.info('Successfully updated user groups based on new role model'); + resolve(); + } catch (err) { + logger.error(`Error updating user groups: ${err}`); + reject(err); + } + }); + } +}); + if (process.env.NODE_ENV === 'test') { exports.upgradeFuncs = upgradeFuncs exports.dedupName = dedupName From 25f97ff6124f8a8001925d7b12bee22ef92ed4e9 Mon Sep 17 00:00:00 2001 From: drono Date: Fri, 4 Oct 2024 12:05:09 +0300 Subject: [PATCH 2/8] add upgrade role tests --- src/model/index.js | 1 + src/model/role.js | 2 +- src/upgradeDB.js | 45 +++++++++++++++++ test/unit/upgradeDBTest.js | 101 ++++++++++++++++++++++++++++++++++++- 4 files changed, 146 insertions(+), 3 deletions(-) diff --git a/src/model/index.js b/src/model/index.js index 58c8b795..118a6d6c 100644 --- a/src/model/index.js +++ b/src/model/index.js @@ -20,3 +20,4 @@ export * from './users' export * from './visualizer' export * from './metrics' export * from './passport' +export * from './role' diff --git a/src/model/role.js b/src/model/role.js index 8bcdc48e..2a41ba8d 100644 --- a/src/model/role.js +++ b/src/model/role.js @@ -137,7 +137,7 @@ const RoleSchema = new Schema({ export const RoleModelAPI = connectionAPI.model('Role', RoleSchema) export const RoleModel = connectionDefault.model('Role', RoleSchema) -const roles = { +export const roles = { admin: { name: 'admin', permissions: { diff --git a/src/upgradeDB.js b/src/upgradeDB.js index 287672b3..c52f2e66 100644 --- a/src/upgradeDB.js +++ b/src/upgradeDB.js @@ -372,6 +372,51 @@ async function upgradeDbInternal() { } } +upgradeFuncs.push({ + description: 'Create default roles with permissions and update user groups', + func() { + return new Promise(async (resolve, reject) => { + try { + // Create default roles with permissions + for (const [roleName, roleData] of Object.entries(roles)) { + await RoleModel.findOneAndUpdate( + { name: roleName }, + roleData, + { upsert: true, new: true } + ); + logger.info(`Role ${roleName} created or updated`); + } + + const users = await UserModel.find(); + + const userPromises = users.map(async (user) => { + // Convert old boolean flags to new role system + let newGroup = 'manager'; + if ((user.groups && user.groups.includes('admin')) || user.superUser) { + newGroup = 'admin'; + } + + // Update user's groups + user.groups = [newGroup]; + + // Remove old superUser field + user.superUser = undefined; + + return user.save(); + }); + + await Promise.all(userPromises); + + logger.info('Successfully updated user groups based on new role model'); + resolve(); + } catch (err) { + logger.error(`Error updating user groups: ${err}`); + reject(err); + } + }); + } +}); + export function upgradeDb(callback) { return upgradeDbInternal() .then((...values) => { diff --git a/test/unit/upgradeDBTest.js b/test/unit/upgradeDBTest.js index 1cf4146b..354be14b 100644 --- a/test/unit/upgradeDBTest.js +++ b/test/unit/upgradeDBTest.js @@ -13,7 +13,8 @@ import { KeystoreModel, PassportModel, UserModel, - VisualizerModel + VisualizerModel, + RoleModel } from '../../src/model' describe('Upgrade DB Tests', () => { @@ -546,4 +547,100 @@ describe('Upgrade DB Tests', () => { passports.length.should.eql(2) }) }) -}) + + describe(`updateFunction4 - Create default roles`, () => { + const upgradeFunc = originalUpgradeFuncs[4].func + + afterEach(async () => { + await RoleModel.deleteMany({}) + }) + + it('should create default roles if they do not exist', async () => { + await upgradeFunc() + + const roles = await RoleModel.find() + roles.length.should.be.exactly(4) + + const roleNames = roles.map(r => r.name) + roleNames.should.containEql('manager') + roleNames.should.containEql('admin') + roleNames.should.containEql('operator') + }) + + it('should not create duplicate roles if they already exist', async () => { + // Create an existing role + await new RoleModel({ name: 'admin' }).save() + + await upgradeFunc() + + const roles = await RoleModel.find() + roles.length.should.be.exactly(3) + + const adminRoles = roles.filter(r => r.name === 'admin') + adminRoles.length.should.be.exactly(1) + }) + + it('should set correct permissions for each role', async () => { + await upgradeFunc() + + const managerRole = await RoleModel.findOne({ name: 'manager' }) + const adminRole = await RoleModel.findOne({ name: 'admin' }) + const operatorRole = await RoleModel.findOne({ name: 'operator' }) + + // Helper function to check permissions + const checkPermissions = (role, expectedPermissions) => { + Object.entries(expectedPermissions).forEach(([key, value]) => { + if (typeof value === 'boolean') { + should(role.permissions[key]).equal(value) + } else if (Array.isArray(value)) { + should(role.permissions[key]).deepEqual(value) + } + }) + } + + // Admin role permissions + checkPermissions(adminRole, { + "channel-view-all": true, + "channel-manage-all": true, + "client-view-all": true, + "client-manage-all": true, + "transaction-view-all": true, + "transaction-view-body-all": true, + "transaction-rerun-all": true, + "user-view": true, + "user-manage": true, + "visualizer-manage": true, + "visualizer-view": true + // Add other admin permissions as needed + }) + + // Manager role permissions + checkPermissions(managerRole, { + "channel-view-all": true, + "channel-manage-all": true, + "client-view-all": true, + "client-manage-all": true, + "transaction-view-all": true, + "transaction-view-body-all": true, + "transaction-rerun-all": true, + "user-view": true, + "visualizer-manage": true, + "visualizer-view": true + // Add other manager permissions as needed + }) + + // Operator role permissions + checkPermissions(operatorRole, { + "channel-view-all": true, + "transaction-view-all": true, + "transaction-view-body-all": true, + "transaction-rerun-all": true + // Add other operator permissions as needed + }) + + // Check that operator doesn't have certain permissions + should(operatorRole.permissions["user-manage"]).be.false() + should(operatorRole.permissions["client-manage-all"]).be.false() + }) + }) +}) \ No newline at end of file From 8653852ee0bd6e7dbe79b27418c59dbc07838588 Mon Sep 17 00:00:00 2001 From: drono Date: Mon, 7 Oct 2024 13:20:47 +0300 Subject: [PATCH 3/8] cleanup --- src/upgradeDB.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/upgradeDB.js b/src/upgradeDB.js index c52f2e66..d5747a9c 100644 --- a/src/upgradeDB.js +++ b/src/upgradeDB.js @@ -314,17 +314,12 @@ upgradeFuncs.push({ const users = await UserModel.find(); const userPromises = users.map(async (user) => { - // Convert old boolean flags to new role system let newGroup = 'manager'; if ((user.groups && user.groups.includes('admin')) || user.superUser) { newGroup = 'admin'; } - // Update user's groups user.groups = [newGroup]; - - // Remove old superUser field - user.superUser = undefined; return user.save(); }); From 5d5c17879ad187e147ee9fd9acf52c90cc6baa9f Mon Sep 17 00:00:00 2001 From: drono Date: Tue, 8 Oct 2024 12:48:24 +0300 Subject: [PATCH 4/8] Improve test coverage --- test/unit/upgradeDBTest.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/unit/upgradeDBTest.js b/test/unit/upgradeDBTest.js index 354be14b..6789933a 100644 --- a/test/unit/upgradeDBTest.js +++ b/test/unit/upgradeDBTest.js @@ -642,5 +642,33 @@ describe('Upgrade DB Tests', () => { should(operatorRole.permissions["user-manage"]).be.false() should(operatorRole.permissions["client-manage-all"]).be.false() }) + + it('should not create roles if they all already exist', async () => { + // Create all default roles beforehand + await Promise.all([ + new RoleModel({ name: 'admin' }).save(), + new RoleModel({ name: 'manager' }).save(), + new RoleModel({ name: 'operator' }).save() + ]) + + await upgradeFunc() + + const roles = await RoleModel.find() + roles.length.should.be.exactly(3) + }) + + it('should handle partial existing roles', async () => { + // Create only one default role beforehand + await new RoleModel({ name: 'admin' }).save() + + await upgradeFunc() + + const roles = await RoleModel.find() + roles.length.should.be.exactly(3) + const roleNames = roles.map(r => r.name) + roleNames.should.containEql('admin') + roleNames.should.containEql('manager') + roleNames.should.containEql('operator') + }) }) }) \ No newline at end of file From 70d5447c8c344cbe2f7c84fd81590b2bc7f579e9 Mon Sep 17 00:00:00 2001 From: drono Date: Tue, 8 Oct 2024 12:51:38 +0300 Subject: [PATCH 5/8] remove duplicate upgrade function --- src/upgradeDB.js | 45 --------------------------------------------- 1 file changed, 45 deletions(-) diff --git a/src/upgradeDB.js b/src/upgradeDB.js index d5747a9c..59bf28e2 100644 --- a/src/upgradeDB.js +++ b/src/upgradeDB.js @@ -367,51 +367,6 @@ async function upgradeDbInternal() { } } -upgradeFuncs.push({ - description: 'Create default roles with permissions and update user groups', - func() { - return new Promise(async (resolve, reject) => { - try { - // Create default roles with permissions - for (const [roleName, roleData] of Object.entries(roles)) { - await RoleModel.findOneAndUpdate( - { name: roleName }, - roleData, - { upsert: true, new: true } - ); - logger.info(`Role ${roleName} created or updated`); - } - - const users = await UserModel.find(); - - const userPromises = users.map(async (user) => { - // Convert old boolean flags to new role system - let newGroup = 'manager'; - if ((user.groups && user.groups.includes('admin')) || user.superUser) { - newGroup = 'admin'; - } - - // Update user's groups - user.groups = [newGroup]; - - // Remove old superUser field - user.superUser = undefined; - - return user.save(); - }); - - await Promise.all(userPromises); - - logger.info('Successfully updated user groups based on new role model'); - resolve(); - } catch (err) { - logger.error(`Error updating user groups: ${err}`); - reject(err); - } - }); - } -}); - export function upgradeDb(callback) { return upgradeDbInternal() .then((...values) => { From 424337abb49327d727d332f295e249b16e422f4d Mon Sep 17 00:00:00 2001 From: drono Date: Tue, 8 Oct 2024 12:58:39 +0300 Subject: [PATCH 6/8] apply formatting and add more test cases --- test/unit/upgradeDBTest.js | 143 ++++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 58 deletions(-) diff --git a/test/unit/upgradeDBTest.js b/test/unit/upgradeDBTest.js index 6789933a..fb13aa5b 100644 --- a/test/unit/upgradeDBTest.js +++ b/test/unit/upgradeDBTest.js @@ -548,18 +548,24 @@ describe('Upgrade DB Tests', () => { }) }) - describe(`updateFunction4 - Create default roles`, () => { + describe(`updateFunction4 - Create default roles with permissions and update user groups`, () => { const upgradeFunc = originalUpgradeFuncs[4].func + beforeEach(async () => { + await RoleModel.deleteMany({}) + await UserModel.deleteMany({}) + }) + afterEach(async () => { await RoleModel.deleteMany({}) + await UserModel.deleteMany({}) }) it('should create default roles if they do not exist', async () => { await upgradeFunc() const roles = await RoleModel.find() - roles.length.should.be.exactly(4) + roles.length.should.be.exactly(3) const roleNames = roles.map(r => r.name) roleNames.should.containEql('manager') @@ -568,8 +574,7 @@ describe('Upgrade DB Tests', () => { }) it('should not create duplicate roles if they already exist', async () => { - // Create an existing role - await new RoleModel({ name: 'admin' }).save() + await new RoleModel({name: 'admin', permissions: {}}).save() await upgradeFunc() @@ -583,92 +588,114 @@ describe('Upgrade DB Tests', () => { it('should set correct permissions for each role', async () => { await upgradeFunc() - const managerRole = await RoleModel.findOne({ name: 'manager' }) - const adminRole = await RoleModel.findOne({ name: 'admin' }) - const operatorRole = await RoleModel.findOne({ name: 'operator' }) + const managerRole = await RoleModel.findOne({name: 'manager'}) + const adminRole = await RoleModel.findOne({name: 'admin'}) + const operatorRole = await RoleModel.findOne({name: 'operator'}) // Helper function to check permissions const checkPermissions = (role, expectedPermissions) => { + console.log(`Checking permissions for role: ${role.name}`) Object.entries(expectedPermissions).forEach(([key, value]) => { - if (typeof value === 'boolean') { - should(role.permissions[key]).equal(value) - } else if (Array.isArray(value)) { - should(role.permissions[key]).deepEqual(value) - } + should(role.permissions[key]).equal(value) }) } // Admin role permissions checkPermissions(adminRole, { - "channel-view-all": true, - "channel-manage-all": true, - "client-view-all": true, - "client-manage-all": true, - "transaction-view-all": true, - "transaction-view-body-all": true, - "transaction-rerun-all": true, - "user-view": true, - "user-manage": true, - "visualizer-manage": true, - "visualizer-view": true + 'channel-view-all': true, + 'channel-manage-all': true, + 'client-view-all': true, + 'client-manage-all': true, + 'transaction-view-all': true, + 'transaction-view-body-all': true, + 'transaction-rerun-all': true, + 'user-view': true, + 'user-manage': true, + 'visualizer-manage': true, + 'visualizer-view': true // Add other admin permissions as needed }) // Manager role permissions checkPermissions(managerRole, { - "channel-view-all": true, - "channel-manage-all": true, - "client-view-all": true, - "client-manage-all": true, - "transaction-view-all": true, - "transaction-view-body-all": true, - "transaction-rerun-all": true, - "user-view": true, - "visualizer-manage": true, - "visualizer-view": true + 'channel-view-all': true, + 'channel-manage-all': true, + 'client-view-all': true, + 'client-manage-all': true, + 'transaction-view-all': true, + 'transaction-view-body-all': true, + 'transaction-rerun-all': true, + 'user-view': true, + 'visualizer-manage': true, + 'visualizer-view': true // Add other manager permissions as needed }) // Operator role permissions checkPermissions(operatorRole, { - "channel-view-all": true, - "transaction-view-all": true, - "transaction-view-body-all": true, - "transaction-rerun-all": true + 'channel-view-all': true, + 'transaction-view-all': true, + 'transaction-view-body-all': true, + 'transaction-rerun-all': true // Add other operator permissions as needed }) // Check that operator doesn't have certain permissions - should(operatorRole.permissions["user-manage"]).be.false() - should(operatorRole.permissions["client-manage-all"]).be.false() + should(operatorRole.permissions['user-manage']).be.false() + should(operatorRole.permissions['client-manage-all']).be.false() }) - it('should not create roles if they all already exist', async () => { - // Create all default roles beforehand - await Promise.all([ - new RoleModel({ name: 'admin' }).save(), - new RoleModel({ name: 'manager' }).save(), - new RoleModel({ name: 'operator' }).save() - ]) + it('should update user groups to admin for superUsers', async () => { + const superUser = new UserModel({ + email: 'super@test.org', + groups: ['admin'], + firstname: 'Super', + surname: 'User' + }) + await superUser.save() await upgradeFunc() - const roles = await RoleModel.find() - roles.length.should.be.exactly(3) + const updatedUser = await UserModel.findOne({email: 'super@test.org'}) + updatedUser.groups.should.eql(['admin']) }) - it('should handle partial existing roles', async () => { - // Create only one default role beforehand - await new RoleModel({ name: 'admin' }).save() + it('should handle mixed user types correctly', async () => { + const users = [ + new UserModel({ + email: 'regular@test.org', + groups: ['user'], + firstname: 'Regular', + surname: 'User' + }), + new UserModel({ + email: 'admin@test.org', + groups: ['user', 'admin'], + firstname: 'Admin', + surname: 'User' + }), + new UserModel({ + email: 'super@test.org', + groups: ['admin'], + firstname: 'Super', + surname: 'User' + }), + new UserModel({ + email: 'another@test.org', + groups: ['operator'], + firstname: 'Another', + surname: 'User' + }) + ] + await Promise.all(users.map(user => user.save())) await upgradeFunc() - const roles = await RoleModel.find() - roles.length.should.be.exactly(3) - const roleNames = roles.map(r => r.name) - roleNames.should.containEql('admin') - roleNames.should.containEql('manager') - roleNames.should.containEql('operator') + const updatedUsers = await UserModel.find().sort('email') + updatedUsers[0].groups.should.eql(['admin']) // admin@test.org + updatedUsers[1].groups.should.eql(['manager']) // another@test.org + updatedUsers[2].groups.should.eql(['manager']) // regular@test.org + updatedUsers[3].groups.should.eql(['admin']) // super@test.org }) }) -}) \ No newline at end of file +}) From 1c6a39dbb3d9d8e0bfde13ad0e4885f853a89d1d Mon Sep 17 00:00:00 2001 From: drono Date: Thu, 17 Oct 2024 09:00:50 +0300 Subject: [PATCH 7/8] Refactor the upgrade function to use the permission properties in channels and roles defined in channels? --- src/upgradeDB.js | 98 +++++++++++++++------ test/unit/upgradeDBTest.js | 174 ++++++++++++------------------------- 2 files changed, 127 insertions(+), 145 deletions(-) diff --git a/src/upgradeDB.js b/src/upgradeDB.js index 59bf28e2..6b22bc0f 100644 --- a/src/upgradeDB.js +++ b/src/upgradeDB.js @@ -10,6 +10,7 @@ import {UserModel} from './model/users' import {VisualizerModel} from './model/visualizer' import {PassportModel} from './model/passport' import {RoleModel, roles} from './model/role' +import {ChannelModel} from './model/channels' function dedupName(name, names, num) { let newName @@ -301,40 +302,82 @@ upgradeFuncs.push({ func() { return new Promise(async (resolve, reject) => { try { - // Create default roles with permissions - for (const [roleName, roleData] of Object.entries(roles)) { + // Fetch channels and get the role names with their associated channels + const channels = await ChannelModel.find() + const existingRoles = JSON.parse(JSON.stringify(roles)) // Deep clone the roles object + + channels.forEach(channel => { + if (Array.isArray(channel.allow)) { + if (channel.txViewAcl && channel.txViewAcl.length > 0) { + channel.txViewAcl.forEach(role => { + if (!existingRoles[role]) { + existingRoles[role] = { permissions: {} } + } + if (!existingRoles[role].permissions['transaction-view-specified']) { + existingRoles[role].permissions['transaction-view-specified'] = [] + } + existingRoles[role].permissions['transaction-view-specified'].push(channel.name) + if (!existingRoles[role].permissions['channel-view-specified']) { + existingRoles[role].permissions['channel-view-specified'] = [] + } + existingRoles[role].permissions['channel-view-specified'].push(channel.name) + existingRoles[role].permissions['client-view-all'] = true + }) + } + if (channel.txRerunAcl && channel.txRerunAcl.length > 0) { + channel.txRerunAcl.forEach(role => { + if (!existingRoles[role]) { + existingRoles[role] = { permissions: {} } + } + if (!existingRoles[role].permissions['transaction-rerun-specified']) { + existingRoles[role].permissions['transaction-rerun-specified'] = [] + } + existingRoles[role].permissions['transaction-rerun-specified'].push(channel.name) + if (!existingRoles[role].permissions['channel-view-specified']) { + existingRoles[role].permissions['channel-view-specified'] = [] + } + existingRoles[role].permissions['channel-view-specified'].push(channel.name) + existingRoles[role].permissions['client-view-all'] = true + }) + } + if (channel.txViewFullAcl && channel.txViewFullAcl.length > 0) { + channel.txViewFullAcl.forEach(role => { + if (!existingRoles[role]) { + existingRoles[role] = { permissions: {} } + } + if (!existingRoles[role].permissions['transaction-view-body-specified']) { + existingRoles[role].permissions['transaction-view-body-specified'] = [] + } + existingRoles[role].permissions['transaction-view-body-specified'].push(channel.name) + if (!existingRoles[role].permissions['channel-view-specified']) { + existingRoles[role].permissions['channel-view-specified'] = [] + } + existingRoles[role].permissions['channel-view-specified'].push(channel.name) + existingRoles[role].permissions['client-view-all'] = true + }) + } + } + }) + + // Create or update roles + for (const [roleName, roleData] of Object.entries(existingRoles)) { await RoleModel.findOneAndUpdate( { name: roleName }, - roleData, + { name: roleName, permissions: roleData.permissions }, { upsert: true, new: true } - ); - logger.info(`Role ${roleName} created or updated`); + ) + logger.info(`Role ${roleName} created or updated with permissions`) } - const users = await UserModel.find(); - - const userPromises = users.map(async (user) => { - let newGroup = 'manager'; - if ((user.groups && user.groups.includes('admin')) || user.superUser) { - newGroup = 'admin'; - } - // Update user's groups - user.groups = [newGroup]; - - return user.save(); - }); - - await Promise.all(userPromises); - - logger.info('Successfully updated user groups based on new role model'); - resolve(); + logger.info('Successfully updated roles') + resolve() } catch (err) { - logger.error(`Error updating user groups: ${err}`); - reject(err); + logger.error(`Error updating roles: ${err}`) + reject(err) } - }); + }) } -}); +}) if (process.env.NODE_ENV === 'test') { exports.upgradeFuncs = upgradeFuncs @@ -346,8 +389,7 @@ async function upgradeDbInternal() { const dbVer = (await DbVersionModel.findOne()) || new DbVersionModel({version: 0, lastUpdated: new Date()}) - const upgradeFuncsToRun = upgradeFuncs.slice(dbVer.version) - + const upgradeFuncsToRun = upgradeFuncs.slice(dbVer.version) for (const upgradeFunc of upgradeFuncsToRun) { await upgradeFunc.func() dbVer.version++ diff --git a/test/unit/upgradeDBTest.js b/test/unit/upgradeDBTest.js index fb13aa5b..a11474f4 100644 --- a/test/unit/upgradeDBTest.js +++ b/test/unit/upgradeDBTest.js @@ -14,9 +14,10 @@ import { PassportModel, UserModel, VisualizerModel, - RoleModel + RoleModel, + ChannelModel, + roles } from '../../src/model' - describe('Upgrade DB Tests', () => { const originalUpgradeFuncs = [...upgradeDB.upgradeFuncs] upgradeDB.upgradeFuncs.length = 0 @@ -548,29 +549,29 @@ describe('Upgrade DB Tests', () => { }) }) - describe(`updateFunction4 - Create default roles with permissions and update user groups`, () => { + describe(`updateFunction4 - Create default roles with permissions`, () => { const upgradeFunc = originalUpgradeFuncs[4].func beforeEach(async () => { await RoleModel.deleteMany({}) - await UserModel.deleteMany({}) + await ChannelModel.deleteMany({}) }) afterEach(async () => { await RoleModel.deleteMany({}) - await UserModel.deleteMany({}) + await ChannelModel.deleteMany({}) }) it('should create default roles if they do not exist', async () => { await upgradeFunc() - const roles = await RoleModel.find() - roles.length.should.be.exactly(3) + const existingRoles = await RoleModel.find() + existingRoles.length.should.be.exactly(Object.keys(roles).length) - const roleNames = roles.map(r => r.name) - roleNames.should.containEql('manager') - roleNames.should.containEql('admin') - roleNames.should.containEql('operator') + const roleNames = existingRoles.map(r => r.name) + Object.keys(roles).forEach(roleName => { + roleNames.should.containEql(roleName) + }) }) it('should not create duplicate roles if they already exist', async () => { @@ -579,123 +580,62 @@ describe('Upgrade DB Tests', () => { await upgradeFunc() const roles = await RoleModel.find() - roles.length.should.be.exactly(3) + roles.length.should.be.exactly(Object.keys(roles).length) const adminRoles = roles.filter(r => r.name === 'admin') adminRoles.length.should.be.exactly(1) }) it('should set correct permissions for each role', async () => { - await upgradeFunc() - - const managerRole = await RoleModel.findOne({name: 'manager'}) - const adminRole = await RoleModel.findOne({name: 'admin'}) - const operatorRole = await RoleModel.findOne({name: 'operator'}) - - // Helper function to check permissions - const checkPermissions = (role, expectedPermissions) => { - console.log(`Checking permissions for role: ${role.name}`) - Object.entries(expectedPermissions).forEach(([key, value]) => { - should(role.permissions[key]).equal(value) - }) - } - - // Admin role permissions - checkPermissions(adminRole, { - 'channel-view-all': true, - 'channel-manage-all': true, - 'client-view-all': true, - 'client-manage-all': true, - 'transaction-view-all': true, - 'transaction-view-body-all': true, - 'transaction-rerun-all': true, - 'user-view': true, - 'user-manage': true, - 'visualizer-manage': true, - 'visualizer-view': true - // Add other admin permissions as needed - }) - - // Manager role permissions - checkPermissions(managerRole, { - 'channel-view-all': true, - 'channel-manage-all': true, - 'client-view-all': true, - 'client-manage-all': true, - 'transaction-view-all': true, - 'transaction-view-body-all': true, - 'transaction-rerun-all': true, - 'user-view': true, - 'visualizer-manage': true, - 'visualizer-view': true - // Add other manager permissions as needed - }) - - // Operator role permissions - checkPermissions(operatorRole, { - 'channel-view-all': true, - 'transaction-view-all': true, - 'transaction-view-body-all': true, - 'transaction-rerun-all': true - // Add other operator permissions as needed - }) - - // Check that operator doesn't have certain permissions - should(operatorRole.permissions['user-manage']).be.false() - should(operatorRole.permissions['client-manage-all']).be.false() - }) - - it('should update user groups to admin for superUsers', async () => { - const superUser = new UserModel({ - email: 'super@test.org', - groups: ['admin'], - firstname: 'Super', - surname: 'User' - }) - await superUser.save() + // Create test channels + const channel1 = await new ChannelModel({ + name: 'Channel 1', + urlPattern: '/channel1', + allow: ['admin', 'manager'], + txViewAcl: ['admin'], + txRerunAcl: ['admin'], + txViewFullAcl: ['admin'] + }).save() + + const channel2 = await new ChannelModel({ + name: 'Channel 2', + urlPattern: '/channel2', + allow: ['admin', 'manager', 'operator'], + txViewAcl: ['admin', 'manager', 'operator'], + txRerunAcl: ['admin'], + txViewFullAcl: ['admin'] + }).save() await upgradeFunc() - const updatedUser = await UserModel.findOne({email: 'super@test.org'}) - updatedUser.groups.should.eql(['admin']) - }) + const createdRoles = await RoleModel.find() + + for (const role of createdRoles) { + should.exist(role) - it('should handle mixed user types correctly', async () => { - const users = [ - new UserModel({ - email: 'regular@test.org', - groups: ['user'], - firstname: 'Regular', - surname: 'User' - }), - new UserModel({ - email: 'admin@test.org', - groups: ['user', 'admin'], - firstname: 'Admin', - surname: 'User' - }), - new UserModel({ - email: 'super@test.org', - groups: ['admin'], - firstname: 'Super', - surname: 'User' - }), - new UserModel({ - email: 'another@test.org', - groups: ['operator'], - firstname: 'Another', - surname: 'User' - }) - ] - await Promise.all(users.map(user => user.save())) - - await upgradeFunc() + // Check default permissions + if (roles[role.name]) { + Object.entries(roles[role.name].permissions).forEach(([key, value]) => { + should(role.permissions[key]).eql(value) + }) + } - const updatedUsers = await UserModel.find().sort('email') - updatedUsers[0].groups.should.eql(['admin']) // admin@test.org - updatedUsers[1].groups.should.eql(['manager']) // another@test.org - updatedUsers[2].groups.should.eql(['manager']) // regular@test.org - updatedUsers[3].groups.should.eql(['admin']) // super@test.org + // Check channel-specific permissions + if (role.name === 'admin') { + role.permissions['transaction-view-specified'].should.containEql('Channel 1') + role.permissions['transaction-view-specified'].should.containEql('Channel 2') + role.permissions['transaction-rerun-specified'].should.containEql('Channel 1') + role.permissions['transaction-rerun-specified'].should.containEql('Channel 2') + role.permissions['transaction-view-body-specified'].should.containEql('Channel 1') + role.permissions['transaction-view-body-specified'].should.containEql('Channel 2') + } else if (role.name === 'manager') { + role.permissions['transaction-view-specified'].should.not.containEql('Channel 1') + role.permissions['transaction-view-specified'].should.containEql('Channel 2') + } else if (role.name === 'operator') { + role.permissions['transaction-view-specified'].should.not.containEql('Channel 1') + role.permissions['transaction-view-specified'].should.containEql('Channel 2') + } + } }) }) }) From 304e8f48e4344a1164e808026ff982a1ea384e10 Mon Sep 17 00:00:00 2001 From: drono Date: Thu, 17 Oct 2024 09:22:57 +0300 Subject: [PATCH 8/8] code cleanup --- src/upgradeDB.js | 121 ++++++++++++++----------------------- test/unit/upgradeDBTest.js | 6 +- 2 files changed, 49 insertions(+), 78 deletions(-) diff --git a/src/upgradeDB.js b/src/upgradeDB.js index 6b22bc0f..bcb579c6 100644 --- a/src/upgradeDB.js +++ b/src/upgradeDB.js @@ -299,83 +299,54 @@ upgradeFuncs.push({ upgradeFuncs.push({ description: 'Create default roles with permissions and update user groups', - func() { - return new Promise(async (resolve, reject) => { - try { - // Fetch channels and get the role names with their associated channels - const channels = await ChannelModel.find() - const existingRoles = JSON.parse(JSON.stringify(roles)) // Deep clone the roles object - - channels.forEach(channel => { - if (Array.isArray(channel.allow)) { - if (channel.txViewAcl && channel.txViewAcl.length > 0) { - channel.txViewAcl.forEach(role => { - if (!existingRoles[role]) { - existingRoles[role] = { permissions: {} } - } - if (!existingRoles[role].permissions['transaction-view-specified']) { - existingRoles[role].permissions['transaction-view-specified'] = [] - } - existingRoles[role].permissions['transaction-view-specified'].push(channel.name) - if (!existingRoles[role].permissions['channel-view-specified']) { - existingRoles[role].permissions['channel-view-specified'] = [] - } - existingRoles[role].permissions['channel-view-specified'].push(channel.name) - existingRoles[role].permissions['client-view-all'] = true - }) - } - if (channel.txRerunAcl && channel.txRerunAcl.length > 0) { - channel.txRerunAcl.forEach(role => { - if (!existingRoles[role]) { - existingRoles[role] = { permissions: {} } - } - if (!existingRoles[role].permissions['transaction-rerun-specified']) { - existingRoles[role].permissions['transaction-rerun-specified'] = [] - } - existingRoles[role].permissions['transaction-rerun-specified'].push(channel.name) - if (!existingRoles[role].permissions['channel-view-specified']) { - existingRoles[role].permissions['channel-view-specified'] = [] - } - existingRoles[role].permissions['channel-view-specified'].push(channel.name) - existingRoles[role].permissions['client-view-all'] = true - }) - } - if (channel.txViewFullAcl && channel.txViewFullAcl.length > 0) { - channel.txViewFullAcl.forEach(role => { - if (!existingRoles[role]) { - existingRoles[role] = { permissions: {} } - } - if (!existingRoles[role].permissions['transaction-view-body-specified']) { - existingRoles[role].permissions['transaction-view-body-specified'] = [] - } - existingRoles[role].permissions['transaction-view-body-specified'].push(channel.name) - if (!existingRoles[role].permissions['channel-view-specified']) { - existingRoles[role].permissions['channel-view-specified'] = [] - } - existingRoles[role].permissions['channel-view-specified'].push(channel.name) - existingRoles[role].permissions['client-view-all'] = true - }) - } - } - }) + func: async () => { + try { + const channels = await ChannelModel.find() + const existingRoles = JSON.parse(JSON.stringify(roles)) // Deep clone the roles object + + const updateRolePermissions = (role, permissionType, channelName) => { + if (!existingRoles[role]) { + existingRoles[role] = { permissions: {} } + } + if (!existingRoles[role].permissions[permissionType]) { + existingRoles[role].permissions[permissionType] = [] + } + existingRoles[role].permissions[permissionType].push(channelName) + existingRoles[role].permissions['channel-view-specified'] = existingRoles[role].permissions['channel-view-specified'] || [] + existingRoles[role].permissions['channel-view-specified'].push(channelName) + existingRoles[role].permissions['client-view-all'] = true + } - // Create or update roles - for (const [roleName, roleData] of Object.entries(existingRoles)) { - await RoleModel.findOneAndUpdate( - { name: roleName }, - { name: roleName, permissions: roleData.permissions }, - { upsert: true, new: true } - ) - logger.info(`Role ${roleName} created or updated with permissions`) + channels.forEach(channel => { + if (Array.isArray(channel.allow)) { + const aclTypes = [ + { acl: channel.txViewAcl, permissionType: 'transaction-view-specified' }, + { acl: channel.txRerunAcl, permissionType: 'transaction-rerun-specified' }, + { acl: channel.txViewFullAcl, permissionType: 'transaction-view-body-specified' } + ] + + aclTypes.forEach(({ acl, permissionType }) => { + if (acl && acl.length > 0) { + acl.forEach(role => updateRolePermissions(role, permissionType, channel.name)) + } + }) } + }) - logger.info('Successfully updated roles') - resolve() - } catch (err) { - logger.error(`Error updating roles: ${err}`) - reject(err) - } - }) + // Create or update roles + await Promise.all(Object.entries(existingRoles).map(([roleName, roleData]) => + RoleModel.findOneAndUpdate( + { name: roleName }, + { name: roleName, permissions: roleData.permissions }, + { upsert: true, new: true } + ) + )) + + logger.info('Successfully updated roles') + } catch (err) { + logger.error(`Error updating roles: ${err}`) + throw err + } } }) @@ -389,7 +360,7 @@ async function upgradeDbInternal() { const dbVer = (await DbVersionModel.findOne()) || new DbVersionModel({version: 0, lastUpdated: new Date()}) - const upgradeFuncsToRun = upgradeFuncs.slice(dbVer.version) + const upgradeFuncsToRun = upgradeFuncs.slice(dbVer.version) for (const upgradeFunc of upgradeFuncsToRun) { await upgradeFunc.func() dbVer.version++ diff --git a/test/unit/upgradeDBTest.js b/test/unit/upgradeDBTest.js index a11474f4..5a061744 100644 --- a/test/unit/upgradeDBTest.js +++ b/test/unit/upgradeDBTest.js @@ -579,10 +579,10 @@ describe('Upgrade DB Tests', () => { await upgradeFunc() - const roles = await RoleModel.find() - roles.length.should.be.exactly(Object.keys(roles).length) + const existingRoles = await RoleModel.find() + existingRoles.length.should.be.exactly(Object.keys(roles).length) - const adminRoles = roles.filter(r => r.name === 'admin') + const adminRoles = existingRoles.filter(r => r.name === 'admin') adminRoles.length.should.be.exactly(1) })