add rbac + db-create script

This commit is contained in:
2026-04-25 13:31:02 +00:00
parent 44f8ecdc85
commit 84c3d9f9ba
21 changed files with 908 additions and 336 deletions

397
dbcreate.sql Normal file
View File

@@ -0,0 +1,397 @@
/* =========================================================
DATABASE
========================================================= */
IF DB_ID('Radix_OS') IS NULL
BEGIN
CREATE DATABASE Radix_OS;
END
GO
USE Radix_OS;
GO
/* =========================================================
CLEAN RESET
========================================================= */
DROP VIEW IF EXISTS dbo.vAuthenticationEffectivePermissions;
DROP VIEW IF EXISTS dbo.vAuthenticationRoles;
DROP VIEW IF EXISTS dbo.vAuthenticationGroups;
DROP VIEW IF EXISTS dbo.vGroupHierarchy;
DROP VIEW IF EXISTS dbo.vAuthentications;
DROP VIEW IF EXISTS dbo.vEventLog;
DROP VIEW IF EXISTS dbo.vNotifyTray;
DROP TABLE IF EXISTS dbo.AuthenticationRoles;
DROP TABLE IF EXISTS dbo.AuthenticationGroups;
DROP TABLE IF EXISTS dbo.GroupRoles;
DROP TABLE IF EXISTS dbo.RolePermissions;
DROP TABLE IF EXISTS dbo.GroupClosure;
DROP TABLE IF EXISTS dbo.NotifyTray;
DROP TABLE IF EXISTS dbo.NotifyTrayObjects;
DROP TABLE IF EXISTS dbo.EventLog;
DROP TABLE IF EXISTS dbo.EventLevels;
DROP TABLE IF EXISTS dbo.Authentication;
DROP TABLE IF EXISTS dbo.[Group];
DROP TABLE IF EXISTS dbo.[Role];
DROP TABLE IF EXISTS dbo.Permission;
DROP TABLE IF EXISTS dbo.Plugins;
DROP TABLE IF EXISTS dbo.ObjectSource;
DROP TABLE IF EXISTS dbo.AuthenticationUAC;
GO
/* =========================================================
CORE TABLES
========================================================= */
CREATE TABLE dbo.ObjectSource (
ID INT IDENTITY(1,1) PRIMARY KEY,
Name VARCHAR(100) NOT NULL UNIQUE
);
CREATE TABLE dbo.AuthenticationUAC (
ID INT PRIMARY KEY,
AttributeName NVARCHAR(100),
AttributeOriginal VARCHAR(255)
);
CREATE TABLE dbo.[Role] (
ID INT IDENTITY(1,1) PRIMARY KEY,
Name NVARCHAR(255) UNIQUE,
Description NVARCHAR(MAX),
RoleType VARCHAR(50)
);
CREATE TABLE dbo.Permission (
ID INT IDENTITY(1,1) PRIMARY KEY,
Scope VARCHAR(100),
Resource VARCHAR(100),
Action VARCHAR(100),
CONSTRAINT UQ_Permission UNIQUE (Scope, Resource, Action)
);
CREATE TABLE dbo.Plugins (
Name VARCHAR(50) PRIMARY KEY,
Active BIT,
Version VARCHAR(25)
);
/* =========================================================
AUTHENTICATION
========================================================= */
CREATE TABLE dbo.Authentication (
ObjectGUID UNIQUEIDENTIFIER PRIMARY KEY,
sAMAccountName VARCHAR(255),
mail VARCHAR(255),
givenName VARCHAR(255),
sn VARCHAR(255),
employeeID VARCHAR(255),
title VARCHAR(255),
department VARCHAR(255),
streetAddress VARCHAR(255),
userAccountControl_ID INT,
telephoneNumber VARCHAR(255),
physicalDeliveryOfficeName VARCHAR(255),
distinguishedName VARCHAR(MAX),
password VARCHAR(MAX),
refreshtoken VARCHAR(MAX),
active BIT,
online BIT,
ObjectSource_ID INT,
FOREIGN KEY (ObjectSource_ID) REFERENCES dbo.ObjectSource(ID)
);
CREATE TABLE dbo.[Group] (
ObjectGUID UNIQUEIDENTIFIER PRIMARY KEY,
Name VARCHAR(255),
ObjectSource_ID INT,
distinguishedName VARCHAR(MAX),
FOREIGN KEY (ObjectSource_ID) REFERENCES dbo.ObjectSource(ID)
);
/* =========================================================
GROUP CLOSURE
========================================================= */
CREATE TABLE dbo.GroupClosure (
ParentGroup_ObjectGUID UNIQUEIDENTIFIER,
ChildGroup_ObjectGUID UNIQUEIDENTIFIER,
Depth INT,
PRIMARY KEY (ParentGroup_ObjectGUID, ChildGroup_ObjectGUID)
);
/* =========================================================
RBAC
========================================================= */
CREATE TABLE dbo.AuthenticationRoles (
Authentication_ObjectGUID UNIQUEIDENTIFIER,
Role_ID INT,
PRIMARY KEY (Authentication_ObjectGUID, Role_ID)
);
CREATE TABLE dbo.AuthenticationGroups (
Authentication_ObjectGUID UNIQUEIDENTIFIER,
Group_ObjectGUID UNIQUEIDENTIFIER,
PRIMARY KEY (Authentication_ObjectGUID, Group_ObjectGUID)
);
CREATE TABLE dbo.GroupRoles (
Group_ObjectGUID UNIQUEIDENTIFIER,
Role_ID INT,
PRIMARY KEY (Group_ObjectGUID, Role_ID)
);
CREATE TABLE dbo.RolePermissions (
Role_ID INT,
Permission_ID INT,
PRIMARY KEY (Role_ID, Permission_ID)
);
/* =========================================================
EVENT SYSTEM
========================================================= */
CREATE TABLE dbo.EventLevels (
ID INT PRIMARY KEY,
LevelName VARCHAR(50),
DisplayName VARCHAR(150),
Priority INT
);
CREATE TABLE dbo.EventLog (
ID INT IDENTITY(1,1) PRIMARY KEY,
Message VARCHAR(MAX),
Trace VARCHAR(MAX),
Level_ID INT,
PluginName VARCHAR(50),
Date DATETIME2,
ObjectGUID UNIQUEIDENTIFIER
);
/* =========================================================
NOTIFY SYSTEM
========================================================= */
CREATE TABLE dbo.NotifyTrayObjects (
ID INT IDENTITY(1,1) PRIMARY KEY,
PluginName VARCHAR(50),
Message VARCHAR(MAX),
JSON VARCHAR(MAX),
ActionRequired BIT DEFAULT 0,
CreatedAt DATETIME2,
ExpiresAt DATETIME2
);
CREATE TABLE dbo.NotifyTray (
ID INT IDENTITY(1,1) PRIMARY KEY,
ObjectGUID UNIQUEIDENTIFIER,
NotifyTrayObject_ID INT,
SeenAt DATETIME2
);
/* =========================================================
SECURITY VIEWS
========================================================= */
CREATE VIEW dbo.vAuthenticationRoles AS
SELECT a.ObjectGUID, r.ID Role_ID, r.Name, 'DIRECT' Source
FROM dbo.Authentication a
JOIN dbo.AuthenticationRoles ar ON ar.Authentication_ObjectGUID = a.ObjectGUID
JOIN dbo.[Role] r ON r.ID = ar.Role_ID
UNION ALL
SELECT a.ObjectGUID, r.ID, r.Name, 'GROUP'
FROM dbo.Authentication a
JOIN dbo.AuthenticationGroups ag ON ag.Authentication_ObjectGUID = a.ObjectGUID
JOIN dbo.GroupRoles gr ON gr.Group_ObjectGUID = ag.Group_ObjectGUID
JOIN dbo.[Role] r ON r.ID = gr.Role_ID;
CREATE VIEW dbo.vAuthenticationEffectivePermissions AS
SELECT DISTINCT
a.ObjectGUID,
p.Scope,
p.Resource,
p.Action,
CONCAT(p.Scope,'.',p.Resource,'.',p.Action) PermissionKey
FROM dbo.Authentication a
JOIN dbo.vAuthenticationRoles r ON r.ObjectGUID = a.ObjectGUID
JOIN dbo.RolePermissions rp ON rp.Role_ID = r.Role_ID
JOIN dbo.Permission p ON p.ID = rp.Permission_ID;
/* =========================================================
FIXED vEventLog (SEQUELIZE MATCH + SYSTEM FIX)
========================================================= */
CREATE OR ALTER VIEW dbo.vEventLog
AS
SELECT
e.ID,
e.Message,
e.Trace,
e.Date,
e.Level_ID,
el.LevelName,
el.DisplayName AS LevelDisplayName,
el.Priority AS LevelPriority,
e.PluginName,
COALESCE(a.sn + ' ' + a.givenName, 'SYSTEM') AS ClearTextUser,
a.sn AS Surname,
a.givenName,
e.ObjectGUID,
a.sAMAccountName,
a.mail,
a.department,
a.telephoneNumber AS Phone,
a.physicalDeliveryOfficeName AS Office,
a.streetAddress AS Adress,
COALESCE(a.ObjectSource_ID, 1) AS ObjectSource_ID,
os.Name AS ObjectSourceName
FROM dbo.EventLog e
LEFT JOIN dbo.Authentication a ON a.ObjectGUID = e.ObjectGUID
LEFT JOIN dbo.EventLevels el ON el.ID = e.Level_ID
LEFT JOIN dbo.ObjectSource os ON os.ID = COALESCE(a.ObjectSource_ID, 1);
GO
/* =========================================================
AUTH VIEW
========================================================= */
CREATE VIEW dbo.vAuthentications AS
SELECT a.*, os.Name AS ObjectSource
FROM dbo.Authentication a
LEFT JOIN dbo.ObjectSource os ON os.ID = a.ObjectSource_ID;
/* =========================================================
GROUP VIEW
========================================================= */
CREATE VIEW dbo.vGroupHierarchy AS
SELECT * FROM dbo.GroupClosure;
/* =========================================================
NOTIFY VIEWS
========================================================= */
CREATE VIEW vNotifyTray AS
SELECT
n.ID,
n.ObjectGUID,
n.SeenAt,
a.sAMAccountName,
a.givenName,
a.sn,
a.mail,
a.active,
a.online,
nto.PluginName,
nto.JSON,
nto.ActionRequired,
nto.CreatedAt,
nto.Message
FROM NotifyTray n
LEFT JOIN Authentication a ON a.ObjectGUID = n.ObjectGUID
LEFT JOIN NotifyTrayObjects nto ON n.ID = n.NotifyTrayObject_ID
GO
/* =========================================================
SEED DATA
========================================================= */
INSERT INTO dbo.ObjectSource VALUES ('LOCAL'),('AD');
INSERT INTO dbo.EventLevels VALUES
(-1,'test','Test',5),
(0,'success','Success',4),
(1,'log','Log',3),
(2,'warn','Warn',2),
(4,'error','Error',1),
(8,'throw_exception','Exception',0);
INSERT INTO dbo.Plugins VALUES ('SYSTEM',1,'1.0.0');
INSERT INTO dbo.[Role] (Name,Description,RoleType)
VALUES ('ADMIN','System Administrator','SYSTEM');
INSERT INTO dbo.Permission (Scope,Resource,Action)
VALUES ('SYSTEM','ALL','ALL');
INSERT INTO dbo.RolePermissions
SELECT r.ID, p.ID
FROM dbo.[Role] r
JOIN dbo.Permission p ON p.Scope='SYSTEM'
WHERE r.Name='ADMIN';
/* =========================================================
ADMIN USER
========================================================= */
INSERT INTO dbo.Authentication (
ObjectGUID,
sAMAccountName,
mail,
givenName,
sn,
active,
online,
ObjectSource_ID
)
SELECT
'00000000-0000-0000-0000-000000000001',
'admin',
'admin@local',
'System',
'Admin',
1,
0,
ID
FROM dbo.ObjectSource
WHERE Name='LOCAL';
INSERT INTO dbo.AuthenticationRoles
SELECT
'00000000-0000-0000-0000-000000000001',
ID
FROM dbo.[Role]
WHERE Name='ADMIN';

View File

@@ -83,16 +83,6 @@
Plugin: 'dropdown', Plugin: 'dropdown',
Level: 'dropdown', Level: 'dropdown',
User: 'dropdown' User: 'dropdown'
},
checkboxFilter:{
column:'Level_ID',
rules:[
{ label:'Erfolgreich', test:v => parseInt(v) === 0 },
{ label:'Information', test:v => parseInt(v) === 1 },
{ label:'Warnung', test:v => parseInt(v) === 2 },
{ label:'Fehler', test:v => parseInt(v) === 4 },
{ label:'Absturz', test:v => parseInt(v) === 8 },
]
} }
}, },
customRender: (row, tr) => { customRender: (row, tr) => {

View File

@@ -118,7 +118,21 @@ const server = https.createServer(httpsOptions, app);
databaseModel.set('authentication', require(`@models/authenticationModel`)(service.get('sqlManager').getInstance('main'))); databaseModel.set('authentication', require(`@models/authenticationModel`)(service.get('sqlManager').getInstance('main')));
service.set('fileSystemManager', new FileSystemManager()); service.set('fileSystemManager', new FileSystemManager());
service.set('authenticationManager', new AuthenticationManager(databaseModel.get('authentication'), app.locals.configuration.integration.token.secret));
databaseModel.set('authenticationGroupsModel', require(`@models/authenticationGroupsModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('authenticationRolesModel', require(`@models/authenticationRolesModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('groupClosureModel', require(`@models/groupClosureModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('groupModel', require(`@models/groupModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('groupRolesModel', require(`@models/groupRolesModel`)(service.get('sqlManager').getInstance('main')));
databaseModel.set('objectSourceModel', require(`@models/objectSourceModel`)(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('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('activeDirectoryManager', new ActiveDirectory(app.locals.configuration.integration.activedirectory)) service.set('activeDirectoryManager', new ActiveDirectory(app.locals.configuration.integration.activedirectory))
// everytime last created service! // everytime last created service!
@@ -164,7 +178,6 @@ const server = https.createServer(httpsOptions, app);
app.set('trust proxy', true) app.set('trust proxy', true)
//#endregion //#endregion
//#region Error exception handling //#region Error exception handling
app.on('uncaughtException', (err) => service.get('eventManager').write(null, 8, null, err )); app.on('uncaughtException', (err) => service.get('eventManager').write(null, 8, null, err ));
process.on('uncaughtException', (err) => service.get('eventManager').write(null, 8, null, err )); process.on('uncaughtException', (err) => service.get('eventManager').write(null, 8, null, err ));
@@ -188,11 +201,6 @@ const server = https.createServer(httpsOptions, app);
// Loading plugins // Loading plugins
const plugins = await service.get('pluginManager').loadAll() const plugins = await service.get('pluginManager').loadAll()
// const pluginsLoaded = {
// levelId: plugins.some(plugin => plugin.levelId > 0) ? 2 : 0,
// message: plugins.map(plugin => `${plugin.pluginName} v${plugin.metadata.version} ${plugin.message}`).join('<br>')
// }
// service.get('eventManager').write(null, pluginsLoaded.levelId, null, pluginsLoaded.message);
plugins.forEach(plugin => { plugins.forEach(plugin => {
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}`);

View File

@@ -0,0 +1,19 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const AuthenticationGroups = sequelize.define('AuthenticationGroups', {
Authentication_ObjectGUID: {
type: DataTypes.UUID,
primaryKey: true
},
Group_ObjectGUID: {
type: DataTypes.UUID,
primaryKey: true
}
}, {
tableName: 'AuthenticationGroups',
timestamps: false
});
return AuthenticationGroups;
};

View File

@@ -44,10 +44,6 @@ module.exports = (sequelize) => {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
}, },
authenticationType_ID: {
type: DataTypes.INTEGER,
allowNull: true,
},
telephoneNumber: { telephoneNumber: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,

View File

@@ -0,0 +1,19 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const AuthenticationRoles = sequelize.define('AuthenticationRoles', {
Authentication_ObjectGUID: {
type: DataTypes.UUID,
primaryKey: true
},
Role_ID: {
type: DataTypes.INTEGER,
primaryKey: true
}
}, {
tableName: 'AuthenticationRoles',
timestamps: false
});
return AuthenticationRoles;
};

View File

@@ -10,7 +10,6 @@ module.exports = (sequelize) => {
Date: { type: DataTypes.DATE }, Date: { type: DataTypes.DATE },
department: { type: DataTypes.STRING }, department: { type: DataTypes.STRING },
ClearTextUser: { type: DataTypes.STRING }, ClearTextUser: { type: DataTypes.STRING },
ObjectTypDisplayName: { type: DataTypes.STRING },
Level_ID: { type: DataTypes.INTEGER }, Level_ID: { type: DataTypes.INTEGER },
LevelName: { type: DataTypes.STRING }, LevelName: { type: DataTypes.STRING },
LevelPriority: { type: DataTypes.INTEGER }, LevelPriority: { type: DataTypes.INTEGER },
@@ -22,8 +21,8 @@ module.exports = (sequelize) => {
Phone: { type: DataTypes.STRING }, Phone: { type: DataTypes.STRING },
Office: { type: DataTypes.STRING }, Office: { type: DataTypes.STRING },
Adress: { type: DataTypes.STRING }, Adress: { type: DataTypes.STRING },
authenticationType_ID: { type: DataTypes.INTEGER }, ObjectSource_ID: { type: DataTypes.INTEGER },
TypeName: { type: DataTypes.STRING } ObjectSourceName: { type: DataTypes.STRING },
}, { }, {
tableName: 'vEventLog', // dein SQL-View tableName: 'vEventLog', // dein SQL-View
timestamps: false, timestamps: false,

View File

@@ -0,0 +1,20 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const GroupClosure = sequelize.define('GroupClosure', {
ParentGroup_ObjectGUID: {
type: DataTypes.UUID,
primaryKey: true
},
ChildGroup_ObjectGUID: {
type: DataTypes.UUID,
primaryKey: true
},
Depth: DataTypes.INTEGER
}, {
tableName: 'GroupClosure',
timestamps: false
});
return GroupClosure;
};

18
src/models/groupModel.js Normal file
View File

@@ -0,0 +1,18 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const Group = sequelize.define('Group', {
ObjectGUID: {
type: DataTypes.UUID,
primaryKey: true
},
Name: DataTypes.STRING(255),
ObjectSource_ID: DataTypes.INTEGER,
distinguishedName: DataTypes.TEXT
}, {
tableName: 'Group',
timestamps: false
});
return Group;
};

View File

@@ -0,0 +1,19 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const GroupRoles = sequelize.define('GroupRoles', {
Group_ObjectGUID: {
type: DataTypes.UUID,
primaryKey: true
},
Role_ID: {
type: DataTypes.INTEGER,
primaryKey: true
}
}, {
tableName: 'GroupRoles',
timestamps: false
});
return GroupRoles;
};

View File

@@ -11,7 +11,10 @@
"view": "styleconfig", "view": "styleconfig",
"icon": "brush.png", "icon": "brush.png",
"permissions": [ "permissions": [
"Administration" {
"scope": "SYSTEM",
"action": "Administration"
}
] ]
}, },
{ {
@@ -23,7 +26,10 @@
"height": "900px" "height": "900px"
}, },
"permissions": [ "permissions": [
"Administration" {
"scope": "SYSTEM",
"action": "Administration"
}
] ]
} }
] ]
@@ -45,7 +51,10 @@
}, },
"icon": "eventlog.ico", "icon": "eventlog.ico",
"permissions": [ "permissions": [
"Administration" {
"scope": "SYSTEM",
"action": "Administration"
}
] ]
} }
] ]
@@ -67,7 +76,10 @@
}, },
"icon": "plugins.png", "icon": "plugins.png",
"permissions": [ "permissions": [
"Administration" {
"scope": "SYSTEM",
"action": "Administration"
}
] ]
} }
] ]
@@ -89,7 +101,10 @@
}, },
"icon": "serverinfo.png", "icon": "serverinfo.png",
"permissions": [ "permissions": [
"Administration" {
"scope": "SYSTEM",
"action": "Administration"
}
] ]
} }
] ]
@@ -111,7 +126,10 @@
}, },
"icon": "app.png", "icon": "app.png",
"permissions": [ "permissions": [
"*" {
"scope": "SYSTEM",
"action": "Default_Access"
}
] ]
} }
] ]
@@ -129,7 +147,10 @@
"view": "help", "view": "help",
"icon": "help.png", "icon": "help.png",
"permissions": [ "permissions": [
"*" {
"scope": "SYSTEM",
"action": "Default_Access"
}
] ]
} }
] ]

View File

@@ -29,6 +29,11 @@ module.exports = (sequelize) => {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: false, allowNull: false,
defaultValue: DataTypes.NOW, defaultValue: DataTypes.NOW,
},
ExpiresAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW,
} }
}, { }, {
tableName: 'NotifyTrayObjects', tableName: 'NotifyTrayObjects',

View File

@@ -0,0 +1,21 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const ObjectSource = sequelize.define('ObjectSource', {
ID: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
Name: {
type: DataTypes.STRING(100),
allowNull: false,
unique: true
}
}, {
tableName: 'ObjectSource',
timestamps: false
});
return ObjectSource;
};

View File

@@ -0,0 +1,20 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const Permission = sequelize.define('Permission', {
ID: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
Scope: DataTypes.STRING(100),
Resource: DataTypes.STRING(100),
Action: DataTypes.STRING(100)
}, {
tableName: 'Permission',
timestamps: false
});
return Permission;
};

19
src/models/roleModel.js Normal file
View File

@@ -0,0 +1,19 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const Role = sequelize.define('Role', {
ID: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
Name: DataTypes.STRING(255),
Description: DataTypes.TEXT,
RoleType: DataTypes.STRING(50)
}, {
tableName: 'Role',
timestamps: false
});
return Role;
};

View File

@@ -0,0 +1,19 @@
const { DataTypes } = require('sequelize');
module.exports = (sequelize) => {
const RolePermissions = sequelize.define('RolePermissions', {
Role_ID: {
type: DataTypes.INTEGER,
primaryKey: true
},
Permission_ID: {
type: DataTypes.INTEGER,
primaryKey: true
}
}, {
tableName: 'RolePermissions',
timestamps: false
});
return RolePermissions;
};

View File

@@ -12,11 +12,8 @@ const { doesNotReject } = require('assert');
module.exports = { module.exports = {
route: function(app, service) { route: function(app, service) {
app.get('/', service.get('authenticationManager').authenticate(), async (req, res) => { app.get('/', service.get('authenticationManager').authenticate(), async (req, res) => {
const startMenuItems = await global.startMenuItems( console.log(req.cookies.ObjectGUID)
app, const startMenuItems = await global.startMenuItems(app, req.cookies.ObjectGUID, false);
req.cookies.sAMAccountName,
service
);
res.render('desktop', { layout: 'default', startMenuItems: startMenuItems }); res.render('desktop', { layout: 'default', startMenuItems: startMenuItems });
}); });
@@ -54,7 +51,7 @@ module.exports = {
res.status(204).send(); res.status(204).send();
}) })
app.post('/api/Plugins/loadScripts', service.get('authenticationManager').authenticate(), async(req, res) => { app.post('/api/Plugins/loadScripts', async(req, res) => {
const scripts = service.get('pluginManager').getStatus().map(plugin => { const scripts = service.get('pluginManager').getStatus().map(plugin => {
const exists = service.get('fileSystemManager').exists(path.join(plugin.pluginPath, 'public', 'javascript', 'main.js')) const exists = service.get('fileSystemManager').exists(path.join(plugin.pluginPath, 'public', 'javascript', 'main.js'))

View File

@@ -1,31 +1,27 @@
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs');
const { fn, col, where } = require('sequelize');
/**
* Authentication class for login method, token validation and password setting
*/
class AuthenticationManager { class AuthenticationManager {
constructor(model, secretKey) { constructor(model, secretKey, databaseModel) {
this.Authentication = model; this.Authentication = model;
this.SECRET_KEY = secretKey; this.SECRET_KEY = secretKey;
this.databaseModel = databaseModel;
} }
/** // =========================================================
* Helper: Case-insensitive User Lookup // USER
*/ // =========================================================
async findUser(sAMAccountName) { async findUser(sAMAccountName) {
return await this.Authentication.findOne({ return this.Authentication.findOne({
where: where( where: { sAMAccountName }
fn('LOWER', col('sAMAccountName')),
sAMAccountName.toLowerCase()
)
}); });
} }
/** // =========================================================
* Set or reset password of user // PASSWORD
*/ // =========================================================
async setPassword(sAMAccountName, password) { async setPassword(sAMAccountName, password) {
const user = await this.findUser(sAMAccountName); const user = await this.findUser(sAMAccountName);
@@ -33,16 +29,88 @@ class AuthenticationManager {
return { token: null, levelId: 2, message: 'Unbekannter User' }; return { token: null, levelId: 2, message: 'Unbekannter User' };
} }
const hashedPassword = await bcrypt.hash(password, 10); user.password = await bcrypt.hash(password, 10);
user.password = hashedPassword;
await user.save(); await user.save();
return { token: null, levelId: 0, message: 'Passwort gesetzt' }; return { token: null, levelId: 0, message: 'Passwort gesetzt' };
} }
/** // =========================================================
* Login // RBAC RESOLVER (LIVE - WICHTIG!)
*/ // =========================================================
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) {
const user = await this.findUser(sAMAccountName); const user = await this.findUser(sAMAccountName);
@@ -55,20 +123,20 @@ class AuthenticationManager {
return { token: null, levelId: 1, message: 'Benutzer nicht registriert' }; return { token: null, levelId: 1, message: 'Benutzer nicht registriert' };
} }
const passwordMatch = await bcrypt.compare(password, user.password); const ok = await bcrypt.compare(password, user.password);
if (!passwordMatch) { if (!ok) {
return { token: null, levelId: 2, message: 'Falsches Passwort' }; return { token: null, levelId: 2, message: 'Falsches Passwort' };
} }
const payload = { const token = jwt.sign(
sAMAccountName: user.sAMAccountName, {
mail: user.mail, ObjectGUID: user.ObjectGUID,
givenName: user.givenName, sAMAccountName: user.sAMAccountName
sn: user.sn },
}; this.SECRET_KEY,
{ expiresIn: '10s' }
const token = jwt.sign(payload, this.SECRET_KEY, { expiresIn: '100y' }); );
user.refreshtoken = token; user.refreshtoken = token;
user.online = true; user.online = true;
@@ -81,9 +149,10 @@ class AuthenticationManager {
}; };
} }
/** // =========================================================
* Logout // LOGOUT
*/ // =========================================================
async logout(sAMAccountName) { async logout(sAMAccountName) {
const user = await this.findUser(sAMAccountName); const user = await this.findUser(sAMAccountName);
@@ -98,14 +167,15 @@ class AuthenticationManager {
return { token: null, levelId: 0, message: 'Erfolgreich abgemeldet' }; return { token: null, levelId: 0, message: 'Erfolgreich abgemeldet' };
} }
/** // =========================================================
* Token prüfen // VERIFY TOKEN (unchanged)
*/ // =========================================================
async verifyUserToken(sAMAccountName) { async verifyUserToken(sAMAccountName) {
const user = await this.findUser(sAMAccountName); const user = await this.findUser(sAMAccountName);
if (!user || !user.refreshtoken) { if (!user || !user.refreshtoken) {
return { valid: false, levelId: 1, message: 'Kein gültiger Token' }; return { valid: false, levelId: 1 };
} }
try { try {
@@ -113,42 +183,45 @@ class AuthenticationManager {
return { return {
valid: true, valid: true,
payload,
user, user,
levelId: 0, payload
message: 'User verifiziert'
}; };
} catch { } catch {
return { return { valid: false, levelId: 4 };
valid: false,
levelId: 4,
message: 'Ungültiger Token'
};
} }
} }
/** // =========================================================
* Middleware // 🔥 AUTH MIDDLEWARE (HIER PASSIERT DIE MAGIE)
*/ // =========================================================
authenticate() { authenticate() {
return async (req, res, next) => { return async (req, res, next) => {
try { try {
const sAMAccountName = req.cookies?.sAMAccountName; const sAMAccountName = req.cookies?.sAMAccountName;
const objectGUID = req.cookies?.ObjectGUID;
if (!sAMAccountName || !objectGUID) { if (!sAMAccountName) {
return res.redirect('/login'); return res.redirect('/login');
} }
const user = await this.findUser(sAMAccountName); const user = await this.findUser(sAMAccountName);
if (!user || !user.refreshtoken || user.active === false) { if (!user || !user.refreshtoken || !user.active) {
return res.redirect('/login'); return res.redirect('/login');
} }
jwt.verify(user.refreshtoken, this.SECRET_KEY); // jwt.verify(user.refreshtoken, this.SECRET_KEY);
this.verifyUserToken(sAMAccountName)
// 🔥 LIVE RBAC RESOLUTION (bei JEDEM REQUEST)
const rbac = await this.resolvePermissions(user.ObjectGUID);
req.user = user; req.user = {
...user.toJSON(),
groups: rbac.groups,
roles: rbac.roles,
permissions: rbac.permissions
};
console.log(req.user)
next(); next();
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -159,176 +232,3 @@ class AuthenticationManager {
} }
module.exports = AuthenticationManager; module.exports = AuthenticationManager;
// const jwt = require('jsonwebtoken');
// const bcrypt = require('bcryptjs');
// let { levelId, message } = '';
// /**
// * Authentication class for login method, token validation and password setting
// */
// class AuthenticationManager {
// /**
// *
// * @param {object} model - Use the authentication database model for interact with the database
// * @param {string} secretKey - Defines the server secret for token validation
// */
// constructor(model, secretKey, eventManager) {
// this.eventManager = eventManager;
// // if (!model) throw new Error('Sequelize Model wird benötigt');
// // if (!secretKey) throw new Error('Secret Key wird benötigt');
// this.Authentication = model;
// this.SECRET_KEY = secretKey;
// }
// /**
// * Set or reset password of user
// * @param {string} sAMAccountName - Windows account name
// * @param {string} password - Set the new password
// */
// async setPassword(sAMAccountName, password) {
// const user = await this.Authentication.findOne({ where: { sAMAccountName } });
// if (!user) {
// // this.eventManager.write(null, 2, 0, { aboveLevel: 1 }, `User nicht gefunden`);
// levelId = 2;
// message = `Unbekannter User`
// return {token: null, levelId: levelId };
// // throw new Error(`User ${sAMAccountName} nicht gefunden`);
// }
// // if (user.password) throw new Error('Passwort bereits gesetzt');
// const hashedPassword = await bcrypt.hash(password, 10);
// user.password = hashedPassword;
// await user.save();
// }
// /**
// * Login mit Speicherung des Tokens in der Datenbank
// */
// async login(sAMAccountName, password) {
// const user = await this.Authentication.findOne({ where: { sAMAccountName } });
// if (!user) {
// //this.eventManager.write(null, 2, null, null, `User ${sAMAccountName} nicht geufunden`)
// levelId = 2;
// message = `Unbekannter Benutzer`;
// return { token: null, levelId: levelId, message: message };
// // throw new Error('Unkown user');
// }
// if (!user.password) {
// this.setPassword(sAMAccountName, password);
// // this.eventManager.write(user.ObjectGUID, 2, null, null, 'User registration initialized')
// levelId = 1;
// message = `Benutzer nicht registiert`;
// return { token: null, levelId: levelId, message: message };
// // throw new Error('User not registered');
// }
// const passwordMatch = await bcrypt.compare(password, user.password);
// if (!passwordMatch) {
// // this.eventManager.write(user.ObjectGUID, 2, null, null, 'Password doesn\'t match');
// levelId = 2;
// message = `Falsches Passwort`;
// return { token: null, levelId: levelId, message: message };
// // throw new Error('Wrong password');
// }
// // Token erzeugen
// const payload = {
// sAMAccountName: user.sAMAccountName,
// mail: user.mail,
// givenName: user.givenName,
// sn: user.sn
// };
// const token = jwt.sign(payload, this.SECRET_KEY, { expiresIn: '100y' });
// // Token in DB speichern
// user.refreshtoken = token;
// user.online = true;
// await user.save();
// // this.eventManager.write(user.ObjectGUID, 1, null, null, 'Erfolgreich angemeldet');
// levelId = 0;
// message = `Erfolgreich angemeldet`;
// return { token: token, levelId: levelId, message: message };
// }
// /**
// * Logout löscht Token aus der DB
// */
// async logout(sAMAccountName) {
// const user = await this.Authentication.findOne({ where: { sAMAccountName } });
// if (user) {
// user.refreshtoken = null;
// user.online = false;
// await user.save();
// levelId = 0;
// message = `Erfolgreich abgemeldet`;
// return { token: null, levelId: levelId, message: message };
// }
// }
// /**
// * Token-Prüfung (über DB)
// */
// async verifyUserToken(sAMAccountName) {
// const user = await this.Authentication.findOne({ where: { sAMAccountName } });
// if (!user || !user.refreshtoken) {
// levelId = 1,
// message = `Kein gültiger Token`;
// // throw new Error('Kein gespeicherter Token gefunden');
// }
// try {
// const payload = jwt.verify(user.refreshtoken, this.SECRET_KEY);
// levelId = 0;
// message = `User verifiziert`;
// return { valid: true, payload, user, levelId: levelId, message: message }
// } catch {
// levelId = 4;
// message = `Ungültiger Token`;
// return { valid: false, payload, user, levelId: levelId, message: message }
// }
// }
// /**
// * Express Middleware prüft Token direkt aus DB anhand sAMAccountNamec
// */
// authenticate() {
// return async (req, res, next) => {
// try {
// const sAMAccountName = req.cookies?.sAMAccountName;
// const objectGUID = req.cookies?.ObjectGUID;
// if (!sAMAccountName || !objectGUID) {
// return res.redirect('/login');
// // return res.status(401).json({ message: 'Kein Benutzer-Cookie gefunden' });
// }
// const user = await this.Authentication.findOne({ where: { sAMAccountName } });
// if (!user || !user.refreshtoken) {
// return res.redirect('/login');
// // return res.status(401).json({ message: 'Benutzer oder Token nicht gefunden' });
// }
// if (user.active === false) {
// return res.redirect('/login');
// // return res.status(401).json({ message: 'Benutzer ist nicht aktiv' });
// }
// // Token aus DB prüfen
// const payload = jwt.verify(user.refreshtoken, this.SECRET_KEY);
// req.user = user;
// next();
// } catch (err) {
// console.error(err);
// return res.redirect('/login');
// // res.status(401).json({ message: 'Authentifizierung fehlgeschlagen' });
// }
// };
// }
// }
// module.exports = AuthenticationManager;

View File

@@ -61,17 +61,6 @@ class notifyTrayManager {
ObjectGUID: objectGuid, ObjectGUID: objectGuid,
SeenAt: null SeenAt: null
}, },
// include: [{
// model: this.objects,
// as: 'NotificationObject',
// required: true, // join zwingend
// where: {
// [this.objects.sequelize.Op.Or]: [
// { ExpiresAt: null },
// { ExpiresAt: { [this.objects.sequelize.Op.gt]: new Date() } }
// ]
// }
// }],
order: [[ 'SeenAt', 'ASC']] order: [[ 'SeenAt', 'ASC']]
}); });
} }

View File

@@ -91,7 +91,7 @@ class PluginManager {
await this.Plugin.upsert({ await this.Plugin.upsert({
Name: name, Name: name,
Active: withActivate ? true : (await this.Plugin.findOne({ where: { Name: meta.live.name }})).Active, Active: !withActivate ? meta.live.active : false,
Version: meta.live.version Version: meta.live.version
}); });
@@ -287,26 +287,44 @@ class PluginManager {
} }
} }
__pluginTemplate(name, options = { }) { __pluginTemplate(name, options = {}) {
return { return {
name, name,
description: options.description || 'Beschreibung hier einfügen', description: options.description || 'Beschreibung hier einfügen',
version: options.version || `1.${new Date().getFullYear().toString().slice(-2)}.${new Date().getMonth() + 1}.${new Date().getDate()}`,
version: options.version ||
`1.${new Date().getFullYear().toString().slice(-2)}.${new Date().getMonth() + 1}.${new Date().getDate()}`,
// =========================
// MENU (RBAC READY)
// =========================
menu: { menu: {
label: name, label: name,
items:[ items: [
{ {
label: name, label: name,
view: "index", view: "index",
defaultSize: { width: '800px', height: '600px' }, defaultSize: { width: '800px', height: '600px' },
icon: "../../images/app.png", icon: "../../images/app.png",
permissions: ["*"]
// =========================
// RBAC PERMISSIONS
// =========================
permissions: [
{
scope: name, // Plugin Scope (default = plugin name)
action: "Default_Access",
resource: "MenuItem"
}
]
} }
] ]
}, },
config: options.config || {}, config: options.config || {},
active: true active: true
} };
} }
__setPermissions(pluginPath) { __setPermissions(pluginPath) {

160
utils.js
View File

@@ -23,14 +23,22 @@ global.json = {
startMenuItems: new HotReload(path.join(global.path.source, 'models', 'integratedStartMenuItems.json')) startMenuItems: new HotReload(path.join(global.path.source, 'models', 'integratedStartMenuItems.json'))
} }
module.exports = startMenuItems = async function (app, objectGuid, debug = false) {
module.exports = startMenuItems = async function (app, sAMAccountName) {
function safeClone(obj) { function safeClone(obj) {
return JSON.parse(JSON.stringify(obj)); return JSON.parse(JSON.stringify(obj));
} }
const integratedStartmenuItems = safeClone(service.get('fileSystemManager').loadJSON(global.json.startMenuItems.filePath)); const log = (...args) => {
if (debug) console.log('[RBAC DEBUG]', ...args);
};
// =========================
// Load menu sources
// =========================
const integratedStartmenuItems =
service.get('fileSystemManager')
.loadJSON(global.json.startMenuItems.filePath) || [];
const plugins = service const plugins = service
.get('pluginManager') .get('pluginManager')
@@ -40,59 +48,123 @@ module.exports = startMenuItems = async function (app, sAMAccountName) {
section: 'Plugin' section: 'Plugin'
})); }));
let getAllPlugins = [...plugins, ...integratedStartmenuItems]; let allPlugins = [...plugins, ...integratedStartmenuItems];
for (const plugin of getAllPlugins) { // =========================
// Load user permissions
// =========================
const authManager = service.get('authenticationManager');
plugin.menu.items = await Promise.all( const userPermissions =
(plugin.menu.items || []).map(async item => { (await authManager.resolvePermissions(objectGuid))
?.permissions || [];
const authorized = const normalizedPermissions = userPermissions.map(p => ({
item.label === 'hr' || scope: p.scope,
item.permissions.includes('Administration') action: p.action,
? global.json.configuration.live.administration.some( resource: p.resource || null
name => name.toLowerCase() === sAMAccountName.toLowerCase() }));
)
: item.permissions.includes('*') || log('USER OBJECTGUID:', objectGuid);
( log('PERMISSIONS:', normalizedPermissions);
await Promise.all(
item.permissions.map(async permission => // =========================
(await service.get('activeDirectoryManager').getGroup(permission)) && // SUPER ADMIN CHECK
(await service.get('activeDirectoryManager').isUserMemberOfRecursive( // =========================
sAMAccountName, const isSuperAdmin = normalizedPermissions.some(p =>
permission p.scope === 'SYSTEM' &&
)) p.resource === 'ALL' &&
) p.action === 'ALL'
) );
).some(Boolean);
log('SUPER ADMIN:', isSuperAdmin);
// =========================
// BUILD MENU
// =========================
for (const plugin of allPlugins) {
plugin.menu.items = (plugin.menu.items || []).map(item => {
const resource = item.label;
const requiredPermissions = item.permissions || [];
const debugTrace = [];
const authorized = isSuperAdmin
? (debugTrace.push('SUPERADMIN OVERRIDE'), true)
: normalizedPermissions.some(userPerm => {
return requiredPermissions.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 === resource;
const result = scopeMatch && actionMatch && resourceMatch;
if (debug) {
debugTrace.push({
userPerm,
required,
scopeMatch,
actionMatch,
resourceMatch,
result
});
}
return result;
});
});
if (debug) {
log(`\n--- MENU ITEM: ${item.label} ---`);
log('AUTHORIZED:', authorized);
log('TRACE:', debugTrace);
}
return { return {
...safeClone(item), ...safeClone(item),
authorized authorized
}; };
}) });
);
plugin.onlyAdministration = plugin.onlyAdministration =
plugin.menu.items.every(item => !item.authorized) && plugin.menu.items.every(i => !i.authorized);
!global.json.configuration.live.administration.includes(sAMAccountName);
if (debug) {
log(`PLUGIN: ${plugin.name}`);
log('VISIBLE:', !plugin.onlyAdministration);
}
} }
getAllPlugins = getAllPlugins // =========================
.filter(plugin => !plugin.onlyAdministration) // FILTER FINAL MENU
.filter(plugin => plugin.active); // =========================
allPlugins = allPlugins
app.locals.startMenuItems = getAllPlugins; .filter(p => !p.onlyAdministration)
.filter(p => p.active);
return [...getAllPlugins]; app.locals.startMenuItems = allPlugins;
return allPlugins;
}; };
// module.exports = startMenuItems = async function(app, sAMAccountName) {
// module.exports = startMenuItems = async function (app, sAMAccountName) {
// function safeClone(obj) { // function safeClone(obj) {
// return JSON.parse(JSON.stringify(obj)); // return JSON.parse(JSON.stringify(obj));
// } // }
// delete integratedStartmenuItems;
// integratedStartmenuItems = safeClone(json.startMenuItems.live); // const integratedStartmenuItems = safeClone(service.get('fileSystemManager').loadJSON(global.json.startMenuItems.filePath));
// const plugins = service // const plugins = service
// .get('pluginManager') // .get('pluginManager')
@@ -112,13 +184,18 @@ module.exports = startMenuItems = async function (app, sAMAccountName) {
// const authorized = // const authorized =
// item.label === 'hr' || // item.label === 'hr' ||
// item.permissions.includes('Administration') // item.permissions.includes('Administration')
// ? global.json.configuration.live.administration.some(name => name.toLowerCase() === sAMAccountName.toLowerCase()) // ? global.json.configuration.live.administration.some(
// name => name.toLowerCase() === sAMAccountName.toLowerCase()
// )
// : item.permissions.includes('*') || // : item.permissions.includes('*') ||
// ( // (
// await Promise.all( // await Promise.all(
// item.permissions.map(async permission => // item.permissions.map(async permission =>
// (await service.get('activeDirectoryManager').getGroup(permission)) && // (await service.get('activeDirectoryManager').getGroup(permission)) &&
// (await service.get('activeDirectoryManager').isUserMemberOfRecursive(sAMAccountName, permission)) // (await service.get('activeDirectoryManager').isUserMemberOfRecursive(
// sAMAccountName,
// permission
// ))
// ) // )
// ) // )
// ).some(Boolean); // ).some(Boolean);
@@ -145,6 +222,7 @@ module.exports = startMenuItems = async function (app, sAMAccountName) {
// }; // };
/** /**
* Convert date into custom dateformat * Convert date into custom dateformat
* @param {any} date - Valid date as datetype or string * @param {any} date - Valid date as datetype or string