Files
trueconf_bot/instruct/handlers.py
T

222 lines
9.1 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.
import logging
import asyncio
import os
import sys
from trueconf import Router, Message
from trueconf.types import FSInputFile
from utils.texts import (
UNKNOWN_MAIN_CMD_TEXT,
EMOJI_DIGITS,
INSTRUCT_MAIN_MENU_TEXT,
INSTRUCT_TRUECONF_TEXT,
INSTRUCT_TRUECONF_WITH_AD,
INSTRUCT_EMAIL_TEXT,
)
from utils.states import get_state, set_state, clear_state
from utils.stats_logger import log_menu_stats
from service_desk.handlers import create_ticket_in_sd
# Безопасный импорт конфигурации
try:
from config.config import DEFAULT_REQUESTER, SD_URL, SD_TOKEN
except ImportError:
import importlib.util
config_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config", "config.py")
spec = importlib.util.spec_from_file_location("custom_config", config_path)
custom_config = importlib.util.module_from_spec(spec)
spec.loader.exec_module(custom_config)
DEFAULT_REQUESTER = custom_config.DEFAULT_REQUESTER
SD_URL = custom_config.SD_URL
SD_TOKEN = custom_config.SD_TOKEN
logger = logging.getLogger(__name__)
router = Router()
# Путь к файлу инструкции Trueconf
TRUECONF_INSTRUCT_PATH = os.path.join(
os.path.dirname(__file__),
"Проверка_наличия_и_авторизация_на_мобильном_устройстве_Trueconf.docx"
)
def get_user_cn(user_id: str) -> str | None:
"""
Получить CN пользователя из AD по его user_id (email или логин).
user_id из TrueConf — это обычно email, например 'ds.krivochenko@tcs.sibcem.ru'
"""
from utils.ad_search import search_by_user_id
try:
search_id = user_id.replace("@tcs.sibcem.ru", "@sibcem.ru")
short_username = search_id.split("@")[0] if "@" in search_id else user_id
logger.info(f"AD lookup CN: user_id={user_id}, search_id={search_id}, short={short_username}")
entries = search_by_user_id(search_id, ["cn", "sAMAccountName", "mail", "userPrincipalName", "l", "userAccountControl"])
if entries:
entry = entries[0]
cn = str(entry.cn)
mail = str(entry.mail) if 'mail' in entry else ""
sam = str(entry.sAMAccountName) if 'sAMAccountName' in entry else "N/A"
upn = str(entry.userPrincipalName) if 'userPrincipalName' in entry else "N/A"
logger.info(f"AD found: cn={cn}, mail={mail}, sam={sam}, upn={upn}")
return cn
logger.warning(f"AD not found for user_id={user_id}, search_id={search_id}, short={short_username}")
except Exception as e:
logger.error(f"Ошибка получения CN из AD для {user_id}: {e}", exc_info=True)
return None
def get_ad_user_info(user_id: str) -> dict | None:
"""
Получить полную информацию о пользователе из AD.
Возвращает {cn, mail, city} или None.
"""
from utils.ad_search import search_by_user_id
try:
search_id = user_id.replace("@tcs.sibcem.ru", "@sibcem.ru")
entries = search_by_user_id(search_id, ["cn", "mail", "l", "userAccountControl"])
if entries:
entry = entries[0]
cn = str(entry.cn)
raw_mail = str(entry.mail) if 'mail' in entry else ""
# mail может быть "[]" (пустой LDAP объект), "" или "N/A" — фильтруем
mail = raw_mail if raw_mail and raw_mail not in ("[]", "N/A", "") else None
city = str(entry.l) if 'l' in entry and entry.l.value else "Кемерово"
return {"cn": cn, "mail": mail, "city": city}
except Exception as e:
logger.error(f"Ошибка получения AD info для {user_id}: {e}", exc_info=True)
return None
async def create_sd_ticket_for_trueconf(user_id: str, cn: str) -> str | None:
"""
Создать заявку в Service Desk для запроса доступа к Trueconf.
Если пользователь не найден в AD или у него нет email — создаёт заявку от DEFAULT_REQUESTER (ai@sibcem.ru).
Если пользователь найден — создаёт заявку от его email.
"""
subject = "Запрос доступа к Trueconf"
# Пытаемся получить информацию из AD
ad_info = get_ad_user_info(user_id)
logger.info(f"AD search result for SD ticket ({user_id}): {ad_info}")
if ad_info and ad_info.get("mail"):
# Пользователь найден в AD и имеет email
requester_email = ad_info["mail"]
city = ad_info.get("city", "Кемерово")
description = (
f"Пользователь {cn} ({user_id}) запросил инструкцию по Trueconf, "
f"но не имеет группы 2FA. Требуется выдача доступа."
)
else:
# Пользователь не найден в AD ИЛИ не имеет email — создаём от DEFAULT_REQUESTER (ai@sibcem.ru)
requester_email = DEFAULT_REQUESTER
city = "Кемерово"
logger.info(f"Using DEFAULT_REQUESTER ({requester_email}) for user {user_id} (cn={cn})")
if ad_info and not ad_info.get("mail"):
description = (
f"Пользователь {cn} ({user_id}) найден в Active Directory, но у него не указан email. "
f"Требуется ручная проверка и выдача доступа к Trueconf."
)
else:
description = (
f"Пользователь {cn} ({user_id}) не найден в Active Directory. "
f"Требуется ручная проверка и выдача доступа к Trueconf."
)
try:
logger.info(f"Calling create_ticket_in_sd: requester={requester_email}, city={city}")
ticket_id = await create_ticket_in_sd(requester_email, subject, description, city)
if ticket_id:
logger.info(f"SD ticket created for {cn}: #{ticket_id} (requester={requester_email})")
else:
logger.error(f"Failed to create SD ticket for {cn}")
return ticket_id
except Exception as e:
logger.error(f"Ошибка создания SD заявки для {cn}: {e}", exc_info=True)
return None
@router.message()
async def instruct_router_handler(msg: Message):
user_id = msg.from_user.id
user_name = getattr(msg.from_user, 'name', 'N/A')
user_username = getattr(msg.from_user, 'username', 'N/A')
logger.info(f"Instruct: user_id={user_id}, name={user_name}, username={user_username}")
current_state = get_state(user_id)
if not msg.text:
return
cmd = msg.text.strip().lower()
for raw_num, emoji_num in EMOJI_DIGITS.items():
if cmd == emoji_num:
cmd = raw_num
break
if current_state is None:
from utils.menu import MENU_MAP
if MENU_MAP.get(cmd) == "INSTRUCT":
msg.handled = True
set_state(user_id, "INSTRUCT_MODE")
await msg.answer(INSTRUCT_MAIN_MENU_TEXT, parse_mode="html")
return
return
if current_state == "INSTRUCT_MODE":
msg.handled = True
if cmd == "0":
log_menu_stats(user_id, "Инструкции", "Выход в главное меню")
clear_state(user_id)
from utils.menu import MENU_TEXT
await msg.answer(MENU_TEXT, parse_mode="html")
elif cmd == "1":
log_menu_stats(user_id, "Инструкции", "Trueconf")
set_state(user_id, "INSTRUCT_TRUECONF_VIEW")
# Получаем CN пользователя
cn = get_user_cn(user_id)
if cn is None:
cn = user_id # fallback
# Создаём заявку в SD
ticket_id = await create_sd_ticket_for_trueconf(user_id, cn)
# Показываем текст с результатом
text = INSTRUCT_TRUECONF_WITH_AD(EMOJI_DIGITS, cn, ticket_id)
await msg.answer(text, parse_mode="html")
# Отправляем документ-инструкцию
if os.path.isfile(TRUECONF_INSTRUCT_PATH):
await msg.answer_document(FSInputFile(TRUECONF_INSTRUCT_PATH))
elif cmd == "2":
log_menu_stats(user_id, "Инструкции", "Почта")
set_state(user_id, "INSTRUCT_EMAIL_VIEW")
await msg.answer(INSTRUCT_EMAIL_TEXT, parse_mode="html")
else:
await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")
elif current_state in ["INSTRUCT_TRUECONF_VIEW", "INSTRUCT_EMAIL_VIEW"]:
msg.handled = True
if cmd == "0":
clear_state(user_id)
from utils.menu import MENU_TEXT
await msg.answer(MENU_TEXT, parse_mode="html")
elif cmd == "9":
set_state(user_id, "INSTRUCT_MODE")
await msg.answer(INSTRUCT_MAIN_MENU_TEXT, parse_mode="html")
else:
await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")