From 76a30fc94faaac2fa3cebc43e98f3eb15b35a412 Mon Sep 17 00:00:00 2001 From: "manuel.sowada" Date: Thu, 30 Apr 2026 14:22:12 +0200 Subject: [PATCH] rbac roles implementation --- dbcreate.sql | 10 +++ public/javascript/rbacAPI.js | 140 +++++++++++++++++++++++++-------- public/views/rbac.hbs | 3 +- server.js | 1 + src/models/roleOverviewView.js | 36 +++++++++ src/routes/adminRoutes.js | 39 ++++++--- src/services/rbacManager.js | 4 + 7 files changed, 190 insertions(+), 43 deletions(-) create mode 100644 src/models/roleOverviewView.js diff --git a/dbcreate.sql b/dbcreate.sql index ca4be61..f2e4d9c 100644 --- a/dbcreate.sql +++ b/dbcreate.sql @@ -492,6 +492,16 @@ JOIN dbo.[Role] r 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) -- ======================================================== diff --git a/public/javascript/rbacAPI.js b/public/javascript/rbacAPI.js index e343a87..c4b66fc 100644 --- a/public/javascript/rbacAPI.js +++ b/public/javascript/rbacAPI.js @@ -22,7 +22,6 @@ const api = async (url, method = 'GET', body) => { ////////////////////////////// const RBAC = { - // 👤 USERS loadUsers: async () => (await api('/api/rbac/auth/get', 'POST')) .map(({ active, online, ...rest }) => ({ @@ -30,31 +29,24 @@ const RBAC = { Aktiv: active })) .sort((a, b) => a.sAMAccountName.localeCompare(b.sAMAccountName)), - createUser: (data) => api('/api/rbac/auth/create', 'POST', data), - deleteUser: (guid) => api(`/api/rbac/auth/${guid}`, 'DELETE'), // 👥 GROUPS loadGroups: () => api('/api/rbac/group/get', 'POST'), - createGroup: (name) => api('/api/rbac/group/create', 'POST', { name }), - 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 - addUserToGroup: (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 }), - - addPermissionToRole: (roleId, permissionId) => - api('/api/rbac/role/add-permission', 'POST', { roleId, permissionId }) - + addUserToGroup: (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 }), + 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.response = await RBAC.addUserToGroup(data.ObjectGUID, targetValue); loadUsers(); + loadGroups(); break; case 'role': process.action = `Du hast den Benutzer der Rolle hinzugefügt`; @@ -116,7 +109,7 @@ function createDropZone(el, type, target) { // 📋 TABLE (USERS) ////////////////////////////// -const vt = virtualTable({ +const rbacVT = virtualTable({ tableEl: document.querySelector('#rbacUsersTable'), data: [], rowHeight: 20, @@ -184,7 +177,7 @@ const vt = virtualTable({ async function loadUsers() { try { - vt.source(await RBAC.loadUsers()); + rbacVT.source(await RBAC.loadUsers()); } catch (err) { console.error(err); } @@ -193,29 +186,29 @@ async function loadUsers() { async function loadGroups() { const container = document.getElementById('rbacGroupContainer'); container.innerHTML = ''; - const groups = await RBAC.loadGroups(); - const fragment = document.createDocumentFragment(); - groups.forEach(group => { - const section = document.createElement('section'); const groupName = document.createElement('span'); - groupName.innerHTML = group.Name; + groupName.innerHTML = `[${group.UserCount}] ${group.Name}`; groupName.dataset.tooltip = group.Name; groupName.dataset.tooltipMode = 'ellipsis'; section.append(groupName); - if(group.ObjectGUID !== '00000000-0000-0000-0000-000000000001') { - const removeButton = document.createElement('div'); - removeButton.className = 'removeButton'; - removeButton.innerHTML = 'X'; - removeButton.onclick = async () => { - await deleteGroup(group.ObjectGUID, group.Name); - }; - section.append(removeButton); + const removeButton = document.createElement('button'); + + 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.innerHTML = 'X'; + removeButton.onclick = async () => { + await deleteGroup(group.ObjectGUID, group.Name); + }; + section.append(removeButton); + 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 ////////////////////////////// @@ -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: `Rolle löschen`, + message: `Möchtest du die Rolle ${name} wirklich löschen`, + buttons: { + no: { text: 'Nein' }, + yes: { + text: 'Ja', + onClick: async () => { + await RBAC.deleteRole(id); + + sendUserEvent('RBAC', `Rolle ${name} gelöscht`, null, 0); + + loadRoles(); + loadUsers(); // optional, falls RoleCount betroffen + } + } + } + }); +} ////////////////////////////// // 🚀 INIT @@ -308,7 +382,7 @@ async function deleteGroup(guid, name) { loadUsers(); loadGroups(); - +loadRoles(); // const ctx = new ContextMenu(); diff --git a/public/views/rbac.hbs b/public/views/rbac.hbs index 53a8fb9..59e17ce 100644 --- a/public/views/rbac.hbs +++ b/public/views/rbac.hbs @@ -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; } + section:hover { background: var(--theme-accent-hover-backcolor); color: var(--theme-accent-hover-color); @@ -101,7 +102,7 @@ input { -
+
ROLLEN WERDEN GELADEN . . .
diff --git a/server.js b/server.js index c2d1659..85bcfe1 100644 --- a/server.js +++ b/server.js @@ -116,6 +116,7 @@ const server = https.createServer(httpsOptions, app); databaseModel.set('authenticationOverviewView', require(`@models/authenticationOverviewView`)(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('authenticationManager', new AuthenticationManager(databaseModel.get('authentication'), runtimeFile.configuration.live.integration.token.secret)); diff --git a/src/models/roleOverviewView.js b/src/models/roleOverviewView.js new file mode 100644 index 0000000..76039f1 --- /dev/null +++ b/src/models/roleOverviewView.js @@ -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 + }); +}; \ No newline at end of file diff --git a/src/routes/adminRoutes.js b/src/routes/adminRoutes.js index 7c29e40..6eb8c80 100644 --- a/src/routes/adminRoutes.js +++ b/src/routes/adminRoutes.js @@ -249,17 +249,31 @@ module.exports = { // ========================================================= // 🎭 ROLES // ========================================================= - - app.post('/api/role', async (req, res) => { + app.post('/api/rbac/role/get', async (req, res) => { try { - const role = await service.get('rbacManager').createRole(req.body); - res.json(role); + rbacRoles = await service.get('rbacManager').getRole(); + res.json(rbacRoles); } catch (err) { 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 { await service.get('rbacManager').updateRole(req.params.id, req.body); res.json({ ok: true }); @@ -268,11 +282,18 @@ module.exports = { } }); - app.delete('/role/:id', async (req, res) => { - try { - await service.get('rbacManager').deleteRole(req.params.id); - res.json({ ok: true }); + app.delete('/api/rbac/role/:id', async (req, res) => { + try { + const role = await databaseModel.get('role').findOne({ where: { ID: req.params.id }, raw: 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) { + service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err); res.status(500).json({ error: err.message }); } }); diff --git a/src/services/rbacManager.js b/src/services/rbacManager.js index 550f82d..6ee5bad 100644 --- a/src/services/rbacManager.js +++ b/src/services/rbacManager.js @@ -346,6 +346,10 @@ async removeUserFromGroup(authId, groupId) { // ========================================================= // 🎭 ROLE CRUD // ========================================================= +async getRole() { + const role = this.db.get('roleOverviewView'); + return await role.findAll({ raw: true }) || []; +} async createRole(data) { const Role = this.db.get('rolesModel');