mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-05-11 09:05:26 +00:00
Merge branch 'release/v2.20.x'
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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
@@ -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",
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user