From 276dc31abea03bbeb1c8a723520f2445135f4968 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 24 Oct 2025 09:42:05 -0700 Subject: [PATCH 1/2] Fix: add missing import of ConfirmButtonComponent in user-edit-dialog (#11167) --- src-ui/messages.xlf | 10 +++++----- .../user-edit-dialog/user-edit-dialog.component.ts | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index 8eb325858..f466c2c09 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -4539,32 +4539,32 @@ Create new user account src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts - 70 + 72 Edit user account src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts - 74 + 76 Totp deactivated src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts - 130 + 132 Totp deactivation failed src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts - 133 + 135 src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts - 138 + 140 diff --git a/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts b/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts index 86e60151b..1c87a4308 100644 --- a/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts +++ b/src-ui/src/app/components/common/edit-dialog/user-edit-dialog/user-edit-dialog.component.ts @@ -14,6 +14,7 @@ import { GroupService } from 'src/app/services/rest/group.service' import { UserService } from 'src/app/services/rest/user.service' import { SettingsService } from 'src/app/services/settings.service' import { ToastService } from 'src/app/services/toast.service' +import { ConfirmButtonComponent } from '../../confirm-button/confirm-button.component' import { PasswordComponent } from '../../input/password/password.component' import { SelectComponent } from '../../input/select/select.component' import { TextComponent } from '../../input/text/text.component' @@ -28,6 +29,7 @@ import { PermissionsSelectComponent } from '../../permissions-select/permissions SelectComponent, TextComponent, PasswordComponent, + ConfirmButtonComponent, FormsModule, ReactiveFormsModule, ], From 63dab0ab09448e84efed3c469d7a08f5b3b80c05 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Fri, 24 Oct 2025 16:25:59 -0700 Subject: [PATCH 2/2] Change: restrict superuser modifications to superusers only --- src/documents/tests/test_admin.py | 35 +++++++++++++++++++++++++++++++ src/paperless/views.py | 4 ++++ 2 files changed, 39 insertions(+) diff --git a/src/documents/tests/test_admin.py b/src/documents/tests/test_admin.py index 278014f7c..61a579dc7 100644 --- a/src/documents/tests/test_admin.py +++ b/src/documents/tests/test_admin.py @@ -2,9 +2,11 @@ import types from unittest.mock import patch from django.contrib.admin.sites import AdminSite +from django.contrib.auth.models import Permission from django.contrib.auth.models import User from django.test import TestCase from django.utils import timezone +from rest_framework import status from documents import index from documents.admin import DocumentAdmin @@ -125,3 +127,36 @@ class TestPaperlessAdmin(DirectoriesMixin, TestCase): form.request = types.SimpleNamespace(user=superuser) self.assertTrue(form.is_valid()) self.assertEqual({}, form.errors) + + def test_superuser_can_only_be_modified_by_superuser(self): + superuser = User.objects.create_superuser(username="superuser", password="test") + user = User.objects.create( + username="test", + is_superuser=False, + is_staff=True, + ) + change_user_perm = Permission.objects.get(codename="change_user") + user.user_permissions.add(change_user_perm) + + self.client.force_login(user) + response = self.client.patch( + f"/api/users/{superuser.pk}/", + {"first_name": "Updated"}, + content_type="application/json", + ) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + self.assertEqual( + response.content.decode(), + "Superusers can only be modified by other superusers", + ) + + self.client.logout() + self.client.force_login(superuser) + response = self.client.patch( + f"/api/users/{superuser.pk}/", + {"first_name": "Updated"}, + content_type="application/json", + ) + self.assertEqual(response.status_code, status.HTTP_200_OK) + superuser.refresh_from_db() + self.assertEqual(superuser.first_name, "Updated") diff --git a/src/paperless/views.py b/src/paperless/views.py index fc5bb5463..69375e1bc 100644 --- a/src/paperless/views.py +++ b/src/paperless/views.py @@ -125,6 +125,10 @@ class UserViewSet(ModelViewSet): def update(self, request, *args, **kwargs): user_to_update: User = self.get_object() + if not request.user.is_superuser and user_to_update.is_superuser: + return HttpResponseForbidden( + "Superusers can only be modified by other superusers", + ) if ( not request.user.is_superuser and request.data.get("is_superuser") is not None