Umbenennung ueber users.cfg statt Administration API
Der Test gegen einen echten Server (Kerio Connect 10.0.9 patch 2) hat gezeigt, dass Users.set das Feld loginName stillschweigend ignoriert - kein Fehler, keine Wirkung, waehrend fullName im selben Aufruf sauber uebernommen wird. Vier Varianten geprueft (nur loginName, mit domainId, mit leeren emailAddresses, vollstaendiges User-Objekt): alle wirkungslos. Der bisherige Ansatz konnte also gar nicht funktionieren. Der Login-Name steht in users.cfg, und zwar in zwei Listen: User und UserAdditionalData. Beide verweisen ueber Name+Domain statt ueber die Guid, beide muessen mit, sonst verliert der Benutzer seine Passworthistorie. Geaendert wird der Rohtext, damit Formatierung und unbekannte Felder unangetastet bleiben. users.cfg und Store-Verzeichnis wandern jetzt in EINEM Stopp-Fenster. Laeuft Kerio zwischendurch mit nur einer Haelfte, legt es die vermeintlich fehlende Mailbox sofort neu an - beim Entwickeln genau so passiert. Damit entfaellt auch der Admin-Zugang: das Script braucht keine Zugangsdaten mehr, --list zeigt die Benutzer aus der Datei. Ausserdem gefunden: itemSource heisst 'DSInternalSource', nicht 'Internal' wie angenommen - die alte Pruefung haette bei jedem lokalen Benutzer faelschlich abgebrochen. Die Erkennung laeuft jetzt ueber InternalDb in users.cfg. Tests fuer beide Haelften unter tests/. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b12041fc81
commit
5011491067
@@ -0,0 +1,149 @@
|
||||
"""Testet UsersConfig gegen eine realistische users.cfg."""
|
||||
import shutil, sys, tempfile
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from kerio_users_config import UsersConfig, validate_login
|
||||
from kerio_common import AbortedError
|
||||
|
||||
SAMPLE = '''<config>
|
||||
<list name="User">
|
||||
<listitem>
|
||||
<variable name="Name">Admin</variable>
|
||||
<variable name="Domain">local.lan</variable>
|
||||
<variable name="Account_enabled">1</variable>
|
||||
<variable name="Guid">CDE8D0A3-929B-4AC6-8197-4C39D58C8A64</variable>
|
||||
<variable name="Password">D3S:0faed6fd</variable>
|
||||
<variable name="Fullname">Administrator</variable>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<variable name="Name">max.mustermann</variable>
|
||||
<variable name="Domain">local.lan</variable>
|
||||
<variable name="Account_enabled">1</variable>
|
||||
<variable name="Guid">94480090-4C04-4FE2-9126-0DBDAEC85F5F</variable>
|
||||
<variable name="Password">D3S:b6a11602</variable>
|
||||
<variable name="Fullname">Max Mustermann</variable>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<variable name="Name">max.mustermann</variable>
|
||||
<variable name="Domain">andere.lan</variable>
|
||||
<variable name="Account_enabled">1</variable>
|
||||
<variable name="Guid">AAAAAAAA-0000-0000-0000-000000000000</variable>
|
||||
<variable name="Password">D3S:anders</variable>
|
||||
<variable name="Fullname">Max in anderer Domain</variable>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<variable name="Name">ldap.user</variable>
|
||||
<variable name="Domain">local.lan</variable>
|
||||
<variable name="Account_enabled">1</variable>
|
||||
<variable name="Guid">BBBBBBBB-0000-0000-0000-000000000000</variable>
|
||||
<variable name="Fullname">LDAP Benutzer</variable>
|
||||
</listitem>
|
||||
</list>
|
||||
|
||||
<list name="Group">
|
||||
<listitem>
|
||||
<variable name="Name">testgruppe</variable>
|
||||
<variable name="Domain">local.lan</variable>
|
||||
<variable name="Guid">E78FEFAB-2C65-4D08-946B-76E3989EEF9D</variable>
|
||||
</listitem>
|
||||
</list>
|
||||
|
||||
<list name="Alias">
|
||||
</list>
|
||||
|
||||
<list name="UserAdditionalData">
|
||||
<listitem>
|
||||
<variable name="Name">max.mustermann</variable>
|
||||
<variable name="Domain">local.lan</variable>
|
||||
<variable name="InternalDb">1</variable>
|
||||
<variable name="PasswordHistory">SHA:28108e32</variable>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<variable name="Name">ldap.user</variable>
|
||||
<variable name="Domain">local.lan</variable>
|
||||
<variable name="InternalDb">0</variable>
|
||||
</listitem>
|
||||
</list>
|
||||
</config>
|
||||
'''
|
||||
|
||||
tmp = Path(tempfile.mkdtemp())
|
||||
cfg_path = tmp / "users.cfg"
|
||||
cfg_path.write_text(SAMPLE, encoding="utf-8")
|
||||
ok = True
|
||||
|
||||
def check(label, cond):
|
||||
global ok
|
||||
print(f" {'ok ' if cond else 'FEHLER'} {label}")
|
||||
if not cond: ok = False
|
||||
|
||||
print("== 1. Lesen ==")
|
||||
c = UsersConfig(cfg_path)
|
||||
check("Domains gefunden", set(c.domains()) == {"local.lan", "andere.lan"})
|
||||
check("4 Benutzer gesamt", len(c.users()) == 4)
|
||||
check("3 in local.lan", len(c.users("local.lan")) == 3)
|
||||
u = c.find_user("max.mustermann", "local.lan")
|
||||
check("Benutzer gefunden", u is not None and u["guid"].startswith("94480090"))
|
||||
check("fullName gelesen", u["fullName"] == "Max Mustermann")
|
||||
check("Domain-Trennung: andere.lan hat eigenen Eintrag",
|
||||
c.find_user("max.mustermann", "andere.lan")["guid"].startswith("AAAA"))
|
||||
|
||||
print("\n== 2. Verzeichnisdienst erkennen ==")
|
||||
check("max.mustermann ist intern", c.is_internal("max.mustermann", "local.lan"))
|
||||
check("ldap.user ist NICHT intern", not c.is_internal("ldap.user", "local.lan"))
|
||||
|
||||
print("\n== 3. Login-Namen validieren ==")
|
||||
for good in ("anna.schmidt", "a", "a-b_c.d", "user123"):
|
||||
try:
|
||||
validate_login(good); check(f"{good!r} erlaubt", True)
|
||||
except AbortedError:
|
||||
check(f"{good!r} erlaubt", False)
|
||||
for bad in ("", ".anfang", "ende.", "mit leerzeichen", "boes<xml>", "a"*100, "a&b"):
|
||||
try:
|
||||
validate_login(bad); check(f"{bad!r} abgelehnt", False)
|
||||
except AbortedError:
|
||||
check(f"{bad!r} abgelehnt", True)
|
||||
|
||||
print("\n== 4. Umbenennen ==")
|
||||
c2 = UsersConfig(cfg_path)
|
||||
n = c2.rename_user("max.mustermann", "maxi.musterfrau", "local.lan", "Maxi Musterfrau")
|
||||
check("2 Bloecke geaendert (User + UserAdditionalData)", n == 2)
|
||||
c2.save()
|
||||
|
||||
c3 = UsersConfig(cfg_path)
|
||||
check("neuer Name da", c3.find_user("maxi.musterfrau", "local.lan") is not None)
|
||||
check("alter Name weg in local.lan", c3.find_user("max.mustermann", "local.lan") is None)
|
||||
check("fullName geaendert", c3.find_user("maxi.musterfrau","local.lan")["fullName"] == "Maxi Musterfrau")
|
||||
check("Guid unveraendert", c3.find_user("maxi.musterfrau","local.lan")["guid"].startswith("94480090"))
|
||||
check("ANDERE DOMAIN unberuehrt",
|
||||
c3.find_user("max.mustermann", "andere.lan") is not None)
|
||||
check("Passworthistorie mitgewandert", c3.is_internal("maxi.musterfrau", "local.lan"))
|
||||
|
||||
txt = cfg_path.read_text(encoding="utf-8")
|
||||
check("Passwort-Hash erhalten", "D3S:b6a11602" in txt)
|
||||
check("PasswordHistory erhalten", "SHA:28108e32" in txt)
|
||||
check("Admin unberuehrt", "<variable name=\"Name\">Admin</variable>" in txt)
|
||||
check("Gruppe unberuehrt", "testgruppe" in txt)
|
||||
check("kein max.mustermann mehr in UserAdditionalData/local.lan",
|
||||
txt.count("max.mustermann") == 1) # nur noch andere.lan
|
||||
|
||||
print("\n== 5. other_occurrences ==")
|
||||
c4 = UsersConfig(cfg_path)
|
||||
hits = c4.other_occurrences("testgruppe", "local.lan")
|
||||
check("findet Treffer in Group-Liste", any("Group" in h for h in hits))
|
||||
check("keine Treffer fuer unbekannten Namen",
|
||||
c4.other_occurrences("gibtsnicht", "local.lan") == [])
|
||||
|
||||
print("\n== 6. Abgelehnter Login-Name aendert nichts ==")
|
||||
c5 = UsersConfig(cfg_path)
|
||||
before = c5.text
|
||||
try:
|
||||
c5.rename_user("maxi.musterfrau", "boes<xml>", "local.lan")
|
||||
check("haette ablehnen muessen", False)
|
||||
except AbortedError:
|
||||
check("ungueltiger Zielname abgelehnt", True)
|
||||
check("Text unveraendert", c5.text == before)
|
||||
|
||||
print("\n" + ("ALLE TESTS BESTANDEN" if ok else "FEHLGESCHLAGEN"))
|
||||
sys.exit(0 if ok else 1)
|
||||
Reference in New Issue
Block a user