#!/usr/bin/env python3 """ Тестовый скрипт для проверки обращения к Active Directory. Аналог find_user_in_ad() из sd_dispatcher.py — standalone. Установка зависимостей (на сервере 192.168.1.106): python3 -m pip install ldap3 Использование: # 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 import os import re import sys # --------------------------------------------------------------------------- # 1. Загрузка учётных данных # --------------------------------------------------------------------------- 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" 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, # не тянем schema — экономим время connect_timeout=5, ) conn = Connection( server, user=ad_user, password=ad_pass, auto_bind=True, receive_timeout=5, ) return conn, ad_base # --------------------------------------------------------------------------- # 3. Поиск пользователя # --------------------------------------------------------------------------- def find_user(conn, ad_base, login=None, email=None, phone=None): """ Ищет пользователя в AD. Приоритет: телефон > логин > email. Возвращает dict с атрибутами или None. """ if phone: filt = ( f"(|(telephoneNumber=*{phone}*)(mobile=*{phone}*) " f"(homePhone=*{phone}*)(pager=*{phone}*) " f"(facsimileTelephoneNumber=*{phone}*)(ipPhone=*{phone}*))" ) by = f"телефону {phone}" elif login: clean = login.split("\\")[-1].split("/")[-1] filt = f"(sAMAccountName={clean})" by = f"логину {clean}" elif email: filt = f"(mail={email})" by = f"почте {email}" else: print("❌ Укажите --login, --email или --phone") sys.exit(1) print(f"\n[LDAP] Поиск: {filt}") print(f"[LDAP] Base DN: {ad_base}") # Запрос 1 — основной conn.search( search_base=ad_base, search_filter=filt, attributes=["l", "company", "manager", "department", "ou", "displayName", "mail", "sAMAccountName", "title", "telephoneNumber", "mobile", "givenName", "sn"], ) if not conn.entries: print(f"\n❌ Пользователь не найден по {by}") return None user = conn.entries[0] print(f"\n✅ Найдено: {user.entry_dn}") result = { "sam": user.sAMAccountName.value if "sAMAccountName" in user else None, "given_name": user.givenName.value if "givenName" in user else None, "surname": user.sn.value if "sn" in user else None, "display_name": user.displayName.value if "displayName" in user else None, "mail": user.mail.value if "mail" in user else None, "title": user.title.value if "title" in user else None, "department": user.department.value if "department" in user else None, "city": user.l.value if "l" in user and user.l.value else None, "company": user.company.value if "company" in user and user.company.value else None, "dn": user.entry_dn, "phone": user.telephoneNumber.value if "telephoneNumber" in user else None, "mobile": user.mobile.value if "mobile" in user else None, } # Город из 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']}") # Запрос 2 — email руководителя manager_dn = user.manager.value if "manager" in user else None if manager_dn: conn.search( search_base=manager_dn, search_filter="(objectClass=user)", attributes=["mail", "displayName"], search_scope="BASE", ) 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 else: result["manager_email"] = None else: result["manager_email"] = None return result # --------------------------------------------------------------------------- # 4. Форматирование вывода # --------------------------------------------------------------------------- def print_report(data): """Красивый вывод результатов.""" print("\n" + "=" * 60) print(" РЕЗУЛЬТАТ ПОИСКА В ACTIVE DIRECTORY") print("=" * 60) print(f" sAMAccountName: {data['sam']}") print(f" Имя: {data['given_name']}") print(f" Фамилия: {data['surname']}") print(f" ФИО: {data['display_name']}") print(f" Email: {data['mail']}") print(f" Должность: {data['title']}") print(f" Отдел: {data['department']}") print(f" Город: {data['city'] or '—'}") print(f" Компания: {data['company'] or '—'}") print(f" Телефон: {data['phone'] or '—'}") print(f" Мобильный: {data['mobile'] or '—'}") print(f" Руководитель: {data['manager_name'] or '—'}") if data.get("manager_email"): print(f" Email руково.: {data['manager_email']}") print("-" * 60) print(f" DN: {data['dn']}") # 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)): indent = " " * i print(f" {indent}└── {ou}") print("=" * 60) # --------------------------------------------------------------------------- # 5. Тестирование всех методов поиска # --------------------------------------------------------------------------- def test_all_methods(conn, ad_base): """Находит первого пользователя и проверяет все методы поиска.""" print("\n" + "─" * 60) print(" РЕЖИМ ТЕСТИРОВАНИЯ: поиск по всем методам") print("─" * 60) conn.search( search_base=ad_base, search_filter="(objectClass=user)", attributes=["sAMAccountName", "mail", "telephoneNumber", "displayName", "l", "company", "department"], size_limit=1, ) if not conn.entries: print("❌ В AD нет пользователей.") return sample = conn.entries[0] sam = sample.sAMAccountName.value 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})") 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, ad_base, **kwargs) if r: print_report(r) print("\n" + "─" * 60) print(" ✅ Тестирование завершено") print("─" * 60) # --------------------------------------------------------------------------- # MAIN # --------------------------------------------------------------------------- if __name__ == "__main__": 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="Номер телефона для поиска") parser.add_argument("--test-all", action="store_true", help="Найти первого пользователя и протестировать все методы поиска") args = parser.parse_args() # Учётные данные: аргументы > 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, ad_base) else: data = find_user(conn, ad_base, login=args.login, email=args.email, phone=args.phone) if data: print_report(data) conn.unbind()