-
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 7 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,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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,46 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let newGroup = 'manager'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if ((user.groups && user.groups.includes('admin')) || user.superUser) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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 Simplify condition using optional chaining You can simplify the condition by using optional chaining to improve readability. Apply this diff: const userPromises = users.map(async (user) => {
let newGroup = 'manager';
- if ((user.groups && user.groups.includes('admin')) || user.superUser) {
+ if (user.groups?.includes('admin') || user.superUser) {
newGroup = 'admin';
}
// Update user's groups
user.groups = [newGroup];
return user.save();
}); 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
logger.error(`Error updating user groups: ${err}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
reject(err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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 using async functions as Promise executor functions Using Apply this diff to fix the issue: upgradeFuncs.push({
description: 'Create default roles with permissions and update user groups',
- func() {
- return new Promise(async (resolve, reject) => {
+ async func() {
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) => {
let newGroup = 'manager';
- if ((user.groups && user.groups.includes('admin')) || user.superUser) {
+ if (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();
} catch (err) {
logger.error(`Error updating user groups: ${err}`);
- reject(err);
+ throw err;
}
- });
}
}); This change ensures proper error handling and cleaner code structure. 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (process.env.NODE_ENV === 'test') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
exports.upgradeFuncs = upgradeFuncs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
exports.dedupName = dedupName | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -13,7 +13,8 @@ import { | |||||||||
KeystoreModel, | ||||||||||
PassportModel, | ||||||||||
UserModel, | ||||||||||
VisualizerModel | ||||||||||
VisualizerModel, | ||||||||||
RoleModel | ||||||||||
} from '../../src/model' | ||||||||||
|
||||||||||
describe('Upgrade DB Tests', () => { | ||||||||||
|
@@ -546,4 +547,155 @@ describe('Upgrade DB Tests', () => { | |||||||||
passports.length.should.eql(2) | ||||||||||
}) | ||||||||||
}) | ||||||||||
|
||||||||||
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(3) | ||||||||||
|
||||||||||
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 () => { | ||||||||||
await new RoleModel({name: 'admin', permissions: {}}).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) => { | ||||||||||
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() | ||||||||||
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. Assertion may fail when permission is undefined When verifying that the Suggested Fix: Modify the assertions to check that the permission is not - should(operatorRole.permissions['user-manage']).be.false()
- should(operatorRole.permissions['client-manage-all']).be.false()
+ should(operatorRole.permissions['user-manage']).not.be.true()
+ should(operatorRole.permissions['client-manage-all']).not.be.true() This ensures the test passes if the permission is either 📝 Committable suggestion
Suggested change
|
||||||||||
}) | ||||||||||
|
||||||||||
it('should update user groups to admin for superUsers', async () => { | ||||||||||
const superUser = new UserModel({ | ||||||||||
email: '[email protected]', | ||||||||||
groups: ['admin'], | ||||||||||
firstname: 'Super', | ||||||||||
surname: 'User' | ||||||||||
}) | ||||||||||
await superUser.save() | ||||||||||
|
||||||||||
await upgradeFunc() | ||||||||||
|
||||||||||
const updatedUser = await UserModel.findOne({email: '[email protected]'}) | ||||||||||
updatedUser.groups.should.eql(['admin']) | ||||||||||
}) | ||||||||||
|
||||||||||
it('should handle mixed user types correctly', async () => { | ||||||||||
const users = [ | ||||||||||
new UserModel({ | ||||||||||
email: '[email protected]', | ||||||||||
groups: ['user'], | ||||||||||
firstname: 'Regular', | ||||||||||
surname: 'User' | ||||||||||
}), | ||||||||||
new UserModel({ | ||||||||||
email: '[email protected]', | ||||||||||
groups: ['user', 'admin'], | ||||||||||
firstname: 'Admin', | ||||||||||
surname: 'User' | ||||||||||
}), | ||||||||||
new UserModel({ | ||||||||||
email: '[email protected]', | ||||||||||
groups: ['admin'], | ||||||||||
firstname: 'Super', | ||||||||||
surname: 'User' | ||||||||||
}), | ||||||||||
new UserModel({ | ||||||||||
email: '[email protected]', | ||||||||||
groups: ['operator'], | ||||||||||
firstname: 'Another', | ||||||||||
surname: 'User' | ||||||||||
}) | ||||||||||
] | ||||||||||
await Promise.all(users.map(user => user.save())) | ||||||||||
|
||||||||||
await upgradeFunc() | ||||||||||
|
||||||||||
const updatedUsers = await UserModel.find().sort('email') | ||||||||||
updatedUsers[0].groups.should.eql(['admin']) // [email protected] | ||||||||||
updatedUsers[1].groups.should.eql(['manager']) // [email protected] | ||||||||||
updatedUsers[2].groups.should.eql(['manager']) // [email protected] | ||||||||||
updatedUsers[3].groups.should.eql(['admin']) // [email protected] | ||||||||||
}) | ||||||||||
}) | ||||||||||
}) |
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.
Unfortunately, we cannot just update a user group to manager as this might give them more privileges than they originally had. A channel object has a few properties which allow users or groups to access transaction of that channel without being an admin. See:
openhim-core-js/src/model/channels.js
Lines 180 to 182 in d259d5d
We should map these to a new role with the same name and the new channel 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.
Hey @rcrichton would it be right to assume that roles not linked to any channels should be discarded? I have made a few tweaks to the upgrade function which now relies on the roles defined in the
allow
array in the channels model.