rbac outsourced
This commit is contained in:
@@ -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`)(
|
||||||
|
|||||||
@@ -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 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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
116
src/services/rbacManager.js
Normal 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;
|
||||||
4
utils.js
4
utils.js
@@ -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 => ({
|
||||||
|
|||||||
Reference in New Issue
Block a user