From e208ef1759e900e9b79f43d0af603ac4f9ebcf87 Mon Sep 17 00:00:00 2001 From: "manuel.sowada" Date: Fri, 1 May 2026 10:50:12 +0000 Subject: [PATCH] add rbac roles --- .gitignore | 3 +- dbcreate.sql | 25 ++- public/javascript/rbacAPI.js | 392 +++-------------------------------- public/views/rbac.hbs | 2 +- src/routes/adminRoutes.js | 65 +++--- src/services/rbacManager.js | 18 +- 6 files changed, 99 insertions(+), 406 deletions(-) diff --git a/.gitignore b/.gitignore index 9b574d8..dc4fcae 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ stylesheet.json .npmrc radix_os_*.png radix_os_icon.ico -login.hbs \ No newline at end of file +login.hbs +todo.txt diff --git a/dbcreate.sql b/dbcreate.sql index f2e4d9c..977e1f7 100644 --- a/dbcreate.sql +++ b/dbcreate.sql @@ -493,12 +493,25 @@ 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 +SELECT + r.ID, + r.Name, + r.Description, + r.RoleType, + + COUNT(DISTINCT gr.Group_ObjectGUID) AS GroupCount, + COUNT(DISTINCT vau.ObjectGUID) AS UserCount + +FROM dbo.Role AS r + +LEFT JOIN dbo.vAuthenticationRoles AS vau + ON vau.Role_ID = r.ID + +LEFT JOIN dbo.GroupRoles AS gr + ON gr.Role_ID = r.ID + +GROUP BY + r.ID, r.Name, r.Description, r.RoleType; GO diff --git a/public/javascript/rbacAPI.js b/public/javascript/rbacAPI.js index c4b66fc..163e168 100644 --- a/public/javascript/rbacAPI.js +++ b/public/javascript/rbacAPI.js @@ -39,7 +39,7 @@ const RBAC = { // 🎭 ROLES loadRoles: () => api('/api/rbac/role/get', 'POST'), - createRole: (name) => api('/api/rbac/role', 'POST', { name }), + createRole: (name) => api('/api/rbac/role/create', 'POST', { name }), deleteRole: (id) => api(`/api/rbac/role/${id}`, 'DELETE'), // 🔗 ASSIGNMENTS @@ -53,15 +53,15 @@ const RBAC = { // 🖱️ DRAG & DROP ////////////////////////////// -function createDragZone(el, data) { +function createDragZone(el, data, type) { el.draggable = true; el.addEventListener('dragstart', (evt) => { - evt.dataTransfer.setData('application/json', JSON.stringify(data)); + evt.dataTransfer.setData('application/json', JSON.stringify( { ...data, type })); }); } function createDropZone(el, type, target) { - const targetValue = target.ObjectGUID || target.Role_ID || target.Permission_ID; + const targetValue = target.ObjectGUID || target.ID || target.Role_ID || target.Permission_ID; let process = { action: null, response: null, failure: false }; el.addEventListener('dragover', e => e.preventDefault()); @@ -72,39 +72,45 @@ function createDropZone(el, type, target) { e.dataTransfer.getData('application/json') ); - if(!targetValue) { + if(!targetValue || !data.type) { sendUserEvent('RBAC', 'Deinem Drop-Ziel wurde kein ID-Attribut zugewiesen. Frag einfach Manuel ', null, 2); return; } + // NOCH NICHT UNTER ADMINROUTES EDITIERT: WENN BENUTZER/GRUPPE BEREITS IN ROLLE ENTHALTEN IST switch (type) { case 'group': - process.action = `Du hast den Benutzer der Gruppe hinzugefügt`; - process.response = await RBAC.addUserToGroup(data.ObjectGUID, targetValue); - loadUsers(); - loadGroups(); + if(data.type === 'user') { + process.action = `Du hast den Benutzer der Gruppe hinzugefügt`; + process.response = await RBAC.addUserToGroup(data.ObjectGUID, targetValue); + } break; case 'role': - process.action = `Du hast den Benutzer der Rolle hinzugefügt`; - process.response = await RBAC.addUserToRole(data.ObjectGUID, targetValue); - break; - - case 'group-to-role': - process.action = `Du hast die Gruppe der Rolle hinzugefügt`; - process.response = await RBAC.addGroupToRole(data.ObjectGUID, targetValue); - break; - + if(data.type === 'user') { + process.action = `Du hast den Benutzer der Rolle hinzugefügt`; + process.response = await RBAC.addUserToRole(data.ObjectGUID, targetValue); + break; + } + if(data.type === 'group') { + process.action = `Du hast die Gruppe der Rolle hinzugefügt`; + process.response = await RBAC.addGroupToRole(data.ObjectGUID, targetValue); + break; + } case 'permission': - process.action = `Du hast den Berechtigung der Rolle hinzugefügt`; + process.action = `Du hast die Berechtigung der Rolle hinzugefügt`; process.response = await RBAC.addPermissionToRole(targetValue, data.ID); break; } - if(process.failure) return; + if(process.failure || !process.action) return; + loadUsers(); + loadGroups(); + loadRoles(); sendUserEvent('RBAC', process.action, null, 0); }); } + ////////////////////////////// // 📋 TABLE (USERS) ////////////////////////////// @@ -127,7 +133,7 @@ const rbacVT = virtualTable({ customRender: (row, tr) => { - createDragZone(tr, row); + createDragZone(tr, row, 'user'); tr.addEventListener('contextmenu', (evt) => { evt.preventDefault(); @@ -209,7 +215,7 @@ async function loadGroups() { }; section.append(removeButton); - + createDragZone(section, group, 'group'); createDropZone(section, 'group', group); fragment.appendChild(section); @@ -243,7 +249,7 @@ async function loadRoles() { const section = document.createElement('section'); const roleName = document.createElement('span'); - roleName.innerHTML = role.Name; + roleName.innerHTML = `[${role.UserCount}][${role.GroupCount}] ${role.Name}`; roleName.dataset.tooltip = role.Name; section.append(roleName); @@ -258,12 +264,7 @@ async function loadRoles() { 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); + createDropZone(section, 'role', role); fragment.appendChild(section); }); @@ -384,336 +385,3 @@ loadUsers(); loadGroups(); loadRoles(); - -// const ctx = new ContextMenu(); -// const vt = virtualTable({ -// tableEl: document.querySelector('#rbacUsersTable'), -// data: [], -// rowHeight: 20, -// buffer: 5, -// groupKey: 'ObjectSourceName', // optional zum Gruppieren -// rowKey: 'ObjectGUID', -// filterConfig: { -// hideCounter: true, -// exceptedColumns: ['', 'Rollen', 'Gruppen'], -// columnModes: { -// Aktiv: 'dropdown', -// Online: 'dropdown' -// } -// }, -// customRender: (row, tr) => { -// tr.draggable = true; -// tr.dataset.guid = row['ObjectGUID']; -// tr.dataset.sAMAccountName = row['sAMAccountName']; - -// tr.ondragstart = (evt) => { -// const data = { -// guid: tr.dataset.guid, -// sAMAccountName: tr.dataset.sAMAccountName -// }; -// evt.dataTransfer.setData('application/json', JSON.stringify(data)); -// }; - - -// tr.oncontextmenu = async evt => { -// if (evt.target.tagName === 'BUTTON') return; -// ctx.setItems([ { label: "Details", onClick: () => showAuthDetails(row['ObjectGUID']) } ]); -// evt.preventDefault(); -// ctx.show(evt.pageX + 5, {y: evt.pageY + 5 }); -// }; - -// createTd(tr, -// ``, { -// styles: { -// 'position': 'sticky', -// 'left': '0px', -// 'width': '20px', -// 'z-index': '2' -// }, classes: [ -// 'text-align:left' -// ], onclick: () => { -// if(row['ObjectGUID'] === '00000000-0000-0000-0000-000000000001') return; -// deleteUser(row['ObjectGUID'], row['sAMAccountName']); -// } -// }); - -// createTd(tr, row['ObjectGUID'], { classes: [ 'text-align:left' ], styles: { 'max-width': '100px' }, attributes: { 'data-tooltip': row['ObjectGUID'] } }); -// createTd(tr, row['sAMAccountName'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['sAMAccountName'] } }); -// createTd(tr, row['sn'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['sn'] } }); -// createTd(tr, row['givenName'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['givenName'] } }); -// createTd(tr, row['mail'], { classes: [ 'text-align:left' ], attributes: { 'data-tooltip': row['mail'] } }); -// createTd(tr, row['active'], { classes: [ 'text-align:center' ] }); -// createTd(tr, row['online'], { classes: [ 'text-align:center' ] }); -// createTd(tr, row['RoleCount'], { classes: [ 'text-align:center' ] }); -// createTd(tr, row['GroupCount'], { classes: [ 'text-align:center' ] }); -// } -// }); - - -// async function api(url, method = 'GET', body) { -// const res = await fetch(url, { -// method, -// headers: { 'Content-Type': 'application/json' }, -// body: body ? JSON.stringify(body) : undefined -// }); - -// return res.json(); -// } - - -// async function showAuthDetails(guid) { -// const details = await api(`/api/rbac/auth/details/${guid}`) -// if(!details) return; -// feedbox({ -// title: `Details`, -// message: Object.entries(details).map(([key, value]) => /*html*/`

${key}:${value}

`).join(''), -// buttons: { -// yes: { -// text: 'Schließen' -// } -// }, -// lock: true, -// primary: 'yes' -// }) -// } - - -// async function createUser() { -// const name = document.getElementById('newUserName').value; -// if(!name || !name.includes('.')) { -// sendUserEvent('RBAC', 'Der sAMAccountName muss einen Punkt enthalten
Der Benutzer wurde nicht angelegt', null, 2); -// return; -// } -// const sn = name.split('.')[1]; -// const givenName = name.split('.')[0]; -// const mail = `${name}@test.com`; - -// const user = await api('/api/rbac/auth/create', 'POST', { -// sAMAccountName: name, -// mail: mail, -// sn: sn[0].toUpperCase() + sn.slice(1), -// givenName: givenName[0].toUpperCase() + givenName.slice(1) -// }); -// if(user) { -// sendUserEvent('RBAC', `Benutzer ${sn[0].toUpperCase() + sn.slice(1)}, ${givenName[0].toUpperCase() + givenName.slice(1)} angelegt`, null, 0); -// loadUsers(); -// } -// } - - -// async function deleteUser(guid, sAMAccountName) { -// feedbox({ -// title: `Benutzer löschen`, -// message: ` -//

Möchtest du den Benutzer ${sAMAccountName} [${guid}] wirklich löschen

-// `, -// buttons: { -// no: { -// text: 'Nein' -// }, -// yes: { -// text: 'Ja', -// onClick: async () => { -// const user = await api(`/api/rbac/auth/${guid}`, 'DELETE', { -// guid -// }); -// if(user) { -// loadUsers(); -// sendUserEvent('RBAC', `Benutzer ${user.sAMAccountName || ''} [${user.ObjectGUID}] gelöscht`, null, 0); -// } -// } -// } -// } -// }); -// } - - -// async function loadUsers() { -// try { -// const users = await api('/api/rbac/auth/get', 'POST'); -// if(users) { -// vt.source(users); -// return; -// } -// sendUserEvent('RBAC', 'Benutzer konnten nicht geladen', null, 4); -// } catch(err) { -// writeEventLog(4, 'RBAC', err); -// } -// } - - -// async function createGroup() { -// const name = document.getElementById('newGroupName').value; -// const group = await api('/api/rbac/group/create', 'POST', { -// name -// }); -// if(group) { -// sendUserEvent('RBAC', `Gruppe ${name} angelegt`, null, 0); -// loadGroups(); -// } -// } - - -// async function deleteGroup(guid, name) { -// feedbox({ -// title: `Gruppe löschen`, -// message: ` -//

Möchtest du die Gruppe ${name} [${guid}] wirklich löschen

-// `, -// buttons: { -// no: { -// text: 'Nein' -// }, -// yes: { -// text: 'Ja', -// onClick: async () => { -// const group = await api(`/api/rbac/group/${guid}`, 'DELETE', { -// guid -// }); -// if(group) { -// sendUserEvent('RBAC', `Gruppe ${group.Name || ''} [${group.ObjectGUID}] gelöscht`, null, 0); -// loadGroups(); -// } -// } -// } -// } -// }); -// // const name = document.getElementById('newGroupName').value; -// // const group = await api('/api/rbac/group/create', 'POST', { -// // name -// // }); -// // if(group) { -// // sendUserEvent('RBAC', `Gruppe ${name} angelegt`, null, 0); -// // loadGroups(); -// // } -// } - - -// // HIER WEITER - GRUPPEN KARTEN MÜSSEN HÜBSCHER WERDEN. -// // BENUTZER UND GRUPPEN KÖNNEN NOCH NICHT GELÖSCHT WERDEN. -// // GRUPPEN AUCH OBJECTSOURCE_ID 1? -// async function loadGroups() { -// try { -// const rbacGroupContainer = document.getElementById('rbacGroupContainer'); -// rbacGroupContainer.innerHTML = ''; -// const groups = await api('/api/rbac/group/get', 'POST'); -// if(groups) { -// let fragment = document.createDocumentFragment(); -// groups.forEach(group => { -// const section = document.createElement('section'); - -// createDropZone(section, 'group', group.ObjectGUID); - -// section.innerHTML = `${group.Name}
X
`; -// section.dataset.tooltip = group.Name; -// fragment.appendChild(section); -// }); -// rbacGroupContainer.innerHTML = ''; -// rbacGroupContainer.appendChild(fragment); -// return; -// } -// sendUserEvent('RBAC', 'Gruppen konnten nicht geladen', null, 4); -// } catch(err) { -// writeEventLog(4, 'RBAC', err); -// } -// } - -// loadUsers(); -// loadGroups(); - -// async function createRole() { -// const name = document.getElementById('newRoleName').value; - -// await api('/api/role', 'POST', { -// name -// }); - -// loadRoles(); -// } - -// async function loadRoles() { -// document.getElementById('roleList').innerHTML = 'Reload roles...'; -// } - - -// async function createPermission() { -// const scope = document.getElementById('permScope').value; -// const resource = document.getElementById('permResource').value; -// const action = document.getElementById('permAction').value; - -// await api('/permission', 'POST', { -// scope, -// resource, -// action -// }); - -// alert('Permission created'); -// } - - - -// function createDropZone(element, direction, keyAttribute) { -// element.addEventListener('dragover', (evt) => { -// evt.preventDefault(); -// }); - -// // 📥 Drop verarbeiten -// element.addEventListener('drop', (evt) => { -// evt.preventDefault(); -// const attributeValue = element.dataset[keyAttribute]; // ObjectGuid or ID - -// const data = JSON.parse( -// evt.dataTransfer.getData('application/json') -// ); -// switch (direction) { -// case 'user-to-group': -// addUserToGroup(data.guid, attributeValue); -// break; -// case 'user-to-role': -// addPermissionToRole(data.guid, attributeValue); -// break; -// case 'group-to-role': -// addGroupToRole(data.guid, attributeValue); -// break; -// case 'permission-to-role': -// addPermissionToRole(data.roleId, attributeValue) -// default: -// break; -// } -// }); -// } - - - -// async function addUserToGroup(authGuid, groupGuid) { -// await api('/api/rbac/group/add-user', 'POST', { -// authGuid, -// groupGuid -// }); -// } - -// async function addUserToRole(authGuid, roleId) { -// await api('/api/rbac/role/add-user', 'POST', { -// authGuid, -// roleId -// }); -// } - -// async function addGroupToRole(groupGuid, roleId) { -// await api('/api/rbac/role/add-group', 'POST', { -// groupGuid, -// roleId -// }); -// } - - -// async function addPermissionToRole(roleId, permissionId) { -// await api('/api/rbac/role/add-permission', 'POST', { -// roleId, -// permissionId -// }); -// } \ No newline at end of file diff --git a/public/views/rbac.hbs b/public/views/rbac.hbs index 59e17ce..d903b80 100644 --- a/public/views/rbac.hbs +++ b/public/views/rbac.hbs @@ -99,7 +99,7 @@ input {
- +
diff --git a/src/routes/adminRoutes.js b/src/routes/adminRoutes.js index 6eb8c80..c563f95 100644 --- a/src/routes/adminRoutes.js +++ b/src/routes/adminRoutes.js @@ -260,12 +260,14 @@ module.exports = { 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`); + console.log(role); res.json(role); } catch (err) { service.get('eventManager').writeLog(req.cookies.ObjectGUID, 4, 'RBAC', err.message); @@ -284,12 +286,12 @@ module.exports = { app.delete('/api/rbac/role/:id', async (req, res) => { try { - const role = await databaseModel.get('role').findOne({ where: { ID: req.params.id }, raw: true }); + const role = await databaseModel.get('roleModel').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`); + service.get('eventManager').writeLog(req.cookies.ObjectGUID, 2, 'RBAC', `Rolen-ID ${req.params.id} nicht gefunden`); res.status(400); } - service.get('eventManager').writeLog(req.cookies.ObjectGUID, 0, 'RBAC', `Gruppe ${role.Name} [${role.ID}] gelöscht`); + service.get('eventManager').writeLog(req.cookies.ObjectGUID, 0, 'RBAC', `Role ${role.Name} [${role.ID}] gelöscht`); await service.get('rbacManager').deleteRole(role.ID); res.status(200).json(role); } catch (err) { @@ -302,26 +304,47 @@ module.exports = { // 🔗 ROLE ASSIGNMENTS // ========================================================= - app.post('/role/assign-user', async (req, res) => { + app.post('/api/rbac/role/add-user', async (req, res) => { try { - const { authId, roleId } = req.body; - await service.get('rbacManager').assignRoleToUser(authId, roleId); + const { authGuid, roleId } = req.body; + await service.get('rbacManager').assignRoleToUser(authGuid, roleId); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); - app.post('/role/assign-group', async (req, res) => { + app.post('/api/rbac/role/add-group', async (req, res) => { try { - const { groupId, roleId } = req.body; - await service.get('rbacManager').assignRoleToGroup(groupId, roleId); + const { groupGuid, roleId } = req.body; + await service.get('rbacManager').assignRoleToGroup(groupGuid, roleId); res.json({ ok: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); + app.post('/api/rbac/role/add-permission', async (req, res) => { + try { + const { roleId, permissionId } = req.body; + await service.get('rbacManager').addPermissionToRole(roleId, permissionId); + res.json({ ok: true }); + } catch (err) { + res.status(500).json({ error: err.message }); + } + }); + + app.post('/api/role/remove-permission', async (req, res) => { + try { + const { roleId, permissionId } = req.body; + await service.get('rbacManager').removePermissionFromRole(roleId, permissionId); + res.json({ ok: true }); + } catch (err) { + res.status(500).json({ error: err.message }); + } + }); + + // ========================================================= // 🔐 PERMISSIONS // ========================================================= @@ -353,30 +376,6 @@ module.exports = { } }); - // ========================================================= - // 🔗 ROLE ↔ PERMISSION - // ========================================================= - - app.post('/role/add-permission', async (req, res) => { - try { - const { roleId, permissionId } = req.body; - await service.get('rbacManager').addPermissionToRole(roleId, permissionId); - res.json({ ok: true }); - } catch (err) { - res.status(500).json({ error: err.message }); - } - }); - - app.post('/api/role/remove-permission', async (req, res) => { - try { - const { roleId, permissionId } = req.body; - await service.get('rbacManager').removePermissionFromRole(roleId, permissionId); - res.json({ ok: true }); - } catch (err) { - res.status(500).json({ error: err.message }); - } - }); - diff --git a/src/services/rbacManager.js b/src/services/rbacManager.js index 6ee5bad..d2c5cf1 100644 --- a/src/services/rbacManager.js +++ b/src/services/rbacManager.js @@ -352,7 +352,7 @@ async getRole() { } async createRole(data) { - const Role = this.db.get('rolesModel'); + const Role = this.db.get('roleModel'); return await Role.create({ Name: data.name, @@ -361,7 +361,7 @@ async createRole(data) { } async updateRole(id, data) { - const Role = this.db.get('rolesModel'); + const Role = this.db.get('roleModel'); return await Role.update(data, { where: { ID: id } @@ -369,8 +369,20 @@ async updateRole(id, data) { } async deleteRole(id) { - const Role = this.db.get('rolesModel'); + const Role = this.db.get('roleModel'); + const RolePermissions = this.db.get('rolePermissionsModel'); + const GroupRoles = this.db.get('groupRolesModel'); + const AuthRoles = this.db.get('authenticationRolesModel'); + await AuthRoles.destroy({ + where: { Role_ID: id } + }); + await GroupRoles.destroy({ + where: { Role_ID: id } + }); + await RolePermissions.destroy({ + where: { Role_ID: id } + }); return await Role.destroy({ where: { ID: id } });