rbac outsourced

This commit is contained in:
2026-04-26 07:39:30 +00:00
parent 3081b7a29d
commit fa96ed5976
5 changed files with 203 additions and 95 deletions

View File

@@ -98,6 +98,7 @@ const server = https.createServer(httpsOptions, app);
let AuthenticationManager = require(`@services/authenticationManager.js`); let AuthenticationManager = require(`@services/authenticationManager.js`);
let ActiveDirectory = require(`@services/activeDirectoryManager.js`); let ActiveDirectory = require(`@services/activeDirectoryManager.js`);
let VaultifyManager = require(`@services/vaultifyManager.js`); let VaultifyManager = require(`@services/vaultifyManager.js`);
let RBACManager = require(`@services/rbacManager.js`);
service.set('socketManager', new SocketManager(io)); service.set('socketManager', new SocketManager(io));
await service.get('socketManager').addAsync('/'); await service.get('socketManager').addAsync('/');
@@ -133,7 +134,9 @@ const server = https.createServer(httpsOptions, app);
databaseModel.set('permissionModel', require(`@models/permissionModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('permissionModel', require(`@models/permissionModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('roleModel', require(`@models/roleModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('roleModel', require(`@models/roleModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('rolePermissionsModel', require(`@models/rolePermissionsModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('rolePermissionsModel', require(`@models/rolePermissionsModel`)(service.get('sqlManager').getInstance('main')));
service.set('authenticationManager', new AuthenticationManager(databaseModel.get('authentication'), app.locals.configuration.integration.token.secret, databaseModel)); service.set('authenticationManager', new AuthenticationManager(databaseModel.get('authentication'), app.locals.configuration.integration.token.secret, databaseModel));
service.set('rbacManager', new RBACManager(databaseModel));
service.set('activeDirectoryManager', new ActiveDirectory(app.locals.configuration.integration.activedirectory)) service.set('activeDirectoryManager', new ActiveDirectory(app.locals.configuration.integration.activedirectory))
@@ -210,6 +213,7 @@ const server = https.createServer(httpsOptions, app);
service.get('eventManager').write(null, plugin.levelId, null, `${plugin.pluginName} v${plugin.metadata.version} ${plugin.message}`); service.get('eventManager').write(null, plugin.levelId, null, `${plugin.pluginName} v${plugin.metadata.version} ${plugin.message}`);
}); });
//#region Menu-Generator //#region Menu-Generator
app.use(async (req, res, next) => { app.use(async (req, res, next) => {
next(); next();
@@ -223,6 +227,8 @@ const server = https.createServer(httpsOptions, app);
require(`${app.locals.path.source}/routes/adminRoutes.js`).route(app, service); // #3 - token security always enabled require(`${app.locals.path.source}/routes/adminRoutes.js`).route(app, service); // #3 - token security always enabled
//#endregion //#endregion
app.use(service.get('authenticationManager').authenticate());
app.use(service.get('authenticationManager').requirePermissionMiddleware());
//#region Implements sockets //#region Implements sockets
require(`${app.locals.path.source}/sockets/mainSocket.js`)( require(`${app.locals.path.source}/sockets/mainSocket.js`)(

View File

@@ -11,10 +11,8 @@ const { doesNotReject } = require('assert');
module.exports = { module.exports = {
route: function(app, service) { route: function(app, service) {
app.use(service.get('authenticationManager').authenticate());
app.get('/', async (req, res) => { app.get('/', async (req, res) => {
const startMenuItems = await global.startMenuItems(app, req.cookies.ObjectGUID, false); const startMenuItems = await global.startMenuItems(app, req.cookies.ObjectGUID, true);
res.render('desktop', { layout: 'default', startMenuItems: startMenuItems }); res.render('desktop', { layout: 'default', startMenuItems: startMenuItems });
}); });

View File

@@ -2,10 +2,10 @@ const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs');
class AuthenticationManager { class AuthenticationManager {
constructor(model, secretKey, databaseModel) { constructor(model, secretKey, rbacService) {
this.Authentication = model; this.Authentication = model;
this.SECRET_KEY = secretKey; this.SECRET_KEY = secretKey;
this.databaseModel = databaseModel; this.rbac = rbacService;
} }
// ========================================================= // =========================================================
@@ -36,79 +36,7 @@ class AuthenticationManager {
} }
// ========================================================= // =========================================================
// RBAC RESOLVER (LIVE - WICHTIG!) // LOGIN
// =========================================================
async resolvePermissions(objectGuid) {
const AuthenticationGroups = this.databaseModel.get('authenticationGroupsModel');
const GroupClosure = this.databaseModel.get('groupClosureModel');
const AuthenticationRoles = this.databaseModel.get('authenticationRolesModel');
const GroupRoles = this.databaseModel.get('groupRolesModel');
const RolePermissions = this.databaseModel.get('rolePermissionsModel');
const Permission = this.databaseModel.get('permissionModel');
// 1. USER GROUPS
const userGroups = await AuthenticationGroups.findAll({
where: { Authentication_ObjectGUID: objectGuid }
});
const directGroupIds = userGroups.map(g => g.Group_ObjectGUID);
// 2. NESTED GROUPS
let allGroupIds = [...directGroupIds];
if (directGroupIds.length) {
const closure = await GroupClosure.findAll({
where: { ParentGroup_ObjectGUID: directGroupIds }
});
allGroupIds.push(...closure.map(c => c.ChildGroup_ObjectGUID));
}
allGroupIds = [...new Set(allGroupIds)];
// 3. ROLES
const userRoles = await AuthenticationRoles.findAll({
where: { Authentication_ObjectGUID: objectGuid }
});
const groupRoles = await GroupRoles.findAll({
where: { Group_ObjectGUID: allGroupIds }
});
const roleIds = [
...new Set([
...userRoles.map(r => r.Role_ID),
...groupRoles.map(r => r.Role_ID)
])
];
// 4. PERMISSIONS
const rolePerms = await RolePermissions.findAll({
where: { Role_ID: roleIds }
});
const permissionIds = rolePerms.map(r => r.Permission_ID);
const permissions = await Permission.findAll({
where: { ID: permissionIds }
});
// 🔥 HIER DIE ÄNDERUNG
return {
groups: allGroupIds,
roles: roleIds,
permissions: permissions.map(p => ({
id: p.ID,
scope: p.Scope,
resource: p.Resource,
action: p.Action
}))
};
}
// =========================================================
// LOGIN (minimal change)
// ========================================================= // =========================================================
async login(sAMAccountName, password) { async login(sAMAccountName, password) {
@@ -142,11 +70,7 @@ async resolvePermissions(objectGuid) {
user.online = true; user.online = true;
await user.save(); await user.save();
return { return { token, levelId: 0, message: 'Erfolgreich angemeldet' };
token,
levelId: 0,
message: 'Erfolgreich angemeldet'
};
} }
// ========================================================= // =========================================================
@@ -168,7 +92,7 @@ async resolvePermissions(objectGuid) {
} }
// ========================================================= // =========================================================
// VERIFY TOKEN (unchanged) // VERIFY TOKEN
// ========================================================= // =========================================================
async verifyUserToken(sAMAccountName) { async verifyUserToken(sAMAccountName) {
@@ -192,22 +116,36 @@ async resolvePermissions(objectGuid) {
} }
// ========================================================= // =========================================================
// 🔥 AUTH MIDDLEWARE (HIER PASSIERT DIE MAGIE) // 🔥 MIDDLEWARE BLEIBT HIER
// ========================================================= // =========================================================
authenticate() { authenticate() {
return async (req, res, next) => { return async (req, res, next) => {
try { try {
// 🔥 SKIP PUBLIC ROUTES // =====================================================
if ( // 🔥 GLOBAL PUBLIC ROUTE BYPASS (ROBUST)
req.path.startsWith('/login') || // =====================================================
req.path.startsWith('/public')
) { const url = req.originalUrl.split('?')[0];
const publicRoutes = [
'/login',
'/public'
];
const isPublicRoute = publicRoutes.some(route =>
url === route || url.startsWith(route + '/')
);
if (isPublicRoute) {
return next(); return next();
} }
// =====================================================
// 🔐 AUTH FLOW
// =====================================================
const sAMAccountName = req.cookies?.sAMAccountName; const sAMAccountName = req.cookies?.sAMAccountName;
if (!sAMAccountName) { if (!sAMAccountName) {
@@ -228,14 +166,18 @@ async resolvePermissions(objectGuid) {
return res.redirect('/login'); return res.redirect('/login');
} }
const rbac = await this.resolvePermissions(user.ObjectGUID); const rbac = await this.rbac.resolvePermissions(user.ObjectGUID);
const normalized = this.rbac.normalize(rbac.permissions);
const isSuperAdmin = this.rbac.isSuperAdmin(normalized);
req.user = { req.user = {
...user.toJSON(), ...user.toJSON(),
jwt: payload, jwt: payload,
groups: rbac.groups, groups: rbac.groups,
roles: rbac.roles, roles: rbac.roles,
permissions: rbac.permissions permissions: normalized,
isSuperAdmin
}; };
next(); next();
@@ -246,6 +188,52 @@ async resolvePermissions(objectGuid) {
} }
}; };
} }
// =========================================================
// 🔐 GLOBAL RBAC MIDDLEWARE (app.use)
// =========================================================
//
// USAGE:
// app.get('/admin/users', (req, res) => {
// if (!req.auth.hasPermission([
// { scope: 'USER', action: 'READ', resource: 'USERS' }
// ])) {
// return res.status(403).send('Forbidden');
// }
// res.json({ ok: true });
// });
requirePermissionMiddleware() {
return async (req, res, next) => {
try {
// 🔥 wenn noch kein User da ist → Auth Middleware fehlt
if (!req.user) {
return next(); // oder 401 wenn du streng sein willst
}
const rbac = this.rbac;
const permissions = req.user.permissions || [];
const isSuperAdmin = req.user.isSuperAdmin || false;
req.auth = {
permissions,
isSuperAdmin,
hasPermission: (required) =>
rbac.hasPermission(permissions, required, isSuperAdmin)
};
return next();
next();
} catch (err) {
console.error('[RBAC MIDDLEWARE ERROR]', err);
return res.status(500).json({ message: 'RBAC Fehler' });
}
};
}
} }
module.exports = AuthenticationManager; module.exports = AuthenticationManager;

116
src/services/rbacManager.js Normal file
View File

@@ -0,0 +1,116 @@
// rbac/RbacService.js
class RBACManager {
constructor(databaseModel) {
this.db = databaseModel;
}
async resolvePermissions(objectGuid) {
const AuthenticationGroups = this.db.get('authenticationGroupsModel');
const GroupClosure = this.db.get('groupClosureModel');
const AuthenticationRoles = this.db.get('authenticationRolesModel');
const GroupRoles = this.db.get('groupRolesModel');
const RolePermissions = this.db.get('rolePermissionsModel');
const Permission = this.db.get('permissionModel');
// 1. USER GROUPS
const userGroups = await AuthenticationGroups.findAll({
where: { Authentication_ObjectGUID: objectGuid }
});
const directGroupIds = userGroups.map(g => g.Group_ObjectGUID);
// 2. NESTED GROUPS
let allGroupIds = [...directGroupIds];
if (directGroupIds.length) {
const closure = await GroupClosure.findAll({
where: { ParentGroup_ObjectGUID: directGroupIds }
});
allGroupIds.push(...closure.map(c => c.ChildGroup_ObjectGUID));
}
allGroupIds = [...new Set(allGroupIds)];
// 3. ROLES
const userRoles = await AuthenticationRoles.findAll({
where: { Authentication_ObjectGUID: objectGuid }
});
const groupRoles = await GroupRoles.findAll({
where: { Group_ObjectGUID: allGroupIds }
});
const roleIds = [
...new Set([
...userRoles.map(r => r.Role_ID),
...groupRoles.map(r => r.Role_ID)
])
];
// 4. PERMISSIONS
const rolePerms = await RolePermissions.findAll({
where: { Role_ID: roleIds }
});
const permissionIds = rolePerms.map(r => r.Permission_ID);
const permissions = await Permission.findAll({
where: { ID: permissionIds }
});
return {
groups: allGroupIds,
roles: roleIds,
permissions: permissions.map(p => ({
id: p.ID,
scope: p.Scope,
resource: p.Resource,
action: p.Action
}))
};
}
// 🔥 SUPER CLEAN CHECK (wiederverwendbar überall)
isSuperAdmin(permissions) {
return permissions.some(p =>
p.scope === 'SYSTEM' &&
p.resource === 'ALL' &&
p.action === 'ALL'
);
}
// 🔥 GENERIC CHECK FUNCTION (WICHTIG)
hasPermission(userPerms, requiredPerms, isSuperAdmin = false) {
if (isSuperAdmin) return true;
return userPerms.some(userPerm =>
requiredPerms.some(required => {
const scopeMatch = userPerm.scope === required.scope;
const actionMatch =
userPerm.action === 'ALL' ||
userPerm.action === required.action ||
required.action === 'ALL';
const resourceMatch =
!userPerm.resource ||
userPerm.resource === 'ALL' ||
userPerm.resource === required.resource;
return scopeMatch && actionMatch && resourceMatch;
})
);
}
normalize(permissions) {
return permissions.map(p => ({
scope: p.scope,
action: p.action,
resource: p.resource || null
}));
}
}
module.exports = RBACManager;

View File

@@ -53,10 +53,10 @@ module.exports = startMenuItems = async function (app, objectGuid, debug = false
// ========================= // =========================
// Load user permissions // Load user permissions
// ========================= // =========================
const authManager = service.get('authenticationManager'); const rbacManager = service.get('rbacManager');
const userPermissions = const userPermissions =
(await authManager.resolvePermissions(objectGuid)) (await rbacManager.resolvePermissions(objectGuid))
?.permissions || []; ?.permissions || [];
const normalizedPermissions = userPermissions.map(p => ({ const normalizedPermissions = userPermissions.map(p => ({