From e0a4e3b9ee8ad84dba46e4c0f04cbbe7cf78c38d Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 26 Feb 2026 18:15:30 +0100 Subject: [PATCH] Fixed several security issues. see #3945 --- modoboa/admin/api/v2/viewsets.py | 2 +- modoboa/autoreply/api/v2/serializers.py | 8 +++++++ modoboa/calendars/tests.py | 28 ++++++------------------- modoboa/calendars/viewsets.py | 12 +++++++---- modoboa/maillog/api/v2/viewsets.py | 2 +- modoboa/transport/api/v2/viewsets.py | 2 +- 6 files changed, 25 insertions(+), 29 deletions(-) diff --git a/modoboa/admin/api/v2/viewsets.py b/modoboa/admin/api/v2/viewsets.py index 57a2df889..bcf4f49cf 100644 --- a/modoboa/admin/api/v2/viewsets.py +++ b/modoboa/admin/api/v2/viewsets.py @@ -440,5 +440,5 @@ class AlarmViewSet( """Delete multiple alarms at the same time.""" serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - models.Alarm.objects.filter(pk__in=serializer.validated_data["ids"]).delete() + self.get_queryset().filter(pk__in=serializer.validated_data["ids"]).delete() return response.Response(status=204) diff --git a/modoboa/autoreply/api/v2/serializers.py b/modoboa/autoreply/api/v2/serializers.py index e2aeab258..8d239f37b 100644 --- a/modoboa/autoreply/api/v2/serializers.py +++ b/modoboa/autoreply/api/v2/serializers.py @@ -35,6 +35,14 @@ class ARMessageSerializer(serializers.ModelSerializer): model = models.ARmessage fields = "__all__" + def validate_mbox(self, value): + user = self.context["request"].user + if user.mailbox != value: + raise serializers.ValidationError( + _("You don't have ownership on this mailbox") + ) + return value + def validate(self, data): """Check dates.""" if not data.get("fromdate"): diff --git a/modoboa/calendars/tests.py b/modoboa/calendars/tests.py index 5fc660222..250c73fd7 100644 --- a/modoboa/calendars/tests.py +++ b/modoboa/calendars/tests.py @@ -389,9 +389,6 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): def test_get_user_events(self): """Test event(s) retrieval.""" - data = {"username": "user@test.com", "password": "toto"} - self.client.post(reverse("core:login"), data) - # FIXME: drf nested routers does not handle reverse() properly url = f"/api/v2/user-calendars/{self.calendar.pk}/events/{1234}/" response = self.client.get(url) @@ -404,11 +401,14 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): self.assertEqual(response.status_code, 200) self.assertEqual(len(response.json()), 2) + def test_get_user_events_wrong_calendar(self): + url = f"/api/v2/user-calendars/{self.calendar2.pk}/events/" + url = "{}?start={}&end={}".format(url, "20060712T182145Z", "20070712T182145Z") + response = self.client.get(url) + self.assertEqual(response.status_code, 404) + def test_get_shared_events(self): """Test event(s) retrieval.""" - data = {"username": "user@test.com", "password": "toto"} - self.client.post(reverse("core:login"), data) - # FIXME: drf nested routers does not handle reverse() properly url = f"/api/v2/shared-calendars/{self.scalendar.pk}/events/{1234}/" response = self.client.get(url) @@ -423,9 +423,6 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): def test_create_event(self): """Test event creation.""" - data = {"username": "user@test.com", "password": "toto"} - self.client.post(reverse("core:login"), data) - url = f"/api/v2/user-calendars/{self.calendar.pk}/events/" data = { "title": "Test event", @@ -456,9 +453,6 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): def test_update_event(self): """Test event update.""" - data = {"username": "user@test.com", "password": "toto"} - self.client.post(reverse("core:login"), data) - url = f"/api/v2/user-calendars/{self.calendar.pk}/events/1234/" data = { "title": "Test event", @@ -479,9 +473,6 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): def test_patch_event(self): """Test event partial update.""" - data = {"username": "user@test.com", "password": "toto"} - self.client.post(reverse("core:login"), data) - url = f"/api/v2/user-calendars/{self.calendar.pk}/events/1234/" data = { "start_date": "2018-03-27", @@ -493,8 +484,6 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): def test_move_event_between_cals(self): """Move an event.""" - data = {"username": "user@test.com", "password": "toto"} - self.client.post(reverse("core:login"), data) data = { "title": "Test event", "start_date": "2018-03-27", @@ -511,17 +500,12 @@ class EventViewSetTestCase(TestDataMixin, ModoAPITestCase): def test_delete_event(self): """Test event deletion.""" - data = {"username": "user@test.com", "password": "toto"} - self.client.post(reverse("core:login"), data) - url = f"/api/v2/shared-calendars/{self.scalendar.pk}/events/1234/" response = self.client.delete(url) self.assertEqual(response.status_code, 200) def test_import_from_file(self): """Check import feature.""" - data = {"username": "user@test.com", "password": "toto"} - self.client.post(reverse("core:login"), data) url = reverse("api:user-event-import-from-file", args=[self.calendar.pk]) path = os.path.join( os.path.abspath(os.path.dirname(__file__)), "test_data/events.ics" diff --git a/modoboa/calendars/viewsets.py b/modoboa/calendars/viewsets.py index fd89c31de..3945ed345 100644 --- a/modoboa/calendars/viewsets.py +++ b/modoboa/calendars/viewsets.py @@ -21,9 +21,7 @@ from . import serializers def parse_date_from_iso(value): """Return a tz aware datetime parsed from an ISO date.""" - result = dateutil.parser.parse(value) - print(result) - return result + return dateutil.parser.parse(value) class CheckTokenMixin: @@ -91,6 +89,7 @@ class BaseEventViewSet(viewsets.ViewSet): """Event viewset.""" lookup_value_regex = r"[0-9a-zA-Z\-\.@]+" + permission_classes = (permissions.IsAuthenticated,) def get_serializer(self, data=None, **kwargs): args = [] @@ -212,7 +211,12 @@ class UserEventViewSet(BaseEventViewSet): def get_calendar(self, pk): """Return UserCalendar instance.""" - return models.UserCalendar.objects.get(pk=pk) + calendar = models.UserCalendar.objects.filter( + mailbox__user=self.request.user, pk=pk + ).first() + if not calendar: + raise http.Http404 + return calendar class SharedEventViewSet(BaseEventViewSet): diff --git a/modoboa/maillog/api/v2/viewsets.py b/modoboa/maillog/api/v2/viewsets.py index 34b51999d..dee21438d 100644 --- a/modoboa/maillog/api/v2/viewsets.py +++ b/modoboa/maillog/api/v2/viewsets.py @@ -55,7 +55,7 @@ class MaillogViewSet(GetThrottleViewsetMixin, viewsets.ReadOnlyModelViewSet): ordering = ["-date"] ordering_fields = "__all__" pagination_class = pagination.CustomPageNumberPagination - permissions = (permissions.IsAuthenticated,) + permission_classes = (permissions.IsAuthenticated,) search_fields = ["queue_id", "sender", "rcpt", "original_rcpt", "status"] serializer_class = serializers.MaillogSerializer diff --git a/modoboa/transport/api/v2/viewsets.py b/modoboa/transport/api/v2/viewsets.py index c8faebe60..345c1e972 100644 --- a/modoboa/transport/api/v2/viewsets.py +++ b/modoboa/transport/api/v2/viewsets.py @@ -12,7 +12,7 @@ from ... import backends class TransportViewSet(GetThrottleViewsetMixin, viewsets.ViewSet): """Viewset for Transport.""" - permissions = (permissions.IsAuthenticated,) + permission_classes = (permissions.IsAuthenticated,) @extend_schema(responses={200: serializers.TransportBackendSerializer}) def list(self, request, *args, **kwargs):