Files
ArchiveCostWebapp/logic/scripts/ad_lookup.py
T
2026-06-12 14:49:11 +02:00

160 lines
6.9 KiB
Python

"""
Dieses Skript liest eine JSON-Datei mit Gruppeninformationen ein und fragt für jede Gruppe
das zugehörige Department im Active Directory (AD) ab. Das Department ist in Form der entsprechenden
Zahl (z.B. 6000 für CPS) angegeben.
Die JSON-Datei ist eine Liste von Dictionaries, welche folgende Form haben:
{"ownerGroup":"p18973","copies":"oneCopyBig","size":"6.85468397803E11","packedSize":"6.8563388416E11"}
Es werden zwei Hauptfälle unterscheidet:
1. a-Gruppen (z.B. a-groupname): Diese Gruppen enthalten direkt das Attribut 'department'.
2. p-Gruppen (z.B. p12345): Diese Gruppen sind jeweils einer Beamline zugeordnet. Jede
Beamline besitzt ein 'department'-Attribut. Das Skript ermittelt zuerst die Beamline
und danach das Department. Die Zuordnung von Beamline zu Department wird gecached,
um AD-Anfragen zu minimieren, da es viele p-Gruppen aber nur wenige Beamlines gibt.
Am Schluss wird eine Liste der eingelesenen Dictionaries ausgegeben, die allerdings durch
einen 'department' Eintrag ergänzt wurden. Bei den p-Gruppen wird zusätzlich noch ein 'beamline'
Eintrag hinzugefügt.
"""
import json
import os
import sys
from ldap3 import ALL, Connection, Server
# --- Konfiguration ---
AD_SERVER = 'd.psi.ch'
AD_SERVER = os.environ.get('AD_SERVER')
AD_USER = os.environ.get('AD_USER')
AD_PASSWORD = os.environ.get('AD_PASSWORD')
AD_SEARCH_BASE = 'DC=d,DC=psi,DC=ch'
AD_SEARCH_BASE = os.environ.get('AD_SEARCH_BASE')
FILE_PATH = os.environ.get('GROUPINFO_JSON_LNK_NAME_WITH_PATH')
# FILE_PATH = "/data/" + os.environ.get('GROUPINFO_JSON_LNK_NAME')
def lookup_ad():
try:
# Datenbasis aus der via Umgebungsvariable definierten JSON-Datei laden
if FILE_PATH is None:
print("ERROR: Environment variable GROUPINFO_JSON_LNK_NAME_WITH_PATH is not set.",
file=sys.stderr, flush=True)
return
if not os.path.exists(FILE_PATH):
print(f"ERROR: File {FILE_PATH} not found!", file=sys.stderr, flush=True)
return
with open(FILE_PATH, 'r') as f:
items = json.load(f)
print(f"INFO: {len(items)} entries loaded.", file=sys.stderr, flush=True)
# AD-Verbindung via SSL (Port 636) initialisieren
server = Server(AD_SERVER, port=636, use_ssl=True, get_info=ALL, connect_timeout=10)
with Connection(server, user=AD_USER, password=AD_PASSWORD, auto_bind=True) as conn:
# Cache für die Zuordnung von Beamline zu Department
beamline_department_cache = {}
count = 0
for item in items:
count += 1
group_name = item.get('ownerGroup', '')
# --- Fall A: direkte a-Gruppen ---
if group_name.startswith('a-'):
search_filter = f'(&(objectClass=group)(cn={group_name}))'
conn.search(
search_base=AD_SEARCH_BASE,
search_filter=search_filter,
attributes=['department']
)
if conn.entries:
res = str(conn.entries[0].department) if 'department' in conn.entries[0] else "no-dept"
item['department'] = res
else:
item['department'] = "not_found"
# --- Fall B: zweistufige p-Gruppen ---
elif group_name.startswith('p') and group_name[1:2].isdigit():
beamline = "not_found" # Initialisierung
# Stufe 1: 'memberOf' der p-Gruppe abfragen, um die zugehörige Beamline-Gruppe zu finden
p_filter = f'(cn={group_name})'
conn.search(
search_base=AD_SEARCH_BASE,
search_filter=p_filter,
attributes=['memberOf']
)
if conn.entries and 'memberOf' in conn.entries[0] and conn.entries[0].memberOf:
selected_dn = None
for dn in conn.entries[0].memberOf:
dn_str = str(dn)
if 'OU=Beamlines,OU=Experiment,OU=IT' in dn_str:
selected_dn = dn_str
break
if selected_dn is None:
selected_dn = str(conn.entries[0].memberOf[0])
try:
beamline = selected_dn.split(',')[0].split('=')[1]
except IndexError:
print(f"WARN: Format von DN '{selected_dn}' unerwartet.", file=sys.stderr, flush=True)
beamline = "format_error"
item['beamline'] = beamline
# Stufe 2: Department für die ermittelte Beamline abfragen (mit Caching)
if beamline in beamline_department_cache:
item['department'] = beamline_department_cache[beamline]
else:
search_filter = f'(&(objectClass=group)(cn={beamline}))'
conn.search(
search_base=AD_SEARCH_BASE,
search_filter=search_filter,
attributes=['department']
)
if conn.entries:
res = str(conn.entries[0].department) if 'department' in conn.entries[0] else "no-dept"
beamline_department_cache[beamline] = res # Department im Cache speichern
item['department'] = res
else:
beamline_department_cache[beamline] = "not_found"
item['department'] = "not_found"
# --- Fall C: Abweichende Schemata ---
else:
print(f"WARN: Unbekanntes Gruppen-Schema: {group_name}", file=sys.stderr, flush=True)
item['department'] = "unknown_schema"
continue
# Fortschritt alle 500 Einträge im stderr protokollieren
if count % 500 == 0:
print(f"INFO: progress {count}/{len(items)}...", file=sys.stderr, flush=True)
# Kompletter JSON-Output (momentan auskommentiert):
print(json.dumps(items))
# Alle Gruppen mit leerem Department-Feld ausgeben
for item in items:
if item.get('department') == "[]":
if 'beamline' in item:
print(f"LEER -> {item['ownerGroup']} (Beamline: {item['beamline']})", file=sys.stderr, flush=True)
else:
print(f"LEER -> {item['ownerGroup']} (hat keine Beamline)", file=sys.stderr, flush=True)
except Exception as e:
print(f"ERROR: {str(e)}", file=sys.stderr, flush=True)
if __name__ == "__main__":
lookup_ad()