-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cu 86c0bkuxc add a mechanism to migrate users from the old roles to new rbac roles #1234
base: master
Are you sure you want to change the base?
Changes from 8 commits
813d610
25f97ff
b6a944b
8653852
5d5c178
70d5447
424337a
1c6a39d
304e8f4
2977c06
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,8 @@ | |
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 | ||
|
@@ -295,6 +297,88 @@ | |
} | ||
}) | ||
|
||
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 | ||
}) | ||
} | ||
} | ||
}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Refactor duplicated code for handling ACLs into a helper function The code blocks processing Define a helper function outside the loop (this code is added outside the selected line ranges): function processAcl(aclRoles, permissionKey, channelName, existingRoles) {
aclRoles.forEach((role) => {
if (!existingRoles[role]) {
existingRoles[role] = { permissions: {} };
}
if (!existingRoles[role].permissions[permissionKey]) {
existingRoles[role].permissions[permissionKey] = [];
}
existingRoles[role].permissions[permissionKey].push(channelName);
if (!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;
});
} Apply this diff to refactor the code within the loop: 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) {
- // similar code block
- }
- if (channel.txViewFullAcl && channel.txViewFullAcl.length > 0) {
- // similar code block
- }
- }
+ const aclMappings = [
+ { aclKey: 'txViewAcl', permissionKey: 'transaction-view-specified' },
+ { aclKey: 'txRerunAcl', permissionKey: 'transaction-rerun-specified' },
+ { aclKey: 'txViewFullAcl', permissionKey: 'transaction-view-body-specified' },
+ ];
+
+ aclMappings.forEach(({ aclKey, permissionKey }) => {
+ const aclRoles = channel[aclKey];
+ if (aclRoles && aclRoles.length > 0) {
+ processAcl(aclRoles, permissionKey, channel.name, existingRoles);
+ }
+ });
}); |
||
|
||
// 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`) | ||
} | ||
|
||
logger.info('Successfully updated roles') | ||
resolve() | ||
} catch (err) { | ||
logger.error(`Error updating roles: ${err}`) | ||
reject(err) | ||
} | ||
}) | ||
} | ||
}) | ||
|
||
if (process.env.NODE_ENV === 'test') { | ||
exports.upgradeFuncs = upgradeFuncs | ||
exports.dedupName = dedupName | ||
|
@@ -305,8 +389,7 @@ | |
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++ | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -13,9 +13,11 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||
KeystoreModel, | ||||||||||||||||||||||||||||||||||||||||||||||
PassportModel, | ||||||||||||||||||||||||||||||||||||||||||||||
UserModel, | ||||||||||||||||||||||||||||||||||||||||||||||
VisualizerModel | ||||||||||||||||||||||||||||||||||||||||||||||
VisualizerModel, | ||||||||||||||||||||||||||||||||||||||||||||||
RoleModel, | ||||||||||||||||||||||||||||||||||||||||||||||
ChannelModel, | ||||||||||||||||||||||||||||||||||||||||||||||
roles | ||||||||||||||||||||||||||||||||||||||||||||||
} from '../../src/model' | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
describe('Upgrade DB Tests', () => { | ||||||||||||||||||||||||||||||||||||||||||||||
const originalUpgradeFuncs = [...upgradeDB.upgradeFuncs] | ||||||||||||||||||||||||||||||||||||||||||||||
upgradeDB.upgradeFuncs.length = 0 | ||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -546,4 +548,94 @@ describe('Upgrade DB Tests', () => { | |||||||||||||||||||||||||||||||||||||||||||||
passports.length.should.eql(2) | ||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
describe(`updateFunction4 - Create default roles with permissions`, () => { | ||||||||||||||||||||||||||||||||||||||||||||||
const upgradeFunc = originalUpgradeFuncs[4].func | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
beforeEach(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
await RoleModel.deleteMany({}) | ||||||||||||||||||||||||||||||||||||||||||||||
await ChannelModel.deleteMany({}) | ||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
afterEach(async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
await RoleModel.deleteMany({}) | ||||||||||||||||||||||||||||||||||||||||||||||
await ChannelModel.deleteMany({}) | ||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
it('should create default roles if they do not exist', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
await upgradeFunc() | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
const existingRoles = await RoleModel.find() | ||||||||||||||||||||||||||||||||||||||||||||||
existingRoles.length.should.be.exactly(Object.keys(roles).length) | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
const roleNames = existingRoles.map(r => r.name) | ||||||||||||||||||||||||||||||||||||||||||||||
Object.keys(roles).forEach(roleName => { | ||||||||||||||||||||||||||||||||||||||||||||||
roleNames.should.containEql(roleName) | ||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+569
to
+574
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid variable shadowing of 'roles' variable The local variable To improve clarity and prevent any unintended behavior, consider renaming the local variables. Here's an example: - const existingRoles = await RoleModel.find()
- existingRoles.length.should.be.exactly(Object.keys(roles).length)
- const roleNames = existingRoles.map(r => r.name)
- Object.keys(roles).forEach(roleName => {
+ const defaultRoleNames = Object.keys(roles)
+ existingRoles.length.should.be.exactly(defaultRoleNames.length)
+ const roleNames = existingRoles.map(r => r.name)
+ defaultRoleNames.forEach(roleName => {
roleNames.should.containEql(roleName)
}) This change avoids confusion between the imported
|
||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+565
to
+575
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid variable shadowing of 'roles' The local variable To improve clarity and prevent any unintended behavior, consider renaming the variables: - const existingRoles = await RoleModel.find()
- existingRoles.length.should.be.exactly(Object.keys(roles).length)
+ const createdRoles = await RoleModel.find()
+ createdRoles.length.should.be.exactly(Object.keys(roles).length)
- const roleNames = existingRoles.map(r => r.name)
+ const createdRoleNames = createdRoles.map(r => r.name)
- Object.keys(roles).forEach(roleName => {
+ Object.keys(roles).forEach(defaultRoleName => {
- roleNames.should.containEql(roleName)
+ createdRoleNames.should.containEql(defaultRoleName)
}) This change avoids confusion between the imported 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
it('should not create duplicate roles if they already exist', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
await new RoleModel({name: 'admin', permissions: {}}).save() | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
await upgradeFunc() | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
const roles = await RoleModel.find() | ||||||||||||||||||||||||||||||||||||||||||||||
roles.length.should.be.exactly(Object.keys(roles).length) | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename local 'roles' variable to prevent conflict with imported 'roles' In the test To enhance code readability and prevent potential bugs, rename the local variable: - 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) This makes it clear when you are referring to the imported 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
const adminRoles = roles.filter(r => r.name === 'admin') | ||||||||||||||||||||||||||||||||||||||||||||||
adminRoles.length.should.be.exactly(1) | ||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
it('should set correct permissions for each role', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||
// 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 createdRoles = await RoleModel.find() | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
for (const role of createdRoles) { | ||||||||||||||||||||||||||||||||||||||||||||||
should.exist(role) | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
// Check default permissions | ||||||||||||||||||||||||||||||||||||||||||||||
if (roles[role.name]) { | ||||||||||||||||||||||||||||||||||||||||||||||
Object.entries(roles[role.name].permissions).forEach(([key, value]) => { | ||||||||||||||||||||||||||||||||||||||||||||||
should(role.permissions[key]).eql(value) | ||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
// 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') | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+589
to
+639
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance test robustness and coverage The test case is comprehensive, but consider the following improvements:
afterEach(async () => {
await RoleModel.deleteMany({})
await ChannelModel.deleteMany({})
})
const channelPermissions = {
'Channel 1': {
view: ['admin'],
rerun: ['admin'],
viewBody: ['admin']
},
'Channel 2': {
view: ['admin', 'manager', 'operator'],
rerun: ['admin'],
viewBody: ['admin']
}
}
for (const role of createdRoles) {
for (const [channelName, permissions] of Object.entries(channelPermissions)) {
for (const [action, allowedRoles] of Object.entries(permissions)) {
const permissionKey = `transaction-${action}-specified`
if (allowedRoles.includes(role.name)) {
role.permissions[permissionKey].should.containEql(channelName)
} else {
role.permissions[permissionKey].should.not.containEql(channelName)
}
}
}
}
for (const [roleName, roleData] of Object.entries(roles)) {
const createdRole = createdRoles.find(r => r.name === roleName)
should.exist(createdRole, `Role ${roleName} should exist`)
Object.entries(roleData.permissions).forEach(([key, value]) => {
should(createdRole.permissions[key]).eql(value, `Permission ${key} for role ${roleName} should match`)
})
} These changes will make the test more robust, easier to maintain, and ensure complete coverage of all defined roles and their permissions. |
||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove unnecessary check on
channel.allow
The condition
if (Array.isArray(channel.allow))
is unnecessary because the subsequent code does not depend onchannel.allow
; it operates onchannel.txViewAcl
,channel.txRerunAcl
, andchannel.txViewFullAcl
. Removing this condition will simplify the code without affecting functionality.Apply this diff to remove the unnecessary condition:
📝 Committable suggestion