diff --git a/.gitignore b/.gitignore index c3df1e740..cdc60680c 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ test_project/envfile # Docker docker/collections +docker/redis diff --git a/docker-compose.yml b/docker-compose.yml index 287d7ef01..047f7514d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,11 @@ services: redis: - image: redis:5-alpine + image: redis:7-alpine ports: - 6379:6379 + command: redis-server --save 60 1 --loglevel warning + volumes: + - ./docker/redis:/data rq: build: @@ -94,7 +97,7 @@ services: dovecot: container_name: modoboa_dovecot restart: unless-stopped - image: dovecot/dovecot:latest + image: dovecot/dovecot:2.3-latest depends_on: - api volumes: diff --git a/modoboa/webmail/lib/attachments.py b/modoboa/webmail/lib/attachments.py index 1ddedeb22..53ca33e93 100644 --- a/modoboa/webmail/lib/attachments.py +++ b/modoboa/webmail/lib/attachments.py @@ -24,9 +24,8 @@ from .rfc6266 import build_header class ComposeSessionManager: - def __init__(self, request): - self.request = request - self.hash = f"webmail-{request.user.username}" + def __init__(self, username: str): + self.hash = f"webmail-{username}" self.rclient = get_redis_connection(bytes) def create(self) -> str: @@ -75,7 +74,7 @@ def save_attachment(request, session_uid: str, f) -> dict: :param f: an uploaded file object (see Django's documentation) or bytes :return: the new random name """ - manager = ComposeSessionManager(request) + manager = ComposeSessionManager(request.user.username) if not manager.exists(session_uid): raise Http404 try: @@ -102,7 +101,7 @@ def save_attachment(request, session_uid: str, f) -> dict: def remove_attachment(request, session_uid: str, name: str) -> Optional[str]: - manager = ComposeSessionManager(request) + manager = ComposeSessionManager(request.user.username) session = manager.get_content(session_uid) for att in session["attachments"]: if att["tmpname"] == name: diff --git a/modoboa/webmail/tests/test_viewsets.py b/modoboa/webmail/tests/test_viewsets.py index a624504a9..74a9d4c47 100644 --- a/modoboa/webmail/tests/test_viewsets.py +++ b/modoboa/webmail/tests/test_viewsets.py @@ -5,6 +5,7 @@ import shutil import tempfile from unittest import mock +from django.core import mail from django.urls import reverse from django.utils import timezone @@ -13,6 +14,7 @@ from oauth2_provider.models import get_access_token_model, get_application_model from modoboa.core import models as core_models from modoboa.admin import factories as admin_factories from modoboa.lib.tests import ModoAPITestCase +from modoboa.webmail.lib.attachments import ComposeSessionManager from . import data as tests_data @@ -133,7 +135,7 @@ class WebmailTestCase(ModoAPITestCase): """Cleanup.""" shutil.rmtree(self.workdir) - def authenticate(self, username: str = "user@test.com"): + def authenticate(self): self.client.credentials(Authorization="Bearer " + self.access_token.token) @@ -232,12 +234,16 @@ class UserEmailViewSetTestCase(WebmailTestCase): class ComposeSessionViewSetTestCase(WebmailTestCase): - def test_create(self): - self.authenticate() + def _create_compose_session(self): url = reverse("v2:webmail-compose-session-list") response = self.client.post(url) self.assertEqual(response.status_code, 201) self.assertIn("uid", response.json()) + return response.json()["uid"] + + def test_create(self): + self.authenticate() + self._create_compose_session() def test_get_allowed_senders(self): self.authenticate() @@ -248,9 +254,83 @@ class ComposeSessionViewSetTestCase(WebmailTestCase): def test_attachments(self): self.authenticate() - url = reverse("v2:webmail-compose-session-list") - response = self.client.post(url) - uid = response.json()["uid"] + uid = self._create_compose_session() url = reverse("v2:webmail-compose-session-attachments", args=[uid]) response = self.client.get(url) self.assertEqual(response.status_code, 200) + + manager = ComposeSessionManager(self.user.username) + # self.set_global_parameters({"max_attachment_size": "10"}) + # with self.settings(MEDIA_ROOT=self.workdir): + # response = self.client.post(url, {"attachment": get_gif()}) + # self.assertEqual(response.status_code, 400) + # self.assertContains(response, "Attachment is too big") + + self.set_global_parameters({"max_attachment_size": "10K"}) + with self.settings(MEDIA_ROOT=self.workdir): + response = self.client.post(url, {"attachment": get_gif()}) + self.assertEqual(response.status_code, 200) + content = manager.get_content(uid) + self.assertEqual(len(content["attachments"]), 1) + name = content["attachments"][0]["tmpname"] + path = f"{self.workdir}/webmail/{name}" + self.assertTrue(os.path.exists(path)) + + url = reverse("v2:webmail-compose-session-delete-attachment", args=[uid, name]) + with self.settings(MEDIA_ROOT=self.workdir): + response = self.client.delete(url) + self.assertEqual(response.status_code, 204) + self.assertFalse(os.path.exists(path)) + + def test_delete_attachment(self): + self.authenticate() + uid = self._create_compose_session() + + url = reverse( + "v2:webmail-compose-session-delete-attachment", args=[uid, "unknown"] + ) + with self.settings(MEDIA_ROOT=self.workdir): + response = self.client.delete(url) + self.assertEqual(response.status_code, 404) + + def test_send(self): + self.authenticate() + uid = self._create_compose_session() + + url = reverse("v2:webmail-compose-session-send", args=[uid]) + response = self.client.post( + url, + { + "sender": self.user.email, + "to": ["test@example.test"], + "subject": "test", + "body": "Test", + }, + format="json", + ) + self.assertEqual(response.status_code, 204) + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, "user@test.com") + + # Try to send an email using HTML format + uid = self._create_compose_session() + url = reverse("v2:webmail-compose-session-send", args=[uid]) + + self.user.first_name = "Antoine" + self.user.last_name = "Nguyen" + self.user.parameters.set_value("editor", "html") + self.user.save() + mail.outbox = [] + response = self.client.post( + url, + { + "sender": self.user.email, + "to": ["test@example.test"], + "subject": "test", + "body": "

Test

", + }, + format="json", + ) + self.assertEqual(response.status_code, 204) + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].from_email, '"Antoine Nguyen" ') diff --git a/modoboa/webmail/viewsets.py b/modoboa/webmail/viewsets.py index 17a2f2e0a..2f58466a5 100644 --- a/modoboa/webmail/viewsets.py +++ b/modoboa/webmail/viewsets.py @@ -276,7 +276,7 @@ class ComposeSessionViewSet(viewsets.GenericViewSet): return serializers.ComposeSessionSerializer def create(self, request): - uid = attachments.ComposeSessionManager(request).create() + uid = attachments.ComposeSessionManager(request.user.username).create() serializer = self.get_serializer({"uid": uid}) return response.Response(serializer.data, status=201) @@ -303,6 +303,7 @@ class ComposeSessionViewSet(viewsets.GenericViewSet): ) def attachments(self, request, pk): if request.method == "POST": + manager = attachments.ComposeSessionManager(request.user.username) uploader = attachments.AttachmentUploadHandler() request.upload_handlers.insert(0, uploader) serializer = serializers.AttachmentUploadSerializer(data=request.FILES) @@ -311,7 +312,7 @@ class ComposeSessionViewSet(viewsets.GenericViewSet): request, pk, serializer.validated_data["attachment"] ) return response.Response(result) - manager = attachments.ComposeSessionManager(request) + manager = attachments.ComposeSessionManager(request.user.username) session = manager.get_content(pk) serializer = serializers.UploadedAttachmentSerializer( session["attachments"], many=True @@ -330,7 +331,7 @@ class ComposeSessionViewSet(viewsets.GenericViewSet): """Send an email based on the given compose session.""" serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - manager = attachments.ComposeSessionManager(request) + manager = attachments.ComposeSessionManager(request.user.username) status, error = lib.send_mail( request, serializer.validated_data, manager.get_content(pk)["attachments"] )