#!/usr/bin/env python3 """ Тестовый скрипт — поиск пользователя в Active Directory. Аналог find_user_in_ad() из sd_dispatcher.py. Использует Passwork для загрузки учётных данных AD (как в основном коде). Если Passwork недоступен — просит ввести учётные данные вручную. Единственная зависимость: ldap3 pip install ldap3 Запуск: python3 test_ad_lookup.py --login ivanov python3 test_ad_lookup.py --email ivanov@sibcem.ru python3 test_ad_lookup.py --phone 79131234567 python3 test_ad_lookup.py --test-all """ import argparse import base64 import json import os import re import sys # ========================================================================= # 1. Загрузка учётных данных AD # ========================================================================= def load_ad_credentials(): """ Загружает AD_USER и AD_PASSWORD из Passwork (как в config.py). fallback: ручной ввод. """ # --- Путь 1: через модуль Passwork --- passwork_dir = "/opt/passwork" card_name = os.getenv("PW_ID_AD", "AD").strip() if passwork_dir not in sys.path: sys.path.insert(0, passwork_dir) try: from passwork import get_passwork_secrets print(f"[Passwork] Загрузка карточки '{card_name}'...") pool = get_passwork_secrets(required_cards=[card_name]) card = pool.get(card_name) if card: ad_user = card.get("login", "").strip() ad_pass = card.get("password", "").strip() if ad_user and ad_pass: print(f"[Passwork] ✅ AD_USER: {ad_user}") return ad_user, ad_pass except Exception: pass # --- Путь 2: env-переменные --- ad_user = os.getenv("AD_USER", "").strip() ad_pass = os.getenv("AD_PASS", "").strip() if ad_user and ad_pass: print("[Env] ✅ Учётные данные загружены из окружения") return ad_user, ad_pass # --- Путь 3: ручной ввод --- print("\n❌ Passwork недоступен.") print("Введите учётные данные AD вручную:\n") ad_user = input(" AD user (DOMAIN\\account): ").strip() ad_pass = input(" AD password: ").strip() if not ad_user or not ad_pass: print("❌ Отмена.") sys.exit(1) print(f"[Manual] ✅ AD_USER: {ad_user}") return ad_user, ad_pass # ========================================================================= # 2. Подключение к LDAP # ========================================================================= def connect_to_ad(ad_user, ad_pass): from ldap3 import Server, Connection server = Server( "ldap://172.16.20.20", get_info=None, connect_timeout=5, ) conn = Connection( server, user=ad_user, password=ad_pass, auto_bind=True, receive_timeout=5, ) return conn # ========================================================================= # 3. Поиск пользователя # ========================================================================= def find_user(conn, login=None, email=None, phone=None): """ Приоритет: телефон > логин > 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: OU=-Пользователи,DC=sibcem,DC=ru") conn.search( search_base="OU=-Пользователи,DC=sibcem,DC=ru", search_filter=filt, attributes=[ "sAMAccountName", "displayName", "givenName", "sn", "mail", "title", "department", "l", "company", "manager", "telephoneNumber", "mobile", ], ) 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']}") # Руководитель — второй запрос 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-дерево ou_parts = [p.strip() for p in data["dn"].split(",") if p.strip().startswith("OU=")] if ou_parts: print(f"\n OU-структура:") for i, ou in enumerate(reversed(ou_parts)): print(f" {' ' * i}└── {ou}") print("=" * 60) # ========================================================================= # 5. Тест всех методов # ========================================================================= def test_all_methods(conn): print("\n" + "─" * 60) print(" ТЕСТИРОВАНИЕ: все методы поиска") print("─" * 60) conn.search( search_base="OU=-Пользователи,DC=sibcem,DC=ru", search_filter="(objectClass=user)", attributes=["sAMAccountName", "mail", "telephoneNumber"], 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, **kwargs) if r: print_report(r) print("\n" + "─" * 60) print(" ✅ Готово") print("─" * 60) # ========================================================================= # MAIN # ========================================================================= if __name__ == "__main__": parser = argparse.ArgumentParser( description="Поиск пользователя в Active Directory", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Примеры: python3 test_ad_lookup.py --login ivanov python3 test_ad_lookup.py --email ivanov@sibcem.ru python3 test_ad_lookup.py --phone 79131234567 python3 test_ad_lookup.py --test-all Учётные данные AD загружаются из Passwork (как в основном коде). Если Passwork недоступен — запрашиваются вручную. """, ) 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() # Загрузка учётных данных (Passwork → env → ручной ввод) ad_user, ad_pass = load_ad_credentials() conn = connect_to_ad(ad_user, ad_pass) print(f"[LDAP] Подключено к ldap://172.16.20.20 как {ad_user}") if args.test_all: test_all_methods(conn) else: data = find_user(conn, login=args.login, email=args.email, phone=args.phone) if data: print_report(data) conn.unbind()