diff --git a/ad/test_ad_lookup.py b/ad/test_ad_lookup.py index 84e4cc3..f078d75 100644 --- a/ad/test_ad_lookup.py +++ b/ad/test_ad_lookup.py @@ -1,16 +1,28 @@ #!/usr/bin/env python3 """ -Тестовый скрипт — поиск пользователя в Active Directory. -Аналог find_user_in_ad() из sd_dispatcher.py. +Тестовый скрипт для проверки обращения к Active Directory. +Аналог find_user_in_ad() из sd_dispatcher.py — standalone. -Единственная зависимость: ldap3 - pip install ldap3 +Установка зависимостей (на сервере 192.168.1.106): + python3 -m pip install ldap3 -Запуск (перенесите на любой сервер с доступом к AD): - python3 test_ad_lookup.py --user "DOMAIN\\service_account" --pass "password" --login ivanov - python3 test_ad_lookup.py --user "DOMAIN\\service_account" --pass "password" --email ivanov@sibcem.ru - python3 test_ad_lookup.py --user "DOMAIN\\service_account" --pass "password" --phone 79131234567 - python3 test_ad_lookup.py --user "DOMAIN\\service_account" --pass "password" --test-all +Использование: + # 1) Через аргументы: + python3 test_ad_lookup.py --user "sibcem\\svc_dispatcher" --pass "password" --login ivanov + + # 2) Через env-переменные: + export AD_USER="sibcem\\svc_dispatcher" + export AD_PASS="password" + python3 test_ad_lookup.py --login ivanov + + # 3) По email: + python3 test_ad_lookup.py --email ivanov@sibcem.ru + + # 4) По телефону: + python3 test_ad_lookup.py --phone 79131234567 + + # 5) Тест: найти первого попавшегося пользователя и проверить все методы: + python3 test_ad_lookup.py --test-all """ import argparse @@ -18,17 +30,30 @@ import os import re import sys +# --------------------------------------------------------------------------- +# 1. Загрузка учётных данных +# --------------------------------------------------------------------------- -# ========================================================================= -# 1. Подключение к LDAP -# ========================================================================= +def get_credentials(): + """Возвращает (user, password, base_dn).""" + user = os.getenv("AD_USER", "").strip() + password = os.getenv("AD_PASS", "").strip() + base = os.getenv("AD_BASE", "").strip() or "OU=-Пользователи,DC=sibcem,DC=ru" -def connect_to_ad(ad_user, ad_pass): + return user, password, base + + +# --------------------------------------------------------------------------- +# 2. Подключение к LDAP +# --------------------------------------------------------------------------- + +def connect_to_ad(ad_user, ad_pass, ad_base): + """Возвращает подключённый ldap3.Connection.""" from ldap3 import Server, Connection server = Server( "ldap://172.16.20.20", - get_info=None, + get_info=None, # не тянем schema — экономим время connect_timeout=5, ) conn = Connection( @@ -38,17 +63,17 @@ def connect_to_ad(ad_user, ad_pass): auto_bind=True, receive_timeout=5, ) - return conn + return conn, ad_base -# ========================================================================= -# 2. Поиск пользователя -# ========================================================================= +# --------------------------------------------------------------------------- +# 3. Поиск пользователя +# --------------------------------------------------------------------------- -def find_user(conn, login=None, email=None, phone=None): +def find_user(conn, ad_base, login=None, email=None, phone=None): """ - Приоритет: телефон > логин > email. - Возвращает dict атрибутов или None. + Ищет пользователя в AD. Приоритет: телефон > логин > email. + Возвращает dict с атрибутами или None. """ if phone: filt = ( @@ -68,21 +93,20 @@ def find_user(conn, login=None, email=None, phone=None): print("❌ Укажите --login, --email или --phone") sys.exit(1) - print(f"\n[LDAP] Фильтр: {filt}") - print(f"[LDAP] Base: OU=-Пользователи,DC=sibcem,DC=ru") + print(f"\n[LDAP] Поиск: {filt}") + print(f"[LDAP] Base DN: {ad_base}") + # Запрос 1 — основной conn.search( - search_base="OU=-Пользователи,DC=sibcem,DC=ru", + search_base=ad_base, search_filter=filt, - attributes=[ - "sAMAccountName", "displayName", "givenName", "sn", - "mail", "title", "department", "l", "company", - "manager", "telephoneNumber", "mobile", - ], + attributes=["l", "company", "manager", "department", "ou", + "displayName", "mail", "sAMAccountName", "title", + "telephoneNumber", "mobile", "givenName", "sn"], ) if not conn.entries: - print(f"\n❌ Не найден по {by}") + print(f"\n❌ Пользователь не найден по {by}") return None user = conn.entries[0] @@ -103,16 +127,16 @@ def find_user(conn, login=None, email=None, phone=None): "mobile": user.mobile.value if "mobile" in user else None, } - # Город из OU если атрибут l пустой + # Город из OU-пути если атрибут l пустой if not result["city"]: match = re.search(r"OU=([^,]+),OU=-Пользователи", result["dn"], re.IGNORECASE) if match: result["city"] = match.group(1).strip() if result["city"].lower() == "красноярск" and "OU=ООО Комбинат Волна" in result["dn"]: result["company"] = "ООО Комбинат Волна" - print(f" Город из OU: {result['city']}") + print(f" Город извлечён из OU: {result['city']}") - # Руководитель — второй запрос + # Запрос 2 — email руководителя manager_dn = user.manager.value if "manager" in user else None if manager_dn: conn.search( @@ -123,9 +147,7 @@ def find_user(conn, login=None, email=None, phone=None): ) if conn.entries and "mail" in conn.entries[0]: result["manager_email"] = conn.entries[0].mail.value - result["manager_name"] = ( - conn.entries[0].displayName.value if "displayName" in conn.entries[0] else None - ) + result["manager_name"] = conn.entries[0].displayName.value if "displayName" in conn.entries[0] else None else: result["manager_email"] = None else: @@ -134,11 +156,12 @@ def find_user(conn, login=None, email=None, phone=None): return result -# ========================================================================= -# 3. Вывод -# ========================================================================= +# --------------------------------------------------------------------------- +# 4. Форматирование вывода +# --------------------------------------------------------------------------- def print_report(data): + """Красивый вывод результатов.""" print("\n" + "=" * 60) print(" РЕЗУЛЬТАТ ПОИСКА В ACTIVE DIRECTORY") print("=" * 60) @@ -160,33 +183,38 @@ def print_report(data): print(f" DN: {data['dn']}") # OU-дерево - ou_parts = [p.strip() for p in data["dn"].split(",") if p.strip().startswith("OU=")] + dn = data["dn"] + parts = [p.strip() for p in dn.split(",")] + ou_parts = [p for p in parts if p.startswith("OU=")] if ou_parts: print(f"\n OU-структура:") for i, ou in enumerate(reversed(ou_parts)): - print(f" {' ' * i}└── {ou}") + indent = " " * i + print(f" {indent}└── {ou}") print("=" * 60) -# ========================================================================= -# 4. Тест всех методов -# ========================================================================= +# --------------------------------------------------------------------------- +# 5. Тестирование всех методов поиска +# --------------------------------------------------------------------------- -def test_all_methods(conn): +def test_all_methods(conn, ad_base): + """Находит первого пользователя и проверяет все методы поиска.""" print("\n" + "─" * 60) - print(" ТЕСТИРОВАНИЕ: все методы поиска") + print(" РЕЖИМ ТЕСТИРОВАНИЯ: поиск по всем методам") print("─" * 60) conn.search( - search_base="OU=-Пользователи,DC=sibcem,DC=ru", + search_base=ad_base, search_filter="(objectClass=user)", - attributes=["sAMAccountName", "mail", "telephoneNumber"], + attributes=["sAMAccountName", "mail", "telephoneNumber", "displayName", + "l", "company", "department"], size_limit=1, ) if not conn.entries: - print("❌ Нет пользователей в AD.") + print("❌ В AD нет пользователей.") return sample = conn.entries[0] @@ -194,56 +222,61 @@ def test_all_methods(conn): mail = sample.mail.value if "mail" in sample else None phone = sample.telephoneNumber.value if "telephoneNumber" in sample else None - print(f"\n📌 Тестовый: {sam} ({mail})") + print(f"\n📌 Тестовый пользователь: {sam} ({mail})") for label, kwargs in [("логину", {"login": sam}), ("email", {"email": mail}) if mail else None, ("телефону", {"phone": phone}) if phone else None]: if kwargs is None: continue - print(f"\n[Тест] По {label}...") - r = find_user(conn, **kwargs) + print(f"\n[Тест] Поиск по {label}...") + r = find_user(conn, ad_base, **kwargs) if r: print_report(r) print("\n" + "─" * 60) - print(" ✅ Готово") + print(" ✅ Тестирование завершено") print("─" * 60) -# ========================================================================= +# --------------------------------------------------------------------------- # MAIN -# ========================================================================= +# --------------------------------------------------------------------------- if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Поиск пользователя в Active Directory", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Примеры: - python3 test_ad_lookup.py --user "sibcem\\\\svc_ad" --pass "pass" --login ivanov - python3 test_ad_lookup.py --user "sibcem\\\\svc_ad" --pass "pass" --email ivanov@sibcem.ru - python3 test_ad_lookup.py --user "sibcem\\\\svc_ad" --pass "pass" --phone 79131234567 - python3 test_ad_lookup.py --user "sibcem\\\\svc_ad" --pass "pass" --test-all - """, - ) - parser.add_argument("--user", required=True, help="LDAP user (DOMAIN\\\\account)") - parser.add_argument("--pass", dest="password", required=True, help="LDAP password") + parser = argparse.ArgumentParser(description="Тестовый поиск пользователя в AD") + parser.add_argument("--user", help="LDAP user (sAMAccountName или DN)") + parser.add_argument("--pass", dest="password", help="LDAP password") + parser.add_argument("--base", help="Base DN (по умолчанию OU=-Пользователи,DC=sibcem,DC=ru)") group = parser.add_mutually_exclusive_group(required=True) - group.add_argument("--login", help="sAMAccountName пользователя") - group.add_argument("--email", help="Email пользователя") - group.add_argument("--phone", help="Номер телефона") + group.add_argument("--login", help="sAMAccountName пользователя для поиска") + group.add_argument("--email", help="Email пользователя для поиска") + group.add_argument("--phone", help="Номер телефона для поиска") parser.add_argument("--test-all", action="store_true", - help="Найти первого пользователя и проверить все методы") + help="Найти первого пользователя и протестировать все методы поиска") args = parser.parse_args() - conn = connect_to_ad(args.user, args.password) - print(f"[LDAP] Подключено к ldap://172.16.20.20 как {args.user}") + # Учётные данные: аргументы > env > prompt + ad_user = args.user or os.getenv("AD_USER", "") + ad_pass = args.password or os.getenv("AD_PASS", "") + ad_base = args.base or os.getenv("AD_BASE", "OU=-Пользователи,DC=sibcem,DC=ru") + + if not ad_user or not ad_pass: + # Пробуем получить из .env файла проекта + env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "sd_dispatcher", "config", ".env") + if os.path.exists(env_path): + print(f"[.env] Загружено: {env_path}") + else: + print("❌ Укажите --user и --pass, или задайте AD_USER / AD_PASS") + sys.exit(1) + + conn, ad_base = connect_to_ad(ad_user, ad_pass, ad_base) + print(f"[LDAP] Подключено к ldap://172.16.20.20 как {ad_user}") if args.test_all: - test_all_methods(conn) + test_all_methods(conn, ad_base) else: - data = find_user(conn, login=args.login, email=args.email, phone=args.phone) + data = find_user(conn, ad_base, login=args.login, email=args.email, phone=args.phone) if data: print_report(data)