Verzeichnis (LDAP)¶
Ein Verzeichnis (LDAP, im Firmenumfeld oft Active Directory) ist das
zentrale Adressbuch einer Organisation: Benutzer, Gruppen, Telefonnummern,
Abteilungen. Mit ldap liest ein Skript daraus und schreibt — mit den
passenden Rechten — auch hinein.
Ausführliches dazu: LDAP-Zugriff.
Was du vorher wissen solltest¶
- Jeder Eintrag hat eine eindeutige Adresse, den DN (Distinguished
Name), z. B.
uid=oliver,ou=Users,dc=kcforms,dc=test. Lies ihn von links nach rechts wie eine Postanschrift: Personoliverin der AbteilungUsersder Organisationkcforms.test. - Ein Eintrag hat Attribute wie
cn(voller Name),mail,title(Funktion) odertelephoneNumber. Manche Attribute haben mehrere Werte. - Gesucht wird mit einem Filter in Klammern:
(uid=oliver),(&(sn=Meyer)(l=Hagen))(beides muss passen),(|(l=Köln)(l=Bonn))(eins von beiden).
Die Beispiele verwenden den LDAP-Konnektor kcads und den Test-Server
der Entwicklungsumgebung (dc=kcforms,dc=test). Bei dir heißen Konnektor,
Basis-DN und Attribute womöglich anders — das richtet der Admin unter
LDAP-Konnektoren ein.
Einsteiger¶
1. Verbindung öffnen und wieder schließen¶
ldap.connect("name") meldet sich mit den Daten aus dem Konnektor an.
close() beendet die Verbindung.
Vergisst du close(), schließt KCForms die Verbindung am Ende des Skripts
selbst.
2. Gibt es einen Benutzer?¶
exists(dn) antwortet mit true oder false.
var c = ldap.connect("kcads");
if (c.exists("uid=oliver,ou=Users,dc=kcforms,dc=test")) {
log.info("Den Benutzer oliver gibt es.");
}
c.close();
3. Einen Benutzer auslesen¶
get(dn) holt einen Eintrag. e.get("attribut") liest einen Wert daraus.
var c = ldap.connect("kcads");
var e = c.get("uid=oliver,ou=Users,dc=kcforms,dc=test");
if (e) {
log.info("Name: " + e.get("cn"));
log.info("E-Mail: " + e.get("mail"));
log.info("Funktion: " + e.get("title"));
}
c.close();
Gibt es den Eintrag nicht, liefert get den Wert null.
4. Einen Benutzer über seinen Anmeldenamen suchen¶
Meist kennst du nicht den ganzen DN, sondern nur den Anmeldenamen. search
findet den Eintrag. Leere Such-Basis "" heißt: im ganzen Verzeichnis.
var c = ldap.connect("kcads");
var treffer = c.search("", "(uid=cbecker)");
if (treffer.length > 0) {
log.info("Gefunden: " + treffer[0].dn);
}
c.close();
5. Wie viele Personen stehen im Verzeichnis?¶
search liefert eine Liste; length ist ihre Länge.
var c = ldap.connect("kcads");
var alle = c.search("ou=Users,dc=kcforms,dc=test", "(objectClass=inetOrgPerson)");
log.info(alle.length + " Personen im Verzeichnis");
c.close();
6. Alle Werte eines Attributs¶
values("attribut") liefert alle Werte als Liste — wichtig bei
Attributen wie objectClass, die mehrere Werte haben.
var c = ldap.connect("kcads");
var e = c.get("uid=oliver,ou=Users,dc=kcforms,dc=test");
log.info("Objektklassen: " + e.values("objectClass").join(", "));
c.close();
7. Prüfen, ob ein Attribut gefüllt ist¶
var c = ldap.connect("kcads");
var e = c.get("uid=oliver,ou=Users,dc=kcforms,dc=test");
if (e.has("telephoneNumber")) {
log.info("Telefon: " + e.get("telephoneNumber"));
} else {
log.info("Keine Telefonnummer hinterlegt");
}
c.close();
8. Welche Attribute hat ein Eintrag?¶
attributes.ids listet alle Attributnamen — hilfreich, wenn du ein neues
Verzeichnis erkundest.
var c = ldap.connect("kcads");
var e = c.search("", "(uid=abeck)")[0];
log.info("Attribute: " + e.attributes.ids.join(", "));
c.close();
9. Ein Passwort prüfen¶
ldap.authenticate(konnektor, dn, passwort) meldet sich testweise als
dieser Benutzer an und sagt true oder false. Ein leeres Passwort musst
du vorher abfangen — manche Server lassen eine Anmeldung ohne Passwort als
„anonym" durch.
var dn = "uid=" + ctx.form.login + ",ou=Users,dc=kcforms,dc=test";
var passwort = "sicher-falsch";
if (!passwort) {
log.info("Kein Passwort eingegeben");
} else if (ldap.authenticate("kcads", dn, passwort)) {
log.info("Passwort stimmt");
} else {
log.info("Passwort falsch");
}
10. Werte ins Formular übernehmen¶
Ein Klassiker im onLoad-Hook: Name und E-Mail des Benutzers vorbelegen.
var c = ldap.connect("kcads");
var e = c.get("uid=" + ctx.form.login + ",ou=Users,dc=kcforms,dc=test");
if (e) {
ctx.form.vorname = e.get("givenName");
ctx.form.name = e.get("sn");
ctx.form.email = e.get("mail");
log.info("Formular vorbelegt für " + e.get("cn"));
}
c.close();
Fortgeschritten¶
11. Suche mit mehreren Bedingungen¶
& verbindet Bedingungen („und"). Hier: alle Personen aus Hagen in der
Abteilung „IT & Infrastruktur".
var c = ldap.connect("kcads");
var filter = "(&(objectClass=inetOrgPerson)(l=Hagen)(ou=IT & Infrastruktur))";
var leute = c.search("ou=Users,dc=kcforms,dc=test", filter);
log.info(leute.length + " IT-Mitarbeitende in Hagen");
leute.slice(0, 5).forEach(function (p) { log.info("- " + p.get("cn") + ", " + p.get("title")); });
c.close();
12. Treffer durchgehen und früh aufhören¶
Mit einer Funktion als viertem Wert wird jeder Treffer einzeln behandelt.
Gibt die Funktion false zurück, endet die Suche.
var c = ldap.connect("kcads");
var gezaehlt = 0;
c.search("ou=Users,dc=kcforms,dc=test", "(title=*leiter*)", "subtree", function (e) {
gezaehlt++;
log.info("gefunden: " + e.get("cn") + " – " + e.get("title"));
return gezaehlt < 3; // nach drei Treffern aufhören
});
c.close();
13. Nur eine Ebene durchsuchen¶
"onelevel" sucht nur direkt unterhalb der Basis — hier: alle Gruppen, ohne
in die Tiefe zu gehen.
var c = ldap.connect("kcads");
var gruppen = c.search("ou=Groups,dc=kcforms,dc=test", "(objectClass=groupOfNames)", "onelevel");
log.info(gruppen.length + " Gruppen: "
+ gruppen.slice(0, 6).map(function (g) { return g.get("cn"); }).join(", ") + " …");
c.close();
14. Die Mitglieder einer Gruppe lesen¶
Eine Gruppe speichert ihre Mitglieder im Attribut member — als Liste von
DNs.
var c = ldap.connect("kcads");
var g = c.get("cn=Entwickler,ou=Groups,dc=kcforms,dc=test");
g.values("member").forEach(function (dn) {
var person = c.get(dn);
log.info("Mitglied: " + (person ? person.get("cn") : dn));
});
c.close();
15. In welchen Gruppen ist ein Benutzer?¶
Die „umgekehrte" Suche: alle Gruppen, in deren member der Benutzer steht.
Das funktioniert mit jedem Verzeichnis.
var c = ldap.connect("kcads");
var userDn = "uid=oliver,ou=Users,dc=kcforms,dc=test";
var gruppen = c.search("", "(&(objectClass=groupOfNames)(member=" + userDn + "))");
log.info("Gruppen von oliver: " + gruppen.map(function (g) { return g.get("cn"); }).join(", "));
c.close();
In Active Directory steht dasselbe direkt am Benutzer im Attribut
memberOf.
16. Eingaben im Filter absichern¶
Zeichen wie *, ( oder ) haben im Filter eine Bedeutung. Kommt der
Suchbegriff vom Benutzer, müssen sie „entschärft" werden — sonst lässt sich
die Suche manipulieren.
function ldapEscape(s) {
return String(s || "").replace(/[\\*()\0]/g, function (z) {
return "\\" + ("0" + z.charCodeAt(0).toString(16)).slice(-2);
});
}
var c = ldap.connect("kcads");
var filter = "(sn=" + ldapEscape(ctx.form.suchbegriff) + ")";
log.info("Filter: " + filter);
log.info(c.search("", filter).length + " Treffer"); // der Trick-Versuch findet nichts
c.close();
17. Ein kleines Telefonbuch¶
Alle Personen, deren Nachname mit „Beck" beginnt, alphabetisch sortiert.
Den * setzt hier das Skript, nicht der Benutzer.
var c = ldap.connect("kcads");
var leute = c.search("ou=Users,dc=kcforms,dc=test", "(sn=Beck*)");
leute.sort(function (a, b) { return a.get("cn").localeCompare(b.get("cn")); });
leute.forEach(function (p) {
log.info(p.get("cn") + " " + (p.get("telephoneNumber") || "–") + " " + p.get("l"));
});
c.close();
18. Suchergebnis in eine Detail-Tabelle schreiben¶
Eine Detail-Tabelle erwartet eine Liste von Objekten — eins pro Zeile.
var c = ldap.connect("kcads");
var leute = c.search("ou=Users,dc=kcforms,dc=test", "(&(objectClass=inetOrgPerson)(l=Berlin))");
ctx.form.personen = leute.slice(0, 20).map(function (p) {
return { name: p.get("cn"), email: p.get("mail"), telefon: p.get("telephoneNumber") };
});
log.info(ctx.form.personen.length + " Zeilen übernommen");
c.close();
19. Mitarbeitende je Standort zählen¶
Ein Objekt dient als „Strichliste": Für jeden Ort wird hochgezählt.
var c = ldap.connect("kcads");
var zaehler = {};
c.search("ou=Users,dc=kcforms,dc=test", "(objectClass=inetOrgPerson)", "subtree", function (p) {
var ort = p.get("l") || "(ohne Ort)";
zaehler[ort] = (zaehler[ort] || 0) + 1;
});
Object.keys(zaehler).sort().forEach(function (ort) {
log.info("Standort " + ort + ": " + zaehler[ort]);
});
c.close();
20. Den Vorgesetzten nachschlagen¶
Das Attribut manager enthält den DN der Führungskraft. Den liest du mit
einem zweiten get.
var c = ldap.connect("kcads");
var p = c.search("", "(uid=abeck)")[0];
if (p.has("manager")) {
var chef = c.get(p.get("manager"));
log.info(p.get("cn") + " – Vorgesetzte(r): " + (chef ? chef.get("cn") : "unbekannt"));
} else {
log.info("Kein Vorgesetzter eingetragen");
}
c.close();
Profi¶
Für diese Beispiele braucht der Konnektor ein Konto mit Schreibrechten.
Sie legen eigene Übungseinträge an (uid=bsp.muster, cn=Beispielgruppe)
und löschen sie im letzten Beispiel wieder.
21. Einen Benutzer anlegen¶
add(dn, attribute) legt einen neuen Eintrag an. objectClass bestimmt,
welche Attribute erlaubt sind; cn und sn sind bei Personen Pflicht.
var c = ldap.connect("kcads");
var dn = "uid=bsp.muster,ou=Users,dc=kcforms,dc=test";
if (!c.exists(dn)) {
c.add(dn, {
objectClass: ["top", "person", "organizationalPerson", "inetOrgPerson"],
uid: "bsp.muster",
cn: "Max Muster",
givenName: "Max",
sn: "Muster",
mail: "max.muster@beispiel.example",
title: "Praktikant"
});
log.info("Benutzer angelegt: " + dn);
} else {
log.info("Benutzer gibt es schon");
}
c.close();
22. Werte ändern¶
modify(dn, attribute) ersetzt die genannten Attribute. Alle anderen
bleiben, wie sie sind.
var c = ldap.connect("kcads");
var dn = "uid=bsp.muster,ou=Users,dc=kcforms,dc=test";
c.modify(dn, { title: "Sachbearbeiter", telephoneNumber: "+49 221 555-0101" });
var e = c.get(dn);
log.info("Neu: " + e.get("title") + ", " + e.get("telephoneNumber"));
c.close();
23. Ein Attribut entfernen¶
Wird ein Attribut mit null (oder einer leeren Liste) gesetzt, verschwindet
es ganz.
var c = ldap.connect("kcads");
var dn = "uid=bsp.muster,ou=Users,dc=kcforms,dc=test";
c.modify(dn, { telephoneNumber: null });
log.info(c.get(dn).has("telephoneNumber") ? "noch da" : "Telefon entfernt");
c.close();
24. Einen Wert zu einem mehrwertigen Attribut hinzufügen¶
Weil modify ersetzt, liest du zuerst die vorhandenen Werte, fügst den
neuen hinzu und schreibst alle zurück.
var c = ldap.connect("kcads");
var dn = "uid=bsp.muster,ou=Users,dc=kcforms,dc=test";
var adressen = c.get(dn).values("mail").slice(); // Kopie der Liste
var neu = "m.muster@beispiel.example";
if (adressen.indexOf(neu) < 0) adressen.push(neu);
c.modify(dn, { mail: adressen });
log.info(c.get(dn).values("mail").length + " Adresse(n): " + c.get(dn).values("mail").join(", "));
c.close();
25. Eine Gruppe anlegen¶
Eine Gruppe vom Typ groupOfNames braucht mindestens ein Mitglied.
var c = ldap.connect("kcads");
var gruppe = "cn=Beispielgruppe,ou=Groups,dc=kcforms,dc=test";
if (!c.exists(gruppe)) {
c.add(gruppe, {
objectClass: ["top", "groupOfNames"],
cn: "Beispielgruppe",
description: "Übungsgruppe aus den Skript-Beispielen",
member: ["uid=bsp.muster,ou=Users,dc=kcforms,dc=test"]
});
}
log.info("Gruppe angelegt mit " + c.get(gruppe).values("member").length + " Mitglied(ern)");
c.close();
26. Mitglieder hinzufügen und entfernen¶
Wie bei Beispiel 24: lesen, Liste ändern, zurückschreiben.
var c = ldap.connect("kcads");
var gruppe = "cn=Beispielgruppe,ou=Groups,dc=kcforms,dc=test";
function mitglieder(dn) { return c.get(dn).values("member").slice(); }
// hinzufügen
var m = mitglieder(gruppe);
var neu = "uid=cbecker,ou=Users,dc=kcforms,dc=test";
if (m.indexOf(neu) < 0) { m.push(neu); c.modify(gruppe, { member: m }); }
// wieder entfernen – aber nie das letzte Mitglied
m = mitglieder(gruppe).filter(function (x) { return x !== neu; });
if (m.length > 0) c.modify(gruppe, { member: m });
// zur Kontrolle einmal hinzufügen und stehen lassen
m = mitglieder(gruppe); m.push(neu); c.modify(gruppe, { member: m });
log.info("Mitglieder jetzt: " + mitglieder(gruppe).length);
c.close();
27. Formularwerte ins Verzeichnis übernehmen¶
Aus einem Antrag heraus: Gibt es den Benutzer, werden seine Daten aktualisiert, sonst wird er angelegt. Fehler landen sauber im Formular.
var c = ldap.connect("kcads");
try {
var dn = "uid=" + ctx.form.login + ",ou=Users,dc=kcforms,dc=test";
var werte = {
givenName: ctx.form.vorname,
sn: ctx.form.name,
cn: ctx.form.vorname + " " + ctx.form.name,
mail: ctx.form.email
};
if (c.exists(dn)) {
c.modify(dn, werte);
log.info("Verzeichnis aktualisiert: " + dn);
} else {
werte.objectClass = ["top", "person", "organizationalPerson", "inetOrgPerson"];
werte.uid = ctx.form.login;
c.add(dn, werte);
log.info("Im Verzeichnis angelegt: " + dn);
}
} catch (e) {
ctx.addError("login", "Verzeichnis konnte nicht geändert werden: " + e.message);
} finally {
c.close();
}
finally läuft immer — auch nach einem Fehler. So bleibt keine Verbindung
offen.
28. Benutzerliste als CSV speichern¶
LDAP und CSV zusammen: alle Personen eines Standorts als Datei für Excel.
var c = ldap.connect("kcads");
var p = new csv.StandardPrinter({ separator: ";", bom: true, lineSeparator: "WINDOWS" });
p.println(["Name", "E-Mail", "Telefon", "Abteilung"]);
var n = 0;
c.search("ou=Users,dc=kcforms,dc=test", "(&(objectClass=inetOrgPerson)(l=Hagen))", "subtree", function (e) {
p.println([e.get("cn"), e.get("mail"), e.get("telephoneNumber"), e.get("ou")]);
n++;
});
c.close();
fs["/exports/telefonliste-hagen.csv"] = p;
log.info(n + " Zeilen geschrieben");
29. Nur bestimmte Gruppen einlassen¶
Zum Beispiel in onLoad: Wer nicht in einer der erlaubten LDAP-Gruppen ist,
bekommt einen Hinweis und das Formular schließt sich.
var erlaubt = ["ADMIN", "MANAGER"];
var c = ldap.connect("kcads");
var userDn = "uid=" + ctx.form.login + ",ou=Users,dc=kcforms,dc=test";
var gruppen = c.search("ou=Groups,dc=kcforms,dc=test",
"(&(objectClass=groupOfNames)(member=" + userDn + "))").map(function (g) { return g.get("cn"); });
c.close();
var darf = gruppen.some(function (g) { return erlaubt.indexOf(g) >= 0; });
if (darf) {
log.info("Zugriff erlaubt (" + gruppen.join(", ") + ")");
} else {
log.info("Zugriff verweigert");
view.add(new view.Message("Dieses Formular ist nur für Führungskräfte.", "Kein Zugriff"));
ctx.requestClose();
}
30. Aufräumen: Übungseinträge löschen¶
delete(dn) entfernt einen Eintrag. Erst die Gruppe, dann die Personen.
var c = ldap.connect("kcads");
[
"cn=Beispielgruppe,ou=Groups,dc=kcforms,dc=test",
"uid=bsp.muster,ou=Users,dc=kcforms,dc=test",
"uid=bsp.antrag,ou=Users,dc=kcforms,dc=test"
].forEach(function (dn) {
if (c.exists(dn)) { c.delete(dn); log.info("gelöscht: " + dn); }
});
c.close();
Für später: große Verzeichnisse schnell abfragen¶
Bei Tausenden von Benutzern sind Live-Abfragen langsam. ldap.sync spiegelt
Benutzer und Gruppen in die Datenbank — typischerweise nachts als
Job. Danach fragst du die Tabellen per SQL ab. Gemessen an einem
Verzeichnis mit 1.625 Benutzern dauert ein Lauf unter einer Sekunde.
Prüfe immer r.status. Jeder Konnektor hat einen eigenen Schalter
„Synchronisation aktiv". Steht er aus, macht ldap.sync gar nichts und meldet
status: "disabled" — ohne Prüfung siehst du nur Nullen und suchst den Fehler
an der falschen Stelle.
var r = ldap.sync("kcads");
if (r.status !== "ok") {
log.warn("Kein Abgleich: " + r.status + " – " + r.message);
} else {
log.info(r.usersScanned + " Benutzer, " + r.added + " neu, " + r.updated + " geändert");
}
var n = db.scalar("SELECT COUNT(*) FROM exported_user");
log.info(n + " Benutzer in der Datenbank");