From f2c1f7e02d9ef7351482d4d4dcc94e142928100f Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Mon, 1 Dec 2025 23:06:54 +0300 Subject: [PATCH] feat: add self-modification protection for user roles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- myproject/user_roles/services.py | 22 +++++++++++++++++++ .../templates/user_roles/user_role_list.html | 18 ++++++++++----- myproject/user_roles/views.py | 12 ++++++++++ 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/myproject/user_roles/services.py b/myproject/user_roles/services.py index 3f77b23..014a6ec 100644 --- a/myproject/user_roles/services.py +++ b/myproject/user_roles/services.py @@ -98,3 +98,25 @@ class RoleService: if not user_role: return False 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, "" diff --git a/myproject/user_roles/templates/user_roles/user_role_list.html b/myproject/user_roles/templates/user_roles/user_role_list.html index 710f8c6..8ce4671 100644 --- a/myproject/user_roles/templates/user_roles/user_role_list.html +++ b/myproject/user_roles/templates/user_roles/user_role_list.html @@ -67,12 +67,18 @@ {% endif %} - - Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ - - - Π£Π΄Π°Π»ΠΈΡ‚ΡŒ - + {% if user_role.user != request.user %} + + Π˜Π·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ + + + Π£Π΄Π°Π»ΠΈΡ‚ΡŒ + + {% else %} + + Π’Π°ΡˆΠ° Ρ€ΠΎΠ»ΡŒ + + {% endif %} {% endfor %} diff --git a/myproject/user_roles/views.py b/myproject/user_roles/views.py index 28148fc..4759006 100644 --- a/myproject/user_roles/views.py +++ b/myproject/user_roles/views.py @@ -64,6 +64,12 @@ def user_role_edit(request, 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': role_code = request.POST.get('role') 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) + # Π—Π°Ρ‰ΠΈΡ‚Π° ΠΎΡ‚ самоблокировки + 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': email = user_role.user.email user_role.delete()