Files
sd_dispatcher/ad/test_ad_lookup.py
T
2026-07-04 09:43:46 +07:00

284 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()