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