Corp/docs/guides/ldap-password-change-admin.md
lizerk 540e295ef4 feat: Fix LDAP DN bugs + add password management in admin
BREAKING CHANGES:
- Department.head автоматически очищается при увольнении руководителя
- LDAP managedBy атрибут очищается при увольнении

Fixes:
- DN не перемещался при восстановлении сотрудника (Dismissed → Users)
- DN не перемещался при увольнении сотрудника без отделов
- Ошибка при увольнении из множественных отделов (двойной modify_dn)
- Неправильные URL patterns для LDAP моделей в админке
- Двойное кодирование DN в редиректе смены пароля

Features:
- Добавлен метод _ensure_ldap_dn_location для гарантированного перемещения DN
- Добавлена проверка already_there в sync_department_member_to_ldap
- Управление паролями LDAP пользователей через админку:
  * Кнопка смены пароля в карточке пользователя
  * Массовое действие смены пароля
  * Автосинхронизация с Django password hash
- Автоочистка Department.head и LDAP managedBy при увольнении руководителя
- Добавлены методы set_password() и change_password() в LdapUser

Changed:
- Переписан сигнал sync_department_member_to_ldap с реальными методами
- Исправлены URL patterns: ldap_* → employees_*
2026-03-23 20:32:39 +03:00

196 lines
9.2 KiB
Markdown
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.

# Смена паролей LDAP через админку
## Возможности
Теперь можно менять пароли пользователей LDAP прямо через Django админ-панель!
### Добавлено в LdapUser модель:
1. **Поле `unicode_pwd`** (write-only) - для записи паролей в AD
2. **Метод `set_password(new_password)`** - установка пароля администратором
3. **Метод `change_password(old_password, new_password)`** - смена пароля пользователем
### Добавлено в админку:
1. **Кнопка "🔑 Изменить пароль"** в карточке пользователя
2. **Action "🔑 Изменить пароль выбранных пользователей"** в списке
## Как использовать
### Вариант 1: Через карточку пользователя (РЕКОМЕНДУЕТСЯ ⭐)
1. Откройте `/admin/employees/ldapuser/`
2. **Кликните** на нужном пользователе (откроется карточка)
3. Прокрутите до секции **"⚙️ Управление учетной записью"**
4. Нажмите кнопку **"🔑 Изменить пароль"**
5. Введите новый пароль (минимум 7 символов)
6. Подтвердите пароль
7. Нажмите **"Изменить пароль"**
**Результат:**
- Пароль изменён в LDAP (Active Directory)
- Пароль автоматически синхронизирован в Django БД (если есть связь через `employee_number`)
- Вы вернётесь на страницу редактирования пользователя
**Преимущества:**
- ✅ Удобнее - не нужно искать пользователя в списке и выбирать action
- ✅ Быстрее - сразу видно, какого пользователя редактируем
- ✅ Безопаснее - меньше шансов ошибиться с выбором пользователя
### Вариант 2: Через action в списке (массовая операция)
1. Откройте `/admin/employees/ldapuser/`
2. Выберите одного или нескольких пользователей (чекбоксы)
3. В dropdown "Действие" выберите **"🔑 Изменить пароль выбранных пользователей"**
4. Нажмите "Выполнить"
5. Введите новый пароль (минимум 7 символов)
6. Подтвердите пароль
7. Нажмите "Изменить пароль"
**Результат:**
- Пароль изменён в LDAP (Active Directory)
- Пароль автоматически синхронизирован в Django БД (если есть связь через `employee_number`)
- Вы получите сообщение об успехе или ошибке
**Когда использовать:**
- При необходимости сменить пароль сразу нескольким пользователям
- Для массовых операций (например, сброс паролей всем новым сотрудникам)
### Вариант 3: Программно через ORM
```python
from employees.ldap.orm_models import LdapUser
# Найти пользователя
user = LdapUser.objects.get(sam_account_name='ivanov_i')
# Установить новый пароль (админ)
user.set_password('NewSecurePass123!')
# Или сменить пароль (требует старый пароль)
user.change_password('OldPass123', 'NewSecurePass123!')
4
# НЕ нужно вызывать .save() — пароль уже изменён в AD!
```
### Вариант 3: Через Django Shell
```bash
cd /home/lizerk/Dev/EUSRR/backend
.venv/bin/python manage.py shell
```
```python
from employees.ldap.orm_models import LdapUser
from employees.models import Employee
# Найти LDAP пользователя
ldap_user = LdapUser.objects.get(sam_account_name='ivanov_i')
# Установить пароль
ldap_user.set_password('NewPass123!')
# Синхронизировать с Django (опционально)
if ldap_user.employee_number:
emp = Employee.objects.get(pk=int(ldap_user.employee_number))
emp.set_password('NewPass123!')
emp.save(update_fields=['password'])
print(f"✅ Пароль синхронизирован для {emp.email}")
```
## Требования к паролю
Active Directory политика по умолчанию:
- ✅ Минимум **7 символов** (обычно 8+)
- ✅ Буквы **верхнего и нижнего** регистра
- ✅ **Цифры**
- ✅ **Специальные символы** (рекомендуется)
- ❌ НЕ должен содержать имя пользователя
- ❌ НЕ должен быть в истории паролей
## Обработка ошибок
### ValueError
```
Пароль не соответствует политике сложности Active Directory
```
**Решение:** Используйте более сложный пароль (комбинация букв, цифр, спецсимволов)
### ValueError
```
Пароль слишком короткий
```
**Решение:** Увеличьте длину пароля (минимум 7-8 символов)
### DirectoryLdapError
```
Не удалось установить пароль: {...}
```
**Причины:**
- Отсутствует SSL/TLS соединение с AD
- Недостаточно прав у сервисной учётки
- Проблемы с подключением к LDAP
## Безопасность
⚠️ **Важно:**
- Пароли передаются через защищённое LDAP соединение (SSL/TLS)
- В БД Django хранится только хэш (не plaintext)
- В LDAP пароль хранится в защищённом виде (невозможно прочитать)
- Логируются только попытки смены пароля (без самого пароля)
## Интеграция с существующим функционалом
Эта функция **дополняет**, а не заменяет:
- `UserService.update_user(emp, changes={'password': '...'})` - работает как и раньше
- Django admin кнопка "Изменить пароль" для Employee - работает, но **НЕ синхронизирует** с LDAP автоматически
Для **автоматической синхронизации** при смене пароля через стандартную админку Employee нужно добавить переопределение метода (см. предыдущее обсуждение).
## Техническая информация
### Как это реализовано
1. **LdapUser.set_password()** вызывает `UserPasswordService.set_password()`
2. Сервис использует `conn.extend.microsoft.modify_password()` (AD extended operation)
3. Это более надёжно, чем прямая запись в атрибут `unicodePwd`
4. Политика паролей AD проверяется автоматически
5. При ошибках возвращаются понятные сообщения
### Поле unicode_pwd
- **Тип:** `CharField` (но пустое по умолчанию)
- **Write-only:** можно записать, нельзя прочитать
- **Кодировка:** UTF-16-LE с кавычками (обрабатывается автоматически)
- **Использование:** Не рекомендуется напрямую, используйте `.set_password()`
## Тестирование
```python
# В Django shell
from employees.ldap.orm_models import LdapUser
# Создать тестового пользователя (если есть)
user = LdapUser.objects.get(sam_account_name='test_user')
# Попробовать сменить пароль
try:
user.set_password('TestPass123!')
print("✅ Пароль успешно изменён")
except ValueError as e:
print(f"❌ Ошибка валидации: {e}")
except Exception as e:
print(f"❌ Ошибка LDAP: {e}")
```
## История изменений
**23.03.2026:**
- ✅ Добавлено поле `unicode_pwd` в `LdapUser`
- ✅ Добавлены методы `set_password()` и `change_password()`
- ✅ Добавлен action "🔑 Изменить пароль" в список пользователей
- ✅ **Добавлена кнопка "🔑 Изменить пароль" в карточку пользователя** ⭐
- ✅ Создан шаблон `admin/ldap_change_password.html` для массовой смены
- ✅ Создан шаблон `admin/ldap_user_password_change.html` для индивидуальной смены
- ✅ Автоматическая синхронизация с Django Employee при смене пароля