Diverse anpassungen
This commit is contained in:
@@ -125,20 +125,35 @@ class LDAPService {
|
||||
* LDAP-Filter unterstützen UTF-8 direkt nach RFC 4515.
|
||||
* Nur die speziellen LDAP-Filter-Zeichen werden escaped.
|
||||
*/
|
||||
static escapeLDAPFilter(value) {
|
||||
static escapeLDAPFilter(value, debugLabel = '') {
|
||||
if (!value) return '';
|
||||
|
||||
// Stelle sicher, dass der Wert als String behandelt wird
|
||||
const str = String(value);
|
||||
|
||||
// Debug-Logging
|
||||
if (debugLabel) {
|
||||
console.log(`[escapeLDAPFilter ${debugLabel}] Input:`, str);
|
||||
const utf8Bytes = Buffer.from(str, 'utf8');
|
||||
console.log(`[escapeLDAPFilter ${debugLabel}] UTF-8 Bytes:`, Array.from(utf8Bytes));
|
||||
}
|
||||
|
||||
// Escape nur die speziellen LDAP-Filter-Zeichen
|
||||
// UTF-8-Zeichen wie ß, ä, ö, ü werden direkt verwendet
|
||||
return str
|
||||
const escaped = str
|
||||
.replace(/\\/g, '\\5c') // Backslash
|
||||
.replace(/\*/g, '\\2a') // Stern
|
||||
.replace(/\(/g, '\\28') // Öffnende Klammer
|
||||
.replace(/\)/g, '\\29') // Schließende Klammer
|
||||
.replace(/\0/g, '\\00'); // Null-Byte
|
||||
|
||||
if (debugLabel) {
|
||||
console.log(`[escapeLDAPFilter ${debugLabel}] Output:`, escaped);
|
||||
const escapedBytes = Buffer.from(escaped, 'utf8');
|
||||
console.log(`[escapeLDAPFilter ${debugLabel}] Escaped UTF-8 Bytes:`, Array.from(escapedBytes));
|
||||
}
|
||||
|
||||
return escaped;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,6 +287,22 @@ class LDAPService {
|
||||
// Stelle sicher, dass Username als String behandelt wird (UTF-8 wird korrekt unterstützt)
|
||||
const usernameStr = String(username || '').trim();
|
||||
|
||||
console.log('\n========== LDAP AUTHENTICATE - START ==========');
|
||||
console.log('[LDAP] Username input:', usernameStr);
|
||||
const utf8Bytes = Buffer.from(usernameStr, 'utf8');
|
||||
console.log('[LDAP] Username UTF-8 Bytes:', Array.from(utf8Bytes));
|
||||
console.log('[LDAP] Username UTF-8 Bytes (Hex):', Array.from(utf8Bytes).map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
|
||||
console.log('[LDAP] Username length:', usernameStr.length);
|
||||
|
||||
// Character-by-character analysis
|
||||
console.log('[LDAP] Character analysis:');
|
||||
for (let i = 0; i < usernameStr.length; i++) {
|
||||
const char = usernameStr[i];
|
||||
const codePoint = char.codePointAt(0);
|
||||
const charBytes = Buffer.from(char, 'utf8');
|
||||
console.log(` [${i}] "${char}" | U+${codePoint.toString(16).toUpperCase().padStart(4, '0')} (${codePoint}) | Bytes: [${Array.from(charBytes).join(', ')}]`);
|
||||
}
|
||||
|
||||
if (!usernameStr) {
|
||||
return callback(new Error('Benutzername darf nicht leer sein'), false);
|
||||
}
|
||||
@@ -282,24 +313,121 @@ class LDAPService {
|
||||
return callback(new Error('LDAP ist nicht aktiviert'), false);
|
||||
}
|
||||
|
||||
console.log('[LDAP] Config loaded - baseDN:', config.base_dn);
|
||||
console.log('[LDAP] Config - usernameAttr:', config.username_attribute || 'sAMAccountName');
|
||||
|
||||
// LDAP-Verbindung herstellen (mit Service-Account)
|
||||
this.connect(config, (err, client) => {
|
||||
if (err) {
|
||||
console.error('[LDAP] Connection error:', err);
|
||||
return callback(err, false);
|
||||
}
|
||||
|
||||
console.log('[LDAP] Connected successfully');
|
||||
|
||||
const baseDN = config.base_dn || '';
|
||||
const usernameAttr = config.username_attribute || 'sAMAccountName';
|
||||
// OR-Filter mit mehreren Varianten (exakt, lowercase, ß/ss), damit Login trotz unterschiedlicher Schreibweise funktioniert
|
||||
const variants = this.getUsernameSearchVariants(usernameStr);
|
||||
const filterParts = variants.map(v => `(${usernameAttr}=${this.escapeLDAPFilter(v)})`);
|
||||
const searchFilter = filterParts.length === 1 ? filterParts[0] : `(|${filterParts.join('')})`;
|
||||
const searchOptions = {
|
||||
filter: searchFilter,
|
||||
|
||||
// Primär: Exakte Übereinstimmung mit sAMAccountName (wie in AD gespeichert)
|
||||
// Varianten werden als Fallback verwendet, falls exakte Suche fehlschlägt
|
||||
console.log('[LDAP] Using exact sAMAccountName match (as stored in AD)');
|
||||
const exactEscaped = this.escapeLDAPFilter(usernameStr, 'exact');
|
||||
const exactFilter = `(${usernameAttr}=${exactEscaped})`;
|
||||
|
||||
console.log('[LDAP] Exact LDAP Search Filter:', exactFilter);
|
||||
console.log('[LDAP] Exact Filter UTF-8 Bytes:', Array.from(Buffer.from(exactFilter, 'utf8')));
|
||||
|
||||
const exactSearchOptions = {
|
||||
filter: exactFilter,
|
||||
scope: 'sub',
|
||||
attributes: ['dn', usernameAttr]
|
||||
};
|
||||
|
||||
console.log('[LDAP] Starting LDAP search with exact match...');
|
||||
console.log('[LDAP] Search options:', JSON.stringify(exactSearchOptions, null, 2));
|
||||
|
||||
let userDN = null;
|
||||
let canonicalUsername = null;
|
||||
let searchAttempted = false;
|
||||
|
||||
// Zuerst exakte Suche versuchen
|
||||
client.search(baseDN, exactSearchOptions, (err, res) => {
|
||||
if (err) {
|
||||
console.error('[LDAP] Exact search error:', err);
|
||||
// Bei Fehler: Varianten als Fallback versuchen
|
||||
return this.tryVariantSearch(client, baseDN, usernameAttr, usernameStr, password, config, callback);
|
||||
}
|
||||
|
||||
searchAttempted = true;
|
||||
|
||||
res.on('searchEntry', (entry) => {
|
||||
userDN = entry.dn.toString();
|
||||
canonicalUsername = this.getAttributeValue(entry, usernameAttr) || usernameStr;
|
||||
|
||||
console.log('[LDAP] Exact search entry found!');
|
||||
console.log('[LDAP] userDN:', userDN);
|
||||
console.log('[LDAP] canonicalUsername:', canonicalUsername);
|
||||
const canonicalBytes = Buffer.from(canonicalUsername, 'utf8');
|
||||
console.log('[LDAP] canonicalUsername UTF-8 Bytes:', Array.from(canonicalBytes));
|
||||
console.log('[LDAP] canonicalUsername UTF-8 Bytes (Hex):', Array.from(canonicalBytes).map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
|
||||
});
|
||||
|
||||
res.on('error', (err) => {
|
||||
const errorMsg = err.message || String(err);
|
||||
console.error('[LDAP] Exact search error:', errorMsg);
|
||||
console.error('[LDAP] Error details:', err);
|
||||
// Bei Fehler: Varianten als Fallback versuchen
|
||||
if (!userDN) {
|
||||
return this.tryVariantSearch(client, baseDN, usernameAttr, usernameStr, password, config, callback);
|
||||
}
|
||||
client.unbind();
|
||||
callback(new Error(`LDAP-Suchfehler: ${errorMsg}`), false);
|
||||
});
|
||||
|
||||
res.on('end', (result) => {
|
||||
console.log('[LDAP] Exact search ended. Status:', result ? result.status : 'unknown');
|
||||
console.log('[LDAP] userDN found:', userDN ? 'YES' : 'NO');
|
||||
|
||||
if (!userDN) {
|
||||
console.log('[LDAP] Exact match not found, trying variants as fallback...');
|
||||
// Exakte Suche fehlgeschlagen, Varianten als Fallback versuchen
|
||||
return this.tryVariantSearch(client, baseDN, usernameAttr, usernameStr, password, config, callback);
|
||||
}
|
||||
|
||||
// Exakte Suche erfolgreich - mit Bind fortfahren
|
||||
this.performBind(config, userDN, password, canonicalUsername, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Varianten-Suche als Fallback (wenn exakte Suche fehlschlägt)
|
||||
*/
|
||||
static tryVariantSearch(client, baseDN, usernameAttr, usernameStr, password, config, callback) {
|
||||
console.log('[LDAP] Starting variant search as fallback...');
|
||||
|
||||
// OR-Filter mit mehreren Varianten (lowercase, ß/ss), damit Login trotz unterschiedlicher Schreibweise funktioniert
|
||||
const variants = this.getUsernameSearchVariants(usernameStr);
|
||||
console.log('[LDAP] Username variants generated:', variants);
|
||||
console.log('[LDAP] Number of variants:', variants.length);
|
||||
|
||||
const filterParts = variants.map((v, idx) => {
|
||||
const escaped = this.escapeLDAPFilter(v, `variant-${idx}`);
|
||||
return `(${usernameAttr}=${escaped})`;
|
||||
});
|
||||
const searchFilter = filterParts.length === 1 ? filterParts[0] : `(|${filterParts.join('')})`;
|
||||
|
||||
console.log('[LDAP] Variant LDAP Search Filter:', searchFilter);
|
||||
console.log('[LDAP] Variant Filter UTF-8 Bytes:', Array.from(Buffer.from(searchFilter, 'utf8')));
|
||||
|
||||
const searchOptions = {
|
||||
filter: searchFilter,
|
||||
scope: 'sub',
|
||||
attributes: ['dn', usernameAttr]
|
||||
};
|
||||
|
||||
let userDN = null;
|
||||
let canonicalUsername = null;
|
||||
|
||||
@@ -314,44 +442,74 @@ class LDAPService {
|
||||
userDN = entry.dn.toString();
|
||||
// Kanonischen Benutzernamen aus LDAP verwenden (für DB-Lookup nach Sync)
|
||||
canonicalUsername = this.getAttributeValue(entry, usernameAttr) || usernameStr;
|
||||
|
||||
console.log('[LDAP] Search entry found!');
|
||||
console.log('[LDAP] userDN:', userDN);
|
||||
console.log('[LDAP] canonicalUsername:', canonicalUsername);
|
||||
const canonicalBytes = Buffer.from(canonicalUsername, 'utf8');
|
||||
console.log('[LDAP] canonicalUsername UTF-8 Bytes:', Array.from(canonicalBytes));
|
||||
console.log('[LDAP] canonicalUsername UTF-8 Bytes (Hex):', Array.from(canonicalBytes).map(b => '0x' + b.toString(16).padStart(2, '0')).join(' '));
|
||||
});
|
||||
|
||||
res.on('error', (err) => {
|
||||
client.unbind();
|
||||
const errorMsg = err.message || String(err);
|
||||
console.error('[LDAP] Search error:', errorMsg);
|
||||
console.error('[LDAP] Error details:', err);
|
||||
callback(new Error(`LDAP-Suchfehler: ${errorMsg}`), false);
|
||||
});
|
||||
|
||||
res.on('end', (result) => {
|
||||
client.unbind();
|
||||
|
||||
console.log('[LDAP] Search ended. Status:', result ? result.status : 'unknown');
|
||||
console.log('[LDAP] userDN found:', userDN ? 'YES' : 'NO');
|
||||
console.log('[LDAP] canonicalUsername:', canonicalUsername);
|
||||
|
||||
if (!userDN) {
|
||||
console.error('[LDAP] User not found in LDAP (even with variants)!');
|
||||
console.error('[LDAP] Searched for:', usernameStr);
|
||||
console.error('[LDAP] Used filter:', searchFilter);
|
||||
return callback(new Error(`Benutzer "${usernameStr}" nicht gefunden. Hinweis: Prüfen Sie, ob der Benutzername korrekt ist und UTF-8-Zeichen (wie ß, ä, ö, ü) korrekt geschrieben sind.`), false);
|
||||
}
|
||||
|
||||
const authClient = ldap.createClient({
|
||||
url: config.url,
|
||||
timeout: 10000,
|
||||
connectTimeout: 10000
|
||||
});
|
||||
|
||||
authClient.on('error', (err) => {
|
||||
authClient.unbind();
|
||||
callback(err, false);
|
||||
});
|
||||
|
||||
authClient.bind(userDN, password, (err) => {
|
||||
authClient.unbind();
|
||||
if (err) {
|
||||
const errorMsg = err.message || String(err);
|
||||
return callback(new Error(`Ungültiges Passwort oder Authentifizierungsfehler: ${errorMsg}`), false);
|
||||
}
|
||||
// Erfolg: kanonischen Benutzernamen mitgeben, damit die DB-Lookup mit dem Sync-Benutzernamen funktioniert
|
||||
callback(null, true, { username: canonicalUsername });
|
||||
});
|
||||
// Varianten-Suche erfolgreich - mit Bind fortfahren
|
||||
this.performBind(config, userDN, password, canonicalUsername, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* LDAP Bind durchführen (Passwort-Authentifizierung)
|
||||
*/
|
||||
static performBind(config, userDN, password, canonicalUsername, callback) {
|
||||
console.log('[LDAP] Attempting bind with userDN:', userDN);
|
||||
|
||||
const authClient = ldap.createClient({
|
||||
url: config.url,
|
||||
timeout: 10000,
|
||||
connectTimeout: 10000
|
||||
});
|
||||
|
||||
authClient.on('error', (err) => {
|
||||
authClient.unbind();
|
||||
console.error('[LDAP] Bind client error:', err);
|
||||
callback(err, false);
|
||||
});
|
||||
|
||||
authClient.bind(userDN, password, (err) => {
|
||||
authClient.unbind();
|
||||
if (err) {
|
||||
const errorMsg = err.message || String(err);
|
||||
console.error('[LDAP] Bind failed:', errorMsg);
|
||||
console.error('[LDAP] Bind error details:', err);
|
||||
return callback(new Error(`Ungültiges Passwort oder Authentifizierungsfehler: ${errorMsg}`), false);
|
||||
}
|
||||
// Erfolg: kanonischen Benutzernamen mitgeben, damit die DB-Lookup mit dem Sync-Benutzernamen funktioniert
|
||||
console.log('[LDAP] Bind successful!');
|
||||
console.log('[LDAP] Returning canonicalUsername:', canonicalUsername);
|
||||
console.log('========== LDAP AUTHENTICATE - SUCCESS ==========\n');
|
||||
callback(null, true, { username: canonicalUsername });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user