5008ba737f
- ad_connector: full Django→AD provisioning via ldap3 — LDAP client, sync service (create/update/rename CN/enable-disable), Celery tasks, signals (auto-create with panel password, attribute updates, password sync, block/unblock), sync_users_to_ad management command - mattermost_connector: rewrite service on mattermostautodriver/httpx, fix bare except in username generation (catch ResourceNotFound), format_html args instead of interpolation - it_manager: Equipment.brand CharField → FK to core.Directory (equipment_brand type) with data migration - add missing employees is_staff migration, add requirements.txt Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
471 lines
22 KiB
Python
Executable File
471 lines
22 KiB
Python
Executable File
from typing import Optional
|
||
from django.utils import timezone
|
||
from django.contrib.auth import get_user_model
|
||
from apps.mattermost_connector.models import MattermostUser
|
||
|
||
Employee = get_user_model()
|
||
from apps.mattermost_connector.mattermost_service import MattermostService
|
||
from mattermostautodriver.exceptions import ResourceNotFound
|
||
import logging
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
def generate_username(employee: Employee) -> str:
|
||
"""Генерация username для Mattermost"""
|
||
if employee.username:
|
||
return employee.username
|
||
|
||
# Если нет username, генерируем из email
|
||
base = employee.email.split('@')[0].lower()
|
||
# Убираем недопустимые символы
|
||
base = ''.join(c if c.isalnum() or c in '-_.' else '_' for c in base)
|
||
return base
|
||
|
||
|
||
def sync_employee(employee: Employee, password: Optional[str] = None) -> bool:
|
||
"""
|
||
Синхронизация сотрудника с Mattermost
|
||
|
||
Args:
|
||
employee: Объект сотрудника
|
||
password: Пароль пользователя (если известен, используется при создании)
|
||
"""
|
||
service = MattermostService()
|
||
|
||
try:
|
||
mattermost_user = None
|
||
mm_data = None
|
||
|
||
# 1. Проверяем, есть ли уже mattermost_user_id у сотрудника
|
||
if employee.mattermost_user_id:
|
||
try:
|
||
mm_data = service.get_user(employee.mattermost_user_id)
|
||
mattermost_user = MattermostUser.objects.filter(
|
||
mattermost_id=employee.mattermost_user_id
|
||
).first()
|
||
logger.info(f"Найден существующий пользователь Mattermost по ID: {employee.mattermost_user_id}")
|
||
except Exception as e:
|
||
logger.warning(f"Пользователь {employee.mattermost_user_id} не найден в Mattermost: {e}")
|
||
Employee.objects.filter(pk=employee.pk).update(mattermost_user_id=None)
|
||
employee.mattermost_user_id = None
|
||
|
||
# 2. Если ID нет, пытаемся найти по email
|
||
if not employee.mattermost_user_id:
|
||
try:
|
||
mm_data = service.get_user_by_email(employee.email)
|
||
logger.info(f"Найден существующий пользователь Mattermost по email: {employee.email}")
|
||
|
||
# Обновляем Employee через QuerySet (без вызова signals)
|
||
Employee.objects.filter(pk=employee.pk).update(
|
||
mattermost_user_id=mm_data['id'],
|
||
mattermost_username=mm_data['username']
|
||
)
|
||
employee.mattermost_user_id = mm_data['id']
|
||
employee.mattermost_username = mm_data['username']
|
||
|
||
# Создаем или обновляем MattermostUser
|
||
mattermost_user, created = MattermostUser.objects.get_or_create(
|
||
mattermost_id=mm_data['id'],
|
||
defaults={
|
||
'django_user': employee,
|
||
'username': mm_data['username'],
|
||
'email': mm_data['email'],
|
||
'is_active': not mm_data.get('delete_at', 0)
|
||
}
|
||
)
|
||
|
||
if not created and mattermost_user.django_user != employee:
|
||
mattermost_user.django_user = employee
|
||
mattermost_user.save()
|
||
|
||
except Exception as e:
|
||
logger.info(f"Пользователь с email {employee.email} не найден в Mattermost, будет создан новый")
|
||
mm_data = None
|
||
|
||
# 3. Если пользователь не найден - создаем нового
|
||
if not mm_data:
|
||
username = generate_username(employee)
|
||
|
||
# Проверяем уникальность username
|
||
counter = 1
|
||
original_username = username
|
||
while True:
|
||
try:
|
||
service.get_user_by_username(username)
|
||
username = f"{original_username}{counter}"
|
||
counter += 1
|
||
except ResourceNotFound:
|
||
break
|
||
|
||
# Используем переданный пароль или генерируем новый
|
||
user_password = password if password else generate_random_password()
|
||
|
||
user_data = {
|
||
'email': employee.email,
|
||
'username': username,
|
||
'password': user_password,
|
||
'first_name': employee.first_name or '',
|
||
'last_name': employee.last_name or '',
|
||
'nickname': employee.get_full_name() or '',
|
||
'position': employee.post.name if employee.post else '',
|
||
'auth_service': '', # Обязательное поле для пользователей без SSO
|
||
'auth_data': '' # Обязательное поле для пользователей без SSO
|
||
}
|
||
|
||
logger.info(f"Создание нового пользователя Mattermost: {username} (пароль {'передан из Django' if password else 'сгенерирован'})")
|
||
mm_data = service.create_user(user_data)
|
||
|
||
# Устанавливаем timezone отдельно через preferences
|
||
try:
|
||
timezone_data = {
|
||
'useAutomaticTimezone': 'false',
|
||
'manualTimezone': 'Europe/Kiev',
|
||
'automaticTimezone': ''
|
||
}
|
||
service.update_user_preferences(mm_data['id'], timezone_data)
|
||
logger.info(f"Установлен часовой пояс для {username}")
|
||
except Exception as e:
|
||
logger.warning(f"Не удалось установить часовой пояс для {username}: {e}")
|
||
|
||
# Создаем запись в MattermostUser
|
||
mattermost_user = MattermostUser.objects.create(
|
||
django_user=employee,
|
||
mattermost_id=mm_data['id'],
|
||
username=mm_data['username'],
|
||
email=mm_data['email'],
|
||
is_active=True
|
||
)
|
||
|
||
# Обновляем Employee через QuerySet
|
||
Employee.objects.filter(pk=employee.pk).update(
|
||
mattermost_user_id=mm_data['id'],
|
||
mattermost_username=mm_data['username'],
|
||
mattermost_sync_status='synced',
|
||
mattermost_last_sync=timezone.now()
|
||
)
|
||
employee.mattermost_user_id = mm_data['id']
|
||
employee.mattermost_username = mm_data['username']
|
||
employee.mattermost_sync_status = 'synced'
|
||
employee.mattermost_last_sync = timezone.now()
|
||
|
||
logger.info(f"Создан пользователь Mattermost для {employee.email}")
|
||
|
||
# Синхронизируем фото при создании
|
||
if employee.photo:
|
||
try:
|
||
service.set_profile_image(mm_data['id'], employee.photo.path)
|
||
logger.info(f"Фото профиля синхронизировано для {employee.email}")
|
||
except Exception as e:
|
||
logger.warning(f"Не удалось синхронизировать фото для {employee.email}: {e}")
|
||
|
||
else:
|
||
# 4. Обновляем существующего пользователя
|
||
update_data = {
|
||
'email': employee.email,
|
||
'first_name': employee.first_name,
|
||
'last_name': employee.last_name,
|
||
'nickname': employee.get_full_name(),
|
||
'position': employee.post.name if employee.post else '',
|
||
}
|
||
|
||
logger.info(f"Обновление пользователя Mattermost: {employee.mattermost_user_id}")
|
||
service.update_user(employee.mattermost_user_id, update_data)
|
||
|
||
# Синхронизируем фото при обновлении
|
||
if employee.photo:
|
||
try:
|
||
service.set_profile_image(employee.mattermost_user_id, employee.photo.path)
|
||
logger.info(f"Фото профиля обновлено для {employee.email}")
|
||
except Exception as e:
|
||
logger.warning(f"Не удалось обновить фото для {employee.email}: {e}")
|
||
|
||
# Обновляем MattermostUser
|
||
if mattermost_user:
|
||
mattermost_user.email = employee.email
|
||
mattermost_user.updated_at = timezone.now()
|
||
mattermost_user.last_sync = timezone.now()
|
||
mattermost_user.save()
|
||
|
||
# Обновляем Employee через QuerySet
|
||
Employee.objects.filter(pk=employee.pk).update(
|
||
mattermost_sync_status='synced',
|
||
mattermost_last_sync=timezone.now()
|
||
)
|
||
employee.mattermost_sync_status = 'synced'
|
||
employee.mattermost_last_sync = timezone.now()
|
||
|
||
logger.info(f"Обновлен пользователь Mattermost для {employee.email}")
|
||
|
||
# 5. Добавляем пользователя в команды согласно настройкам
|
||
add_user_to_teams(employee)
|
||
|
||
# 6. Добавляем пользователя в каналы согласно настройкам
|
||
add_user_to_channels(employee)
|
||
|
||
# 7. Если сотрудник привязан к аптеке, добавляем в канал аптеки
|
||
if employee.user_pharmacy:
|
||
from apps.mattermost_connector.services.pharmacy_channel_service import add_employee_to_pharmacy_channel
|
||
add_employee_to_pharmacy_channel(employee)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка синхронизации {employee.email}: {e}")
|
||
Employee.objects.filter(pk=employee.pk).update(mattermost_sync_status='error')
|
||
employee.mattermost_sync_status = 'error'
|
||
return False
|
||
def deactivate_mattermost_user(employee: Employee) -> bool:
|
||
"""Деактивация пользователя в Mattermost"""
|
||
if not employee.mattermost_user_id:
|
||
return False
|
||
|
||
service = MattermostService()
|
||
|
||
try:
|
||
service.deactivate_user(employee.mattermost_user_id)
|
||
|
||
# Обновляем MattermostUser
|
||
mattermost_user = MattermostUser.objects.filter(
|
||
mattermost_id=employee.mattermost_user_id
|
||
).first()
|
||
|
||
if mattermost_user:
|
||
mattermost_user.is_active = False
|
||
mattermost_user.save()
|
||
|
||
logger.info(f"Деактивирован пользователь Mattermost для {employee.email}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка деактивации {employee.email}: {e}")
|
||
return False
|
||
|
||
def sync_password(employee: Employee, new_password: str) -> bool:
|
||
"""
|
||
Синхронизация пароля с Mattermost
|
||
|
||
Args:
|
||
employee: Объект сотрудника
|
||
new_password: Новый пароль в plain-text
|
||
"""
|
||
if not employee.mattermost_user_id:
|
||
logger.warning(f"Не могу синхронизировать пароль для {employee.email}: нет mattermost_user_id")
|
||
return False
|
||
|
||
service = MattermostService()
|
||
|
||
try:
|
||
logger.info(f"Синхронизация пароля для {employee.email} в Mattermost")
|
||
service.reset_password(employee.mattermost_user_id, new_password)
|
||
|
||
# Обновляем время последней синхронизации
|
||
Employee.objects.filter(pk=employee.pk).update(
|
||
mattermost_last_sync=timezone.now()
|
||
)
|
||
employee.mattermost_last_sync = timezone.now()
|
||
|
||
logger.info(f"Пароль успешно синхронизирован для {employee.email}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка синхронизации пароля для {employee.email}: {e}")
|
||
return False
|
||
|
||
|
||
def activate_mattermost_user(employee: Employee) -> bool:
|
||
"""Активация пользователя в Mattermost"""
|
||
if not employee.mattermost_user_id:
|
||
return False
|
||
|
||
service = MattermostService()
|
||
|
||
try:
|
||
service.activate_user(employee.mattermost_user_id)
|
||
|
||
# Обновляем MattermostUser
|
||
mattermost_user = MattermostUser.objects.filter(
|
||
mattermost_id=employee.mattermost_user_id
|
||
).first()
|
||
|
||
if mattermost_user:
|
||
mattermost_user.is_active = True
|
||
mattermost_user.save()
|
||
|
||
logger.info(f"Активирован пользователь Mattermost для {employee.email}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка активации {employee.email}: {e}")
|
||
return False
|
||
|
||
|
||
def generate_random_password(length: int = 16) -> str:
|
||
"""Генерация случайного пароля"""
|
||
import secrets
|
||
import string
|
||
|
||
alphabet = string.ascii_letters + string.digits + string.punctuation
|
||
password = ''.join(secrets.choice(alphabet) for _ in range(length))
|
||
return password
|
||
|
||
|
||
def add_user_to_teams(employee: Employee) -> bool:
|
||
"""
|
||
Добавление пользователя в команды Mattermost согласно настройкам синхронизации
|
||
|
||
Args:
|
||
employee: Объект сотрудника
|
||
|
||
Returns:
|
||
bool: True если успешно добавлен хотя бы в одну команду
|
||
"""
|
||
from apps.mattermost_connector.models import TeamSyncSettings, MattermostTeamMembership, MattermostUser
|
||
|
||
if not employee.mattermost_user_id:
|
||
logger.warning(f"Пользователь {employee.email} не имеет mattermost_user_id")
|
||
return False
|
||
|
||
service = MattermostService()
|
||
added_to_teams = 0
|
||
|
||
try:
|
||
# Получаем MattermostUser
|
||
mattermost_user = MattermostUser.objects.filter(mattermost_id=employee.mattermost_user_id).first()
|
||
if not mattermost_user:
|
||
logger.warning(f"MattermostUser не найден для {employee.email}")
|
||
return False
|
||
|
||
# Получаем все активные настройки синхронизации, отсортированные по приоритету
|
||
team_settings = TeamSyncSettings.objects.filter(is_active=True).select_related('team').prefetch_related('departments')
|
||
|
||
for setting in team_settings:
|
||
# Проверяем, нужно ли добавлять пользователя в эту команду
|
||
should_add = False
|
||
|
||
if setting.auto_add_users:
|
||
if setting.filter_by_department:
|
||
# Проверяем, что пользователь из нужного отдела
|
||
if employee.department and setting.departments.filter(id=employee.department.id).exists():
|
||
should_add = True
|
||
else:
|
||
# Добавляем всех пользователей
|
||
should_add = True
|
||
|
||
if should_add:
|
||
# Проверяем, не добавлен ли уже пользователь в эту команду
|
||
membership_exists = MattermostTeamMembership.objects.filter(
|
||
user=mattermost_user,
|
||
team=setting.team
|
||
).exists()
|
||
|
||
if not membership_exists:
|
||
try:
|
||
# Добавляем пользователя в команду
|
||
logger.info(f"Добавление {employee.email} в команду {setting.team.display_name}")
|
||
service.add_user_to_team(setting.team.team_id, employee.mattermost_user_id)
|
||
|
||
# Создаем запись о членстве
|
||
MattermostTeamMembership.objects.create(
|
||
user=mattermost_user,
|
||
team=setting.team
|
||
)
|
||
|
||
added_to_teams += 1
|
||
logger.info(f"Пользователь {employee.email} добавлен в команду {setting.team.display_name}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка добавления {employee.email} в команду {setting.team.display_name}: {e}")
|
||
else:
|
||
logger.debug(f"Пользователь {employee.email} уже в команде {setting.team.display_name}")
|
||
|
||
if added_to_teams > 0:
|
||
logger.info(f"Пользователь {employee.email} добавлен в {added_to_teams} команд(у/ы)")
|
||
return True
|
||
else:
|
||
logger.info(f"Пользователь {employee.email} уже добавлен во все необходимые команды")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка добавления пользователя {employee.email} в команды: {e}")
|
||
return False
|
||
|
||
|
||
def add_user_to_channels(employee: Employee) -> bool:
|
||
"""
|
||
Добавление пользователя в каналы Mattermost согласно настройкам синхронизации
|
||
|
||
Args:
|
||
employee: Объект сотрудника
|
||
|
||
Returns:
|
||
bool: True если успешно добавлен хотя бы в один канал
|
||
"""
|
||
from apps.mattermost_connector.models import ChannelSyncSettings, MattermostChannelMembership, MattermostUser
|
||
|
||
if not employee.mattermost_user_id:
|
||
logger.warning(f"Пользователь {employee.email} не имеет mattermost_user_id")
|
||
return False
|
||
|
||
service = MattermostService()
|
||
added_to_channels = 0
|
||
|
||
try:
|
||
# Получаем MattermostUser
|
||
mattermost_user = MattermostUser.objects.filter(mattermost_id=employee.mattermost_user_id).first()
|
||
if not mattermost_user:
|
||
logger.warning(f"MattermostUser не найден для {employee.email}")
|
||
return False
|
||
|
||
# Получаем все активные настройки синхронизации каналов, отсортированные по приоритету
|
||
channel_settings = ChannelSyncSettings.objects.filter(is_active=True).select_related('channel', 'channel__team').prefetch_related('departments')
|
||
|
||
for setting in channel_settings:
|
||
# Проверяем, нужно ли добавлять пользователя в этот канал
|
||
should_add = False
|
||
|
||
if setting.auto_add_users:
|
||
if setting.filter_by_department:
|
||
# Проверяем, что пользователь из нужного отдела
|
||
if employee.department and setting.departments.filter(id=employee.department.id).exists():
|
||
should_add = True
|
||
else:
|
||
# Добавляем всех пользователей
|
||
should_add = True
|
||
|
||
if should_add:
|
||
# Проверяем, не добавлен ли уже пользователь в этот канал
|
||
membership_exists = MattermostChannelMembership.objects.filter(
|
||
user=mattermost_user,
|
||
channel=setting.channel
|
||
).exists()
|
||
|
||
if not membership_exists:
|
||
try:
|
||
# Добавляем пользователя в канал
|
||
logger.info(f"Добавление {employee.email} в канал {setting.channel.display_name}")
|
||
service.add_user_to_channel(setting.channel.channel_id, employee.mattermost_user_id)
|
||
|
||
# Создаем запись о членстве
|
||
MattermostChannelMembership.objects.create(
|
||
user=mattermost_user,
|
||
channel=setting.channel
|
||
)
|
||
|
||
added_to_channels += 1
|
||
logger.info(f"Пользователь {employee.email} добавлен в канал {setting.channel.display_name}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка добавления {employee.email} в канал {setting.channel.display_name}: {e}")
|
||
else:
|
||
logger.debug(f"Пользователь {employee.email} уже в канале {setting.channel.display_name}")
|
||
|
||
if added_to_channels > 0:
|
||
logger.info(f"Пользователь {employee.email} добавлен в {added_to_channels} канал(ов)")
|
||
return True
|
||
else:
|
||
logger.info(f"Пользователь {employee.email} уже добавлен во все необходимые каналы")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка добавления пользователя {employee.email} в каналы: {e}")
|
||
return False |