add AD 2FA group check to instruct Trueconf module
This commit is contained in:
+33
-1
@@ -3,6 +3,7 @@ from utils.texts import (
|
|||||||
EMOJI_DIGITS,
|
EMOJI_DIGITS,
|
||||||
INSTRUCT_MAIN_MENU_TEXT,
|
INSTRUCT_MAIN_MENU_TEXT,
|
||||||
INSTRUCT_TRUECONF_TEXT,
|
INSTRUCT_TRUECONF_TEXT,
|
||||||
|
INSTRUCT_TRUECONF_WITH_AD,
|
||||||
INSTRUCT_EMAIL_TEXT,
|
INSTRUCT_EMAIL_TEXT,
|
||||||
)
|
)
|
||||||
import logging
|
import logging
|
||||||
@@ -20,6 +21,35 @@ logger = logging.getLogger(__name__)
|
|||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_user_cn(user_id: str) -> str:
|
||||||
|
"""
|
||||||
|
Получить CN пользователя из AD по его user_id (email или логин).
|
||||||
|
user_id из TrueConf — это обычно email, например 'ds.krivochenko@sibcem.ru'
|
||||||
|
"""
|
||||||
|
from ldap3 import Server, Connection, ALL
|
||||||
|
from config.config import AD_SERVER, AD_USER, AD_PASSWORD, AD_BASE
|
||||||
|
from ldap3.utils.conv import escape_filter_chars
|
||||||
|
|
||||||
|
try:
|
||||||
|
server = Server(AD_SERVER, get_info=ALL)
|
||||||
|
conn = Connection(server, user=AD_USER, password=AD_PASSWORD, auto_bind=True)
|
||||||
|
safe_id = escape_filter_chars(user_id)
|
||||||
|
short_username = user_id.split('@')[0] if '@' in user_id else user_id
|
||||||
|
search_filter = (
|
||||||
|
f"(&(objectClass=user)(|"
|
||||||
|
f"(mail={safe_id})"
|
||||||
|
f"(userPrincipalName={safe_id})"
|
||||||
|
f"(userPrincipalName={escape_filter_chars(f'{short_username}@sibcem.ru')}))"
|
||||||
|
f"(sAMAccountName={escape_filter_chars(short_username)})))"
|
||||||
|
)
|
||||||
|
conn.search(AD_BASE, search_filter, attributes=["cn", "sAMAccountName"])
|
||||||
|
if conn.entries:
|
||||||
|
return str(conn.entries[0].cn)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка получения CN из AD для {user_id}: {e}")
|
||||||
|
return user_id # fallback — используем user_id как CN
|
||||||
|
|
||||||
|
|
||||||
@router.message()
|
@router.message()
|
||||||
async def instruct_router_handler(msg: Message):
|
async def instruct_router_handler(msg: Message):
|
||||||
user_id = msg.from_user.id
|
user_id = msg.from_user.id
|
||||||
@@ -55,7 +85,9 @@ async def instruct_router_handler(msg: Message):
|
|||||||
elif cmd == "1":
|
elif cmd == "1":
|
||||||
log_menu_stats(user_id, "Инструкции", "Trueconf")
|
log_menu_stats(user_id, "Инструкции", "Trueconf")
|
||||||
set_state(user_id, "INSTRUCT_TRUECONF_VIEW")
|
set_state(user_id, "INSTRUCT_TRUECONF_VIEW")
|
||||||
await msg.answer(INSTRUCT_TRUECONF_TEXT, parse_mode="html")
|
cn = _get_user_cn(user_id)
|
||||||
|
text = INSTRUCT_TRUECONF_WITH_AD(EMOJI_DIGITS, cn)
|
||||||
|
await msg.answer(text, parse_mode="html")
|
||||||
elif cmd == "2":
|
elif cmd == "2":
|
||||||
log_menu_stats(user_id, "Инструкции", "Почта")
|
log_menu_stats(user_id, "Инструкции", "Почта")
|
||||||
set_state(user_id, "INSTRUCT_EMAIL_VIEW")
|
set_state(user_id, "INSTRUCT_EMAIL_VIEW")
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# /opt/trueconf_bot/utils/ad_checker.py
|
||||||
|
# Проверка членства пользователя в группах Active Directory
|
||||||
|
|
||||||
|
|
||||||
|
from ldap3 import Server, Connection, ALL
|
||||||
|
|
||||||
|
|
||||||
|
def _get_ad_connection():
|
||||||
|
"""Создать соединение с AD."""
|
||||||
|
from config.config import AD_SERVER, AD_USER, AD_PASSWORD, AD_BASE # noqa: E501
|
||||||
|
server = Server(AD_SERVER, get_info=ALL)
|
||||||
|
return Connection(server, user=AD_USER, password=AD_PASSWORD, auto_bind=True), AD_BASE
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_groups(cn: str) -> list[str] | None:
|
||||||
|
"""
|
||||||
|
Найти пользователя по CN и вернуть список групп.
|
||||||
|
Возвращает None если пользователь не найден.
|
||||||
|
"""
|
||||||
|
conn, ad_base = _get_ad_connection()
|
||||||
|
try:
|
||||||
|
conn.search(
|
||||||
|
search_base=ad_base,
|
||||||
|
search_filter=f"(cn={cn})",
|
||||||
|
attributes=["cn", "sAMAccountName", "memberOf"],
|
||||||
|
)
|
||||||
|
if not conn.entries:
|
||||||
|
return None
|
||||||
|
entry = conn.entries[0]
|
||||||
|
return [str(g) for g in entry.memberOf]
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Ошибка AD при поиске {cn}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def check_group_membership(cn: str, group_pattern: str = "2FA") -> dict:
|
||||||
|
"""
|
||||||
|
Проверить членство пользователя в группе.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cn: CN пользователя (например 'Krivochenko_Denis_Sergeevich')
|
||||||
|
group_pattern: подстрока для поиска в имени группы (по умолчанию '2FA')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict с полями:
|
||||||
|
found: bool — найден ли пользователь
|
||||||
|
cn: str | None — CN пользователя
|
||||||
|
sam: str | None — sAMAccountName
|
||||||
|
in_group: bool — в группе ли
|
||||||
|
groups: list[str] — все группы
|
||||||
|
matching_groups: list[str] — группы с паттерном
|
||||||
|
"""
|
||||||
|
groups = get_user_groups(cn)
|
||||||
|
if groups is None:
|
||||||
|
return {
|
||||||
|
"found": False,
|
||||||
|
"cn": None,
|
||||||
|
"sam": None,
|
||||||
|
"in_group": False,
|
||||||
|
"groups": [],
|
||||||
|
"matching_groups": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
matching = [g for g in groups if group_pattern.upper() in g.upper()]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"found": True,
|
||||||
|
"cn": cn,
|
||||||
|
"sam": groups[0].split(",")[0].split("=")[-1], # грубо из DN
|
||||||
|
"in_group": len(matching) > 0,
|
||||||
|
"groups": groups,
|
||||||
|
"matching_groups": matching,
|
||||||
|
}
|
||||||
+43
-1
@@ -586,14 +586,55 @@ def _instruct_main_menu(emojis: dict) -> str:
|
|||||||
f"{emojis['2']} — 📧 Почта\n\n"
|
f"{emojis['2']} — 📧 Почта\n\n"
|
||||||
f"<i>{emojis['0']} — В главное меню</i>"
|
f"<i>{emojis['0']} — В главное меню</i>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _instruct_trueconf(emojis: dict) -> str:
|
def _instruct_trueconf(emojis: dict) -> str:
|
||||||
|
"""Статический текст Trueconf — без проверки группы."""
|
||||||
return (
|
return (
|
||||||
"🖥 <b>Инструкция по Trueconf</b>\n\n"
|
"🖥 <b>Инструкция по Trueconf</b>\n\n"
|
||||||
"Данная функция находится в разработке.\n\n"
|
"Данная функция находится в разработке.\n\n"
|
||||||
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _instruct_trueconf_with_ad(emojis: dict, cn: str) -> str:
|
||||||
|
"""
|
||||||
|
Текст Trueconf с проверкой группы 2FA.
|
||||||
|
cn — CN пользователя из AD.
|
||||||
|
"""
|
||||||
|
from utils.ad_checker import check_group_membership
|
||||||
|
|
||||||
|
result = check_group_membership(cn, "2FA")
|
||||||
|
|
||||||
|
if not result["found"]:
|
||||||
|
return (
|
||||||
|
f"🖥 <b>Инструкция по Trueconf</b>\n\n"
|
||||||
|
f"Пользователь {cn} не найден в Active Directory.\n\n"
|
||||||
|
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
if result["in_group"]:
|
||||||
|
body = (
|
||||||
|
f"🖥 <b>Инструкция по Trueconf</b>\n\n"
|
||||||
|
f"✅ <b>Доступ к Trueconf разрешён.</b>\n\n"
|
||||||
|
f"У вас есть доступ к функциям Trueconf.\n\n"
|
||||||
|
f"Детали:\n"
|
||||||
|
f"• CN: {result['cn']}\n"
|
||||||
|
f"• Группы 2FA: {', '.join(result['matching_groups'])}\n\n"
|
||||||
|
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
body = (
|
||||||
|
f"🖥 <b>Инструкция по Trueconf</b>\n\n"
|
||||||
|
f"❌ <b>Доступ к Trueconf запрещён.</b>\n\n"
|
||||||
|
f"У вас нет группы 2FA в Active Directory.\n\n"
|
||||||
|
f"Детали:\n"
|
||||||
|
f"• CN: {result['cn']}\n"
|
||||||
|
f"• Групп 2FA: 0\n\n"
|
||||||
|
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
|
||||||
|
)
|
||||||
|
|
||||||
|
return body
|
||||||
|
|
||||||
|
|
||||||
def _instruct_email(emojis: dict) -> str:
|
def _instruct_email(emojis: dict) -> str:
|
||||||
return (
|
return (
|
||||||
"📧 <b>Инструкция по Почте</b>\n\n"
|
"📧 <b>Инструкция по Почте</b>\n\n"
|
||||||
@@ -605,4 +646,5 @@ def _instruct_email(emojis: dict) -> str:
|
|||||||
|
|
||||||
INSTRUCT_MAIN_MENU_TEXT = _instruct_main_menu(EMOJI_DIGITS)
|
INSTRUCT_MAIN_MENU_TEXT = _instruct_main_menu(EMOJI_DIGITS)
|
||||||
INSTRUCT_TRUECONF_TEXT = _instruct_trueconf(EMOJI_DIGITS)
|
INSTRUCT_TRUECONF_TEXT = _instruct_trueconf(EMOJI_DIGITS)
|
||||||
|
INSTRUCT_TRUECONF_WITH_AD = _instruct_trueconf_with_ad
|
||||||
INSTRUCT_EMAIL_TEXT = _instruct_email(EMOJI_DIGITS)
|
INSTRUCT_EMAIL_TEXT = _instruct_email(EMOJI_DIGITS)
|
||||||
|
|||||||
Reference in New Issue
Block a user