feat: add self-modification protection for user roles
Protect owners from accidentally locking themselves out by: - Adding RoleService.can_modify_user_role() to centralize validation logic - Blocking edit/delete operations on own role in views - Hiding edit/delete buttons for own role in template This prevents owners from: - Changing their own role to a lower privilege level - Deactivating themselves - Deleting their own access Standard admin pattern used by GitHub, WordPress, Django Admin. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -98,3 +98,25 @@ class RoleService:
|
|||||||
if not user_role:
|
if not user_role:
|
||||||
return False
|
return False
|
||||||
return user_role.code in role_codes
|
return user_role.code in role_codes
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def can_modify_user_role(modifier_user, target_user_role):
|
||||||
|
"""
|
||||||
|
Проверяет, может ли пользователь изменить указанную роль.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
modifier_user: Пользователь, который хочет изменить роль
|
||||||
|
target_user_role: UserRole объект, который нужно изменить
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (bool, str) - (можно ли изменить, причина отказа)
|
||||||
|
"""
|
||||||
|
# Защита от самоблокировки
|
||||||
|
if target_user_role.user == modifier_user:
|
||||||
|
return False, "Вы не можете изменить свою собственную роль"
|
||||||
|
|
||||||
|
# Можно добавить другие проверки в будущем:
|
||||||
|
# - Запрет удаления последнего owner'а
|
||||||
|
# - Проверка иерархии ролей и т.д.
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|||||||
@@ -67,12 +67,18 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'user_roles:edit' user_role.pk %}" class="btn btn-sm btn-outline-primary">
|
{% if user_role.user != request.user %}
|
||||||
<i class="bi bi-pencil"></i> Изменить
|
<a href="{% url 'user_roles:edit' user_role.pk %}" class="btn btn-sm btn-outline-primary">
|
||||||
</a>
|
<i class="bi bi-pencil"></i> Изменить
|
||||||
<a href="{% url 'user_roles:delete' user_role.pk %}" class="btn btn-sm btn-outline-danger">
|
</a>
|
||||||
<i class="bi bi-trash"></i> Удалить
|
<a href="{% url 'user_roles:delete' user_role.pk %}" class="btn btn-sm btn-outline-danger">
|
||||||
</a>
|
<i class="bi bi-trash"></i> Удалить
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-muted small">
|
||||||
|
<i class="bi bi-lock"></i> Ваша роль
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ def user_role_edit(request, pk):
|
|||||||
"""Изменение роли пользователя"""
|
"""Изменение роли пользователя"""
|
||||||
user_role = get_object_or_404(UserRole, pk=pk)
|
user_role = get_object_or_404(UserRole, pk=pk)
|
||||||
|
|
||||||
|
# Защита от самоблокировки
|
||||||
|
can_modify, error_message = RoleService.can_modify_user_role(request.user, user_role)
|
||||||
|
if not can_modify:
|
||||||
|
messages.error(request, error_message)
|
||||||
|
return redirect('user_roles:list')
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
role_code = request.POST.get('role')
|
role_code = request.POST.get('role')
|
||||||
is_active = request.POST.get('is_active') == 'on'
|
is_active = request.POST.get('is_active') == 'on'
|
||||||
@@ -98,6 +104,12 @@ def user_role_delete(request, pk):
|
|||||||
"""Удаление роли пользователя (отключение доступа)"""
|
"""Удаление роли пользователя (отключение доступа)"""
|
||||||
user_role = get_object_or_404(UserRole, pk=pk)
|
user_role = get_object_or_404(UserRole, pk=pk)
|
||||||
|
|
||||||
|
# Защита от самоблокировки
|
||||||
|
can_modify, error_message = RoleService.can_modify_user_role(request.user, user_role)
|
||||||
|
if not can_modify:
|
||||||
|
messages.error(request, error_message)
|
||||||
|
return redirect('user_roles:list')
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
email = user_role.user.email
|
email = user_role.user.email
|
||||||
user_role.delete()
|
user_role.delete()
|
||||||
|
|||||||
Reference in New Issue
Block a user