Merge branch 'release/v2.20.x'

This commit is contained in:
shamoon
2026-04-26 19:00:38 -07:00
6 changed files with 132 additions and 5 deletions
+25
View File
@@ -160,3 +160,28 @@ class TestPaperlessAdmin(DirectoriesMixin, TestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
superuser.refresh_from_db()
self.assertEqual(superuser.first_name, "Updated")
def test_superuser_can_only_be_deleted_by_superuser(self):
superuser = User.objects.create_superuser(username="superuser", password="test")
user = User.objects.create(
username="test",
is_superuser=False,
is_staff=True,
)
delete_user_perm = Permission.objects.get(codename="delete_user")
user.user_permissions.add(delete_user_perm)
self.client.force_login(user)
response = self.client.delete(f"/api/users/{superuser.pk}/")
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(
response.content.decode(),
"Superusers can only be deleted by other superusers",
)
self.assertTrue(User.objects.filter(pk=superuser.pk).exists())
self.client.logout()
self.client.force_login(superuser)
response = self.client.delete(f"/api/users/{superuser.pk}/")
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertFalse(User.objects.filter(pk=superuser.pk).exists())
+48
View File
@@ -0,0 +1,48 @@
import uuid
from django.contrib.auth.models import User
from django.test import TestCase
from django.test import override_settings
from django.urls import resolve
from django.urls import reverse
from rest_framework import status
class TestApiAuthViews(TestCase):
def test_api_auth_login_uses_allauth_login_view(self):
response = self.client.get(reverse("rest_framework:login"))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTemplateUsed(response, "account/login.html")
def test_api_auth_login_uses_same_view_as_account_login(self):
api_match = resolve("/api/auth/login/")
account_match = resolve("/accounts/login/")
self.assertIs(api_match.func.view_class, account_match.func.view_class)
@override_settings(DISABLE_REGULAR_LOGIN=True)
def test_api_auth_login_respects_disable_regular_login(self):
username = f"testuser-{uuid.uuid4().hex}"
User.objects.create_user(
username=username,
password="testpassword",
)
response = self.client.post(
reverse("rest_framework:login"),
data={
"login": username,
"password": "testpassword",
"next": "/api/documents/",
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTemplateUsed(response, "account/login.html")
self.assertContains(response, "Regular login is disabled")
self.assertNotIn("_auth_user_id", self.client.session)
def test_api_auth_logout_uses_named_route(self):
self.assertEqual(reverse("rest_framework:login"), "/api/auth/login/")
self.assertEqual(reverse("rest_framework:logout"), "/api/auth/logout/")
+15 -1
View File
@@ -89,7 +89,21 @@ urlpatterns = [
re_path(
"^auth/",
include(
("rest_framework.urls", "rest_framework"),
(
[
path(
"login/",
allauth_account_views.login,
name="login",
),
path(
"logout/",
allauth_account_views.logout,
name="logout",
),
],
"rest_framework",
),
namespace="rest_framework",
),
),
+10
View File
@@ -180,6 +180,16 @@ class UserViewSet(ModelViewSet):
)
return super().update(request, *args, **kwargs)
def destroy(self, request, *args, **kwargs):
user_to_delete: User = self.get_object()
if not request.user.is_superuser and user_to_delete.is_superuser:
return HttpResponseForbidden(
"Superusers can only be deleted by other superusers",
)
return super().destroy(request, *args, **kwargs)
@extend_schema(
request=None,
responses={
+13 -1
View File
@@ -2,6 +2,7 @@ from django.utils.translation import gettext as _
from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied
from documents.permissions import get_objects_for_user_owner_aware
from documents.permissions import has_perms_owner_aware
from documents.serialisers import CorrespondentField
from documents.serialisers import DocumentTypeField
@@ -59,7 +60,18 @@ class MailAccountSerializer(OwnedObjectSerializer):
class AccountField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return MailAccount.objects.all().order_by("-id")
user = getattr(self.context.get("request"), "user", None)
if user is None:
user = getattr(self.root, "user", None)
if user is None:
return MailAccount.objects.none()
return get_objects_for_user_owner_aware(
user,
"change_mailaccount",
MailAccount,
).order_by("-id")
class MailRuleSerializer(OwnedObjectSerializer):
+21 -3
View File
@@ -632,7 +632,7 @@ class TestAPIMailRules(DirectoriesMixin, APITestCase):
self.assertEqual(returned_rule1.name, "Updated Name 1")
self.assertEqual(returned_rule1.action, MailRule.MailAction.DELETE)
def test_create_mail_rule_forbidden_for_unpermitted_account(self):
def test_create_mail_rule_scopes_accounts(self):
other_user = User.objects.create_user(username="mail-owner")
foreign_account = MailAccount.objects.create(
name="ForeignEmail",
@@ -660,8 +660,26 @@ class TestAPIMailRules(DirectoriesMixin, APITestCase):
"attachment_type": MailRule.AttachmentProcessing.ATTACHMENTS_ONLY,
},
)
missing_response = self.client.post(
self.ENDPOINT,
data={
"name": "Rule1",
"account": foreign_account.pk + 1000,
"folder": "INBOX",
"filter_from": "from@example.com",
"maximum_age": 30,
"action": MailRule.MailAction.MARK_READ,
"assign_title_from": MailRule.TitleSource.FROM_SUBJECT,
"assign_correspondent_from": MailRule.CorrespondentSource.FROM_NOTHING,
"order": 0,
"attachment_type": MailRule.AttachmentProcessing.ATTACHMENTS_ONLY,
},
)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(missing_response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data["account"][0].code, "does_not_exist")
self.assertEqual(missing_response.data["account"][0].code, "does_not_exist")
self.assertEqual(MailRule.objects.count(), 0)
def test_create_mail_rule_allowed_for_granted_account_change_permission(self):
@@ -736,7 +754,7 @@ class TestAPIMailRules(DirectoriesMixin, APITestCase):
data={"account": foreign_account.pk},
)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
rule1.refresh_from_db()
self.assertEqual(rule1.account, own_account)