rbac roles implementation

This commit is contained in:
2026-04-30 14:22:12 +02:00
parent 6679ed20fe
commit 76a30fc94f
7 changed files with 190 additions and 43 deletions

View File

@@ -492,6 +492,16 @@ JOIN dbo.[Role] r
GO GO
CREATE OR ALTER VIEW dbo.vRoleOverview AS
SELECT r.ID, r.Name, r.Description, r.RoleType, COUNT(gr.Group_ObjectGUID) AS GroupCount, COUNT(vau.ObjectGUID) AS UserCount
FROM dbo.Role AS r LEFT OUTER JOIN
dbo.vAuthenticationRoles AS vau ON vau.Role_ID = r.ID LEFT OUTER JOIN
dbo.GroupRoles AS gr ON gr.Role_ID = r.ID LEFT OUTER JOIN
dbo.AuthenticationGroups AS ag ON ag.Group_ObjectGUID = gr.Group_ObjectGUID
GROUP BY r.ID, r.Name, r.Description, r.RoleType
GO
-- ======================================================== -- ========================================================
-- 4. PERMISSIONS (DETAILED WITH ROLE SOURCE) -- 4. PERMISSIONS (DETAILED WITH ROLE SOURCE)
-- ======================================================== -- ========================================================

View File

@@ -22,7 +22,6 @@ const api = async (url, method = 'GET', body) => {
////////////////////////////// //////////////////////////////
const RBAC = { const RBAC = {
// 👤 USERS // 👤 USERS
loadUsers: async () => (await api('/api/rbac/auth/get', 'POST')) loadUsers: async () => (await api('/api/rbac/auth/get', 'POST'))
.map(({ active, online, ...rest }) => ({ .map(({ active, online, ...rest }) => ({
@@ -30,31 +29,24 @@ const RBAC = {
Aktiv: active Aktiv: active
})) }))
.sort((a, b) => a.sAMAccountName.localeCompare(b.sAMAccountName)), .sort((a, b) => a.sAMAccountName.localeCompare(b.sAMAccountName)),
createUser: (data) => api('/api/rbac/auth/create', 'POST', data), createUser: (data) => api('/api/rbac/auth/create', 'POST', data),
deleteUser: (guid) => api(`/api/rbac/auth/${guid}`, 'DELETE'), deleteUser: (guid) => api(`/api/rbac/auth/${guid}`, 'DELETE'),
// 👥 GROUPS // 👥 GROUPS
loadGroups: () => api('/api/rbac/group/get', 'POST'), loadGroups: () => api('/api/rbac/group/get', 'POST'),
createGroup: (name) => api('/api/rbac/group/create', 'POST', { name }), createGroup: (name) => api('/api/rbac/group/create', 'POST', { name }),
deleteGroup: (guid) => api(`/api/rbac/group/${guid}`, 'DELETE'), deleteGroup: (guid) => api(`/api/rbac/group/${guid}`, 'DELETE'),
// 🎭 ROLES
loadRoles: () => api('/api/rbac/role/get', 'POST'),
createRole: (name) => api('/api/rbac/role', 'POST', { name }),
deleteRole: (id) => api(`/api/rbac/role/${id}`, 'DELETE'),
// 🔗 ASSIGNMENTS // 🔗 ASSIGNMENTS
addUserToGroup: (authGuid, groupGuid) => addUserToGroup: (authGuid, groupGuid) => api('/api/rbac/group/add-user', 'POST', { authGuid, groupGuid }),
api('/api/rbac/group/add-user', 'POST', { authGuid, groupGuid }), addUserToRole: (authGuid, roleId) => api('/api/rbac/role/add-user', 'POST', { authGuid, roleId }),
addGroupToRole: (groupGuid, roleId) => api('/api/rbac/role/add-group', 'POST', { groupGuid, roleId }),
addUserToRole: (authGuid, roleId) => addPermissionToRole: (roleId, permissionId) => api('/api/rbac/role/add-permission', 'POST', { roleId, permissionId })
api('/api/rbac/role/add-user', 'POST', { authGuid, roleId }),
addGroupToRole: (groupGuid, roleId) =>
api('/api/rbac/role/add-group', 'POST', { groupGuid, roleId }),
addPermissionToRole: (roleId, permissionId) =>
api('/api/rbac/role/add-permission', 'POST', { roleId, permissionId })
}; };
////////////////////////////// //////////////////////////////
@@ -90,6 +82,7 @@ function createDropZone(el, type, target) {
process.action = `Du hast den Benutzer der Gruppe hinzugefügt`; process.action = `Du hast den Benutzer der Gruppe hinzugefügt`;
process.response = await RBAC.addUserToGroup(data.ObjectGUID, targetValue); process.response = await RBAC.addUserToGroup(data.ObjectGUID, targetValue);
loadUsers(); loadUsers();
loadGroups();
break; break;
case 'role': case 'role':
process.action = `Du hast den Benutzer der Rolle hinzugefügt`; process.action = `Du hast den Benutzer der Rolle hinzugefügt`;
@@ -116,7 +109,7 @@ function createDropZone(el, type, target) {
// 📋 TABLE (USERS) // 📋 TABLE (USERS)
////////////////////////////// //////////////////////////////
const vt = virtualTable({ const rbacVT = virtualTable({
tableEl: document.querySelector('#rbacUsersTable'), tableEl: document.querySelector('#rbacUsersTable'),
data: [], data: [],
rowHeight: 20, rowHeight: 20,
@@ -184,7 +177,7 @@ const vt = virtualTable({
async function loadUsers() { async function loadUsers() {
try { try {
vt.source(await RBAC.loadUsers()); rbacVT.source(await RBAC.loadUsers());
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
@@ -193,29 +186,29 @@ async function loadUsers() {
async function loadGroups() { async function loadGroups() {
const container = document.getElementById('rbacGroupContainer'); const container = document.getElementById('rbacGroupContainer');
container.innerHTML = ''; container.innerHTML = '';
const groups = await RBAC.loadGroups(); const groups = await RBAC.loadGroups();
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
groups.forEach(group => { groups.forEach(group => {
const section = document.createElement('section'); const section = document.createElement('section');
const groupName = document.createElement('span'); const groupName = document.createElement('span');
groupName.innerHTML = group.Name; groupName.innerHTML = `[${group.UserCount}] ${group.Name}`;
groupName.dataset.tooltip = group.Name; groupName.dataset.tooltip = group.Name;
groupName.dataset.tooltipMode = 'ellipsis'; groupName.dataset.tooltipMode = 'ellipsis';
section.append(groupName); section.append(groupName);
if(group.ObjectGUID !== '00000000-0000-0000-0000-000000000001') { const removeButton = document.createElement('button');
const removeButton = document.createElement('div');
if(group.ObjectGUID === '00000000-0000-0000-0000-000000000001') {
removeButton.disabled = true;
removeButton.dataset.tooltip = `Die Gruppe ${group.Name} kann nicht gelöscht werden`;
}
removeButton.className = 'removeButton'; removeButton.className = 'removeButton';
removeButton.innerHTML = 'X'; removeButton.innerHTML = 'X';
removeButton.onclick = async () => { removeButton.onclick = async () => {
await deleteGroup(group.ObjectGUID, group.Name); await deleteGroup(group.ObjectGUID, group.Name);
}; };
section.append(removeButton); section.append(removeButton);
}
createDropZone(section, 'group', group); createDropZone(section, 'group', group);
@@ -236,6 +229,49 @@ async function createGroup() {
} }
} }
async function loadRoles() {
const container = document.getElementById('rbacRoleContainer');
container.innerHTML = '';
const roles = await RBAC.loadRoles();
const fragment = document.createDocumentFragment();
roles.forEach(role => {
const section = document.createElement('section');
const roleName = document.createElement('span');
roleName.innerHTML = role.Name;
roleName.dataset.tooltip = role.Name;
section.append(roleName);
const removeButton = document.createElement('button');
removeButton.className = 'removeButton';
removeButton.innerHTML = 'X';
removeButton.onclick = async () => {
await deleteRole(role.ID, role.Name);
};
section.append(removeButton);
// 🔥 DROP ZONES
createDropZone(section, 'role', role); // User → Role
createDropZone(section, 'group-to-role', role); // Group → Role
// optional: permissions
createDropZone(section, 'permission', role);
fragment.appendChild(section);
});
container.appendChild(fragment);
}
////////////////////////////// //////////////////////////////
// 👤 USER ACTIONS // 👤 USER ACTIONS
////////////////////////////// //////////////////////////////
@@ -301,6 +337,44 @@ async function deleteGroup(guid, name) {
} }
//////////////////////////////
// 🎭 ROLE ACTIONS
//////////////////////////////
async function createRole() {
const name = document.getElementById('newRoleName').value;
if (!name) return;
const role = await RBAC.createRole(name);
sendUserEvent('RBAC', `Rolle ${name} angelegt`, null, 0);
loadRoles();
}
async function deleteRole(id, name) {
feedbox({
title: `<span>Rolle löschen</span>`,
message: `Möchtest du die Rolle <b style="color:red;">${name}</b> wirklich löschen`,
buttons: {
no: { text: 'Nein' },
yes: {
text: '<b style=color:red>Ja</b>',
onClick: async () => {
await RBAC.deleteRole(id);
sendUserEvent('RBAC', `Rolle ${name} gelöscht`, null, 0);
loadRoles();
loadUsers(); // optional, falls RoleCount betroffen
}
}
}
});
}
////////////////////////////// //////////////////////////////
// 🚀 INIT // 🚀 INIT
@@ -308,7 +382,7 @@ async function deleteGroup(guid, name) {
loadUsers(); loadUsers();
loadGroups(); loadGroups();
loadRoles();
// const ctx = new ContextMenu(); // const ctx = new ContextMenu();

View File

@@ -36,6 +36,7 @@ section {
transition: background var(--times-transition-colors) ease, border-color var(--times-transition-colors) ease, color var(--times-transition-colors) ease; transition: background var(--times-transition-colors) ease, border-color var(--times-transition-colors) ease, color var(--times-transition-colors) ease;
} }
section:hover { section:hover {
background: var(--theme-accent-hover-backcolor); background: var(--theme-accent-hover-backcolor);
color: var(--theme-accent-hover-color); color: var(--theme-accent-hover-color);
@@ -101,7 +102,7 @@ input {
<input id="newRoleName" placeholder="Role Name" /> <input id="newRoleName" placeholder="Role Name" />
<button class="bluebutton" onclick="createRole()">Create Role</button> <button class="bluebutton" onclick="createRole()">Create Role</button>
<div id="rbacGroupContainer"> <div id="rbacRoleContainer">
<span>ROLLEN WERDEN GELADEN . . .</span> <span>ROLLEN WERDEN GELADEN . . .</span>
</div> </div>
</div> </div>

View File

@@ -116,6 +116,7 @@ const server = https.createServer(httpsOptions, app);
databaseModel.set('authenticationOverviewView', require(`@models/authenticationOverviewView`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('authenticationOverviewView', require(`@models/authenticationOverviewView`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('groupOverviewView', require(`@models/groupOverviewView`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('groupOverviewView', require(`@models/groupOverviewView`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('roleOverviewView', require(`@models/roleOverviewView`)(service.get('sqlManager').getInstance('main')));
service.set('rbacManager', new RBACManager(databaseModel, runtimeFile.configuration.live.integration.token.secret)); service.set('rbacManager', new RBACManager(databaseModel, runtimeFile.configuration.live.integration.token.secret));
service.set('authenticationManager', new AuthenticationManager(databaseModel.get('authentication'), runtimeFile.configuration.live.integration.token.secret)); service.set('authenticationManager', new AuthenticationManager(databaseModel.get('authentication'), runtimeFile.configuration.live.integration.token.secret));

View File

@@ -0,0 +1,36 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize ) => {
return sequelize.define('vRoleOverview', {
ID: {
type: DataTypes.INTEGER,
primaryKey: true
},
Name: {
type: DataTypes.STRING
},
Description: {
type: DataTypes.STRING
},
RoleType: {
type: DataTypes.STRING
},
GroupCount: {
type: DataTypes.INTEGER
},
UserCount: {
type: DataTypes.INTEGER
}
}, {
tableName: 'vRoleOverview', // 🔥 wichtig: Name deiner VIEW
timestamps: false,
freezeTableName: true
});
};

View File

@@ -249,17 +249,31 @@ module.exports = {
// ========================================================= // =========================================================
// 🎭 ROLES // 🎭 ROLES
// ========================================================= // =========================================================
app.post('/api/rbac/role/get', async (req, res) => {
app.post('/api/role', async (req, res) => {
try { try {
const role = await service.get('rbacManager').createRole(req.body); rbacRoles = await service.get('rbacManager').getRole();
res.json(role); res.json(rbacRoles);
} catch (err) { } catch (err) {
res.status(500).json({ error: err.message }); res.status(500).json({ error: err.message });
} }
}); });
app.put('/role/:id', async (req, res) => { app.post('/api/rbac/role/create', async (req, res) => {
try {
if(rbacRoles.map(role => role.Name.toLowerCase()).includes(req.body.name.toLowerCase())) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 2, 'RBAC', `${req.body.name} nicht angelegt.\r\nGruppe existiert bereits`);
return res.status(400).json({ error: `${req.body.name} existiert bereits` });
}
const role = await service.get('rbacManager').createRole(req.body);
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 0, 'RBAC', `Gruppe ${role.Name} [${role.ID}] angelegt`);
res.json(role);
} catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err.message);
res.status(500).json({ error: err.message });
}
});
app.put('/api/rbac/role/:id', async (req, res) => {
try { try {
await service.get('rbacManager').updateRole(req.params.id, req.body); await service.get('rbacManager').updateRole(req.params.id, req.body);
res.json({ ok: true }); res.json({ ok: true });
@@ -268,11 +282,18 @@ module.exports = {
} }
}); });
app.delete('/role/:id', async (req, res) => { app.delete('/api/rbac/role/:id', async (req, res) => {
try { try {
await service.get('rbacManager').deleteRole(req.params.id); const role = await databaseModel.get('role').findOne({ where: { ID: req.params.id }, raw: true });
res.json({ ok: true }); if(!role) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 2, 'RBAC', `Gruppen-GUID ${req.params.guid} nicht gefunden`);
res.status(400);
}
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 0, 'RBAC', `Gruppe ${role.Name} [${role.ID}] gelöscht`);
await service.get('rbacManager').deleteRole(role.ID);
res.status(200).json(role);
} catch (err) { } catch (err) {
service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err);
res.status(500).json({ error: err.message }); res.status(500).json({ error: err.message });
} }
}); });

View File

@@ -346,6 +346,10 @@ async removeUserFromGroup(authId, groupId) {
// ========================================================= // =========================================================
// 🎭 ROLE CRUD // 🎭 ROLE CRUD
// ========================================================= // =========================================================
async getRole() {
const role = this.db.get('roleOverviewView');
return await role.findAll({ raw: true }) || [];
}
async createRole(data) { async createRole(data) {
const Role = this.db.get('rolesModel'); const Role = this.db.get('rolesModel');