-
Notifications
You must be signed in to change notification settings - Fork 79
Cu 86c0bkuxc add a mechanism to migrate users from the old roles to new rbac roles #1234
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
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
6f010f7
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 {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' | ||
| import {ChannelModel} from './model/channels' | ||
|
|
||
| function dedupName(name, names, num) { | ||
| let newName | ||
|
|
@@ -295,6 +297,88 @@ 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 | ||
| }) | ||
| } | ||
| } | ||
| }) | ||
|
||
|
|
||
| // 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 @@ 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++ | ||
|
|
||
| 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
Contributor
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
Contributor
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) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
| 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) | |
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.
🛠️ Refactor suggestion
Enhance test robustness and coverage
The test case is comprehensive, but consider the following improvements:
- Clean up test channels in the afterEach hook to ensure a clean state for each test:
afterEach(async () => {
await RoleModel.deleteMany({})
await ChannelModel.deleteMany({})
})- Make channel-specific permission assertions more robust:
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)
}
}
}
}- Verify permissions for all roles defined in the imported
rolesobject:
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.allowThe 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