diff --git a/tools/test_ldap.py b/tools/test_ldap.py new file mode 100644 index 0000000..9b82844 --- /dev/null +++ b/tools/test_ldap.py @@ -0,0 +1,135 @@ +"""Test LDAP/AD step-by-step (bind admin + search user + bind user). + +Lit la config depuis app_secrets (PatchCenter). Optionnel : --user/--pass +pour tester un compte utilisateur après le bind admin. + +Usage: + python tools/test_ldap.py # test bind admin uniquement + python tools/test_ldap.py --user moutaouakil-ext # test bind admin + recherche user + python tools/test_ldap.py --user xx --pass yy # + bind user (verifie mdp) +""" +import os +import sys +import argparse +import base64 +from sqlalchemy import create_engine, text + +DATABASE_URL = os.getenv("DATABASE_URL_DEMO") or os.getenv("DATABASE_URL") \ + or "postgresql://patchcenter:PatchCenter2026!@localhost:5432/patchcenter_demo" + +try: + from ldap3 import Server, Connection, ALL + from cryptography.fernet import Fernet +except ImportError: + print("[ERR] pip install ldap3 cryptography") + sys.exit(1) + + +def get_secret(conn, key): + secret = os.getenv("SECRET_KEY", + "slpm-patchcenter-secret-key-2026-change-in-production") + raw = secret.encode()[:32].ljust(32, b"\0") + f = Fernet(base64.urlsafe_b64encode(raw)) + row = conn.execute(text("SELECT value FROM app_secrets WHERE key=:k"), {"k": key}).fetchone() + if not row or not row.value: + return None + try: + return f.decrypt(row.value.encode()).decode() + except Exception: + return row.value + + +def main(): + p = argparse.ArgumentParser() + p.add_argument("--user", help="Username AD a tester (sAMAccountName)") + p.add_argument("--pass", dest="pwd", help="Mot de passe user a tester") + args = p.parse_args() + + engine = create_engine(DATABASE_URL) + conn = engine.connect() + cfg = { + "server": get_secret(conn, "ldap_server"), + "base_dn": get_secret(conn, "ldap_base_dn"), + "bind_dn": get_secret(conn, "ldap_bind_dn"), + "bind_pwd": get_secret(conn, "ldap_bind_pwd"), + "user_filter": get_secret(conn, "ldap_user_filter") or "(sAMAccountName={username})", + "email_attr": get_secret(conn, "ldap_email_attr") or "mail", + "name_attr": get_secret(conn, "ldap_name_attr") or "displayName", + } + conn.close() + + print(f"[INFO] Server : {cfg['server']}") + print(f"[INFO] Base DN : {cfg['base_dn']}") + print(f"[INFO] Bind DN : {cfg['bind_dn']}") + print(f"[INFO] Bind pwd: {'***configure***' if cfg['bind_pwd'] else 'VIDE'}") + print(f"[INFO] User filter: {cfg['user_filter']}") + print() + + if not cfg["server"] or not cfg["bind_dn"] or not cfg["bind_pwd"]: + print("[ERR] Config incomplete dans app_secrets. Configure /settings onglet LDAP d'abord.") + return + + use_ssl = cfg["server"].startswith("ldaps://") + server = Server(cfg["server"], get_info=ALL, use_ssl=use_ssl) + + # Step 1: bind admin + print("[STEP 1] Bind compte admin...") + try: + c = Connection(server, user=cfg["bind_dn"], password=cfg["bind_pwd"], auto_bind=True) + print(f" OK Bind admin reussi. Server info: {server.info.naming_contexts if server.info else 'N/A'}") + except Exception as e: + print(f" KO Bind admin echec: {type(e).__name__}: {e}") + return + + if not args.user: + c.unbind() + print("\n[OK] Bind admin OK. Lance avec --user pour tester la recherche.") + return + + # Step 2: search user + print(f"\n[STEP 2] Recherche user {args.user}...") + user_filter = cfg["user_filter"].replace("{username}", args.user) + try: + c.search(cfg["base_dn"], user_filter, + attributes=[cfg["email_attr"], cfg["name_attr"], "distinguishedName", "memberOf"]) + except Exception as e: + c.unbind() + print(f" KO Search echec: {e}") + return + + if not c.entries: + c.unbind() + print(f" KO User '{args.user}' introuvable (filtre: {user_filter})") + return + + entry = c.entries[0] + user_dn = entry.entry_dn + email = str(getattr(entry, cfg["email_attr"], "")) + name = str(getattr(entry, cfg["name_attr"], "")) + print(f" OK Trouve:") + print(f" DN : {user_dn}") + print(f" Email : {email}") + print(f" Name : {name}") + if hasattr(entry, "memberOf"): + groups = list(entry.memberOf.values) if entry.memberOf else [] + print(f" Groups: {len(groups)} appartenances") + for g in groups[:5]: + print(f" - {g}") + c.unbind() + + if not args.pwd: + print("\n[OK] Recherche OK. Lance avec --pass pour tester l'auth.") + return + + # Step 3: bind user + print(f"\n[STEP 3] Bind user {args.user} avec mdp fourni...") + try: + uc = Connection(server, user=user_dn, password=args.pwd, auto_bind=True) + uc.unbind() + print(" OK Authentification reussie.") + except Exception as e: + print(f" KO Auth echec: {e}") + + +if __name__ == "__main__": + main()