280 lines
8.0 KiB
JavaScript
280 lines
8.0 KiB
JavaScript
const ldap = require('ldapjs');
|
|
const { db } = require('./database');
|
|
const bcrypt = require('bcryptjs');
|
|
|
|
/**
|
|
* LDAP-Service für Benutzer-Synchronisation
|
|
*/
|
|
class LDAPService {
|
|
/**
|
|
* LDAP-Konfiguration aus der Datenbank abrufen
|
|
*/
|
|
static getConfig(callback) {
|
|
db.get('SELECT * FROM ldap_config WHERE id = 1', (err, config) => {
|
|
if (err) {
|
|
return callback(err, null);
|
|
}
|
|
callback(null, config);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* LDAP-Verbindung herstellen
|
|
*/
|
|
static connect(config, callback) {
|
|
if (!config || !config.enabled || !config.url) {
|
|
return callback(new Error('LDAP ist nicht konfiguriert oder deaktiviert'));
|
|
}
|
|
|
|
const client = ldap.createClient({
|
|
url: config.url,
|
|
timeout: 10000,
|
|
connectTimeout: 10000
|
|
});
|
|
|
|
// Fehlerbehandlung
|
|
client.on('error', (err) => {
|
|
callback(err, null);
|
|
});
|
|
|
|
// Bind mit Credentials
|
|
const bindDN = config.bind_dn || '';
|
|
const bindPassword = config.bind_password || '';
|
|
|
|
// Hinweis: Passwort wird im Klartext gespeichert
|
|
// In einer produktiven Umgebung sollte man eine Verschlüsselung mit einem Master-Key verwenden
|
|
|
|
client.bind(bindDN, bindPassword, (err) => {
|
|
if (err) {
|
|
client.unbind();
|
|
return callback(err, null);
|
|
}
|
|
callback(null, client);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Benutzer aus LDAP abrufen
|
|
*/
|
|
static searchUsers(client, config, callback) {
|
|
const baseDN = config.base_dn || '';
|
|
const searchFilter = config.user_search_filter || '(objectClass=person)';
|
|
const searchOptions = {
|
|
filter: searchFilter,
|
|
scope: 'sub',
|
|
attributes: [
|
|
config.username_attribute || 'cn',
|
|
config.firstname_attribute || 'givenName',
|
|
config.lastname_attribute || 'sn'
|
|
]
|
|
};
|
|
|
|
const users = [];
|
|
|
|
client.search(baseDN, searchOptions, (err, res) => {
|
|
if (err) {
|
|
return callback(err, null);
|
|
}
|
|
|
|
res.on('searchEntry', (entry) => {
|
|
const user = {
|
|
username: this.getAttributeValue(entry, config.username_attribute || 'cn'),
|
|
firstname: this.getAttributeValue(entry, config.firstname_attribute || 'givenName'),
|
|
lastname: this.getAttributeValue(entry, config.lastname_attribute || 'sn')
|
|
};
|
|
|
|
// Nur Benutzer mit allen erforderlichen Feldern hinzufügen
|
|
if (user.username && user.firstname && user.lastname) {
|
|
users.push(user);
|
|
}
|
|
});
|
|
|
|
res.on('error', (err) => {
|
|
callback(err, null);
|
|
});
|
|
|
|
res.on('end', (result) => {
|
|
if (result && result.status !== 0) {
|
|
return callback(new Error(`LDAP-Suche fehlgeschlagen: ${result.status}`), null);
|
|
}
|
|
callback(null, users);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Wert eines LDAP-Attributs extrahieren
|
|
*/
|
|
static getAttributeValue(entry, attributeName) {
|
|
const attr = entry.attributes.find(a => a.type === attributeName);
|
|
if (!attr) {
|
|
return null;
|
|
}
|
|
return Array.isArray(attr.values) ? attr.values[0] : attr.values;
|
|
}
|
|
|
|
/**
|
|
* Benutzer in SQLite synchronisieren
|
|
*/
|
|
static syncUsers(ldapUsers, callback) {
|
|
let syncedCount = 0;
|
|
let errorCount = 0;
|
|
const errors = [];
|
|
|
|
if (!ldapUsers || ldapUsers.length === 0) {
|
|
return callback(null, { synced: 0, errors: [] });
|
|
}
|
|
|
|
// Verarbeite jeden Benutzer
|
|
const processUser = (index) => {
|
|
if (index >= ldapUsers.length) {
|
|
return callback(null, { synced: syncedCount, errors: errors });
|
|
}
|
|
|
|
const ldapUser = ldapUsers[index];
|
|
const username = ldapUser.username.trim();
|
|
const firstname = ldapUser.firstname.trim();
|
|
const lastname = ldapUser.lastname.trim();
|
|
|
|
// Prüfe ob Benutzer bereits existiert
|
|
db.get('SELECT id, role FROM users WHERE username = ?', [username], (err, existingUser) => {
|
|
if (err) {
|
|
errors.push(`Fehler beim Prüfen von ${username}: ${err.message}`);
|
|
errorCount++;
|
|
return processUser(index + 1);
|
|
}
|
|
|
|
if (existingUser) {
|
|
// Benutzer existiert - aktualisiere nur Name, behalte Rolle
|
|
db.run(
|
|
'UPDATE users SET firstname = ?, lastname = ? WHERE username = ?',
|
|
[firstname, lastname, username],
|
|
(err) => {
|
|
if (err) {
|
|
errors.push(`Fehler beim Aktualisieren von ${username}: ${err.message}`);
|
|
errorCount++;
|
|
} else {
|
|
syncedCount++;
|
|
}
|
|
processUser(index + 1);
|
|
}
|
|
);
|
|
} else {
|
|
// Neuer Benutzer - erstelle mit Standard-Rolle
|
|
// Generiere ein zufälliges Passwort (Benutzer muss es beim ersten Login ändern)
|
|
const defaultPassword = bcrypt.hashSync('changeme123', 10);
|
|
|
|
db.run(
|
|
'INSERT INTO users (username, password, firstname, lastname, role) VALUES (?, ?, ?, ?, ?)',
|
|
[username, defaultPassword, firstname, lastname, 'mitarbeiter'],
|
|
(err) => {
|
|
if (err) {
|
|
errors.push(`Fehler beim Erstellen von ${username}: ${err.message}`);
|
|
errorCount++;
|
|
} else {
|
|
syncedCount++;
|
|
}
|
|
processUser(index + 1);
|
|
}
|
|
);
|
|
}
|
|
});
|
|
};
|
|
|
|
processUser(0);
|
|
}
|
|
|
|
/**
|
|
* Sync-Log-Eintrag erstellen
|
|
*/
|
|
static createSyncLog(syncType, status, usersSynced, errorMessage, callback) {
|
|
const startedAt = new Date().toISOString();
|
|
const completedAt = new Date().toISOString();
|
|
|
|
db.run(
|
|
`INSERT INTO ldap_sync_log (sync_type, status, users_synced, error_message, sync_started_at, sync_completed_at)
|
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
[syncType, status, usersSynced, errorMessage || null, startedAt, completedAt],
|
|
(err) => {
|
|
if (callback) {
|
|
callback(err);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Letzte Synchronisation aktualisieren
|
|
*/
|
|
static updateLastSync(callback) {
|
|
db.run(
|
|
'UPDATE ldap_config SET last_sync = CURRENT_TIMESTAMP WHERE id = 1',
|
|
(err) => {
|
|
if (callback) {
|
|
callback(err);
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Vollständige Synchronisation durchführen
|
|
*/
|
|
static performSync(syncType, callback) {
|
|
const startedAt = new Date();
|
|
|
|
// Konfiguration abrufen
|
|
this.getConfig((err, config) => {
|
|
if (err) {
|
|
this.createSyncLog(syncType, 'error', 0, `Fehler beim Abrufen der Konfiguration: ${err.message}`, () => {});
|
|
return callback(err);
|
|
}
|
|
|
|
if (!config || !config.enabled) {
|
|
const errorMsg = 'LDAP-Synchronisation ist nicht aktiviert';
|
|
this.createSyncLog(syncType, 'error', 0, errorMsg, () => {});
|
|
return callback(new Error(errorMsg));
|
|
}
|
|
|
|
// LDAP-Verbindung herstellen
|
|
this.connect(config, (err, client) => {
|
|
if (err) {
|
|
this.createSyncLog(syncType, 'error', 0, `LDAP-Verbindungsfehler: ${err.message}`, () => {});
|
|
return callback(err);
|
|
}
|
|
|
|
// Benutzer aus LDAP abrufen
|
|
this.searchUsers(client, config, (err, ldapUsers) => {
|
|
// Verbindung schließen
|
|
client.unbind();
|
|
|
|
if (err) {
|
|
this.createSyncLog(syncType, 'error', 0, `LDAP-Suchfehler: ${err.message}`, () => {});
|
|
return callback(err);
|
|
}
|
|
|
|
// Benutzer synchronisieren
|
|
this.syncUsers(ldapUsers, (err, result) => {
|
|
if (err) {
|
|
this.createSyncLog(syncType, 'error', result.synced, `Sync-Fehler: ${err.message}`, () => {});
|
|
return callback(err);
|
|
}
|
|
|
|
// Letzte Synchronisation aktualisieren
|
|
this.updateLastSync(() => {
|
|
const status = result.errors.length > 0 ? 'error' : 'success';
|
|
const errorMsg = result.errors.length > 0 ? result.errors.join('; ') : null;
|
|
|
|
this.createSyncLog(syncType, status, result.synced, errorMsg, () => {
|
|
callback(null, result);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = LDAPService;
|