From b5ddc00d35441bc32a1c5c863fd8603e17791080 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 16 Sep 2025 17:00:48 +0200 Subject: [PATCH 1/6] Autoconfiguration service --- LICENSE | 2 +- modoboa/autoconfig/__init__.py | 0 modoboa/autoconfig/apps.py | 6 ++ .../templates/autoconfig/autoconfig.xml | 24 ++++++ .../templates/autoconfig/autodiscover.xml | 27 ++++++ .../templates/autoconfig/mobileconfig.xml | 37 ++++++++ modoboa/autoconfig/tests.py | 38 ++++++++ modoboa/autoconfig/urls.py | 21 +++++ modoboa/autoconfig/views.py | 86 +++++++++++++++++++ .../core/commands/templates/settings.py.tpl | 14 +++ modoboa/urls.py | 1 + test_project/test_project/settings.py | 11 +++ 12 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 modoboa/autoconfig/__init__.py create mode 100644 modoboa/autoconfig/apps.py create mode 100644 modoboa/autoconfig/templates/autoconfig/autoconfig.xml create mode 100644 modoboa/autoconfig/templates/autoconfig/autodiscover.xml create mode 100644 modoboa/autoconfig/templates/autoconfig/mobileconfig.xml create mode 100644 modoboa/autoconfig/tests.py create mode 100644 modoboa/autoconfig/urls.py create mode 100644 modoboa/autoconfig/views.py diff --git a/LICENSE b/LICENSE index a00625545..de4bc284b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ ISC License -Copyright (c) 2009-2023, Antoine Nguyen +Copyright (c) 2009-2025, Antoine Nguyen Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/modoboa/autoconfig/__init__.py b/modoboa/autoconfig/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/modoboa/autoconfig/apps.py b/modoboa/autoconfig/apps.py new file mode 100644 index 000000000..c4d33123c --- /dev/null +++ b/modoboa/autoconfig/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AutoconfigConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "modoboa.autoconfig" diff --git a/modoboa/autoconfig/templates/autoconfig/autoconfig.xml b/modoboa/autoconfig/templates/autoconfig/autoconfig.xml new file mode 100644 index 000000000..e8af4ac61 --- /dev/null +++ b/modoboa/autoconfig/templates/autoconfig/autoconfig.xml @@ -0,0 +1,24 @@ + + + + {{ domain }} + Modoboa + Mail + + + {{ connection_settings.imap.HOSTNAME }} + {{ connection_settings.imap.PORT }} + {{ connection_settings.imap.SOCKET_TYPE }} + password-cleartext + {{ emailaddress }} + + + + {{ connection_settings.smtp.HOSTNAME }} + {{ connection_settings.smtp.PORT }} + {{ connection_settings.smtp.SOCKET_TYPE }} + password-cleartext + {{ emailaddress }} + + + diff --git a/modoboa/autoconfig/templates/autoconfig/autodiscover.xml b/modoboa/autoconfig/templates/autoconfig/autodiscover.xml new file mode 100644 index 000000000..feb6cd61e --- /dev/null +++ b/modoboa/autoconfig/templates/autoconfig/autodiscover.xml @@ -0,0 +1,27 @@ + + + + + email + settings + + + IMAP + {{ connection_settings.imap.HOSTNAME }} + {{ connection_settings.imap.PORT }} + true + {{ emailaddress }} + + + + SMTP + {{ connection_settings.smtp.HOSTNAME }} + {{ connection_settings.smtp.PORT }} + false + true + {{ emailaddress }} + + + + + diff --git a/modoboa/autoconfig/templates/autoconfig/mobileconfig.xml b/modoboa/autoconfig/templates/autoconfig/mobileconfig.xml new file mode 100644 index 000000000..832efe86d --- /dev/null +++ b/modoboa/autoconfig/templates/autoconfig/mobileconfig.xml @@ -0,0 +1,37 @@ + + + + + + + com.apple.mail.managed + 1 + {{ reverse_domain }}.email + {{ content_uuid }} + Mail + + Modoboa + EmailTypeIMAP + {{ emailaddress }} + {{ connection_settings.imap.HOSTNAME }} + {{ connection_settings.imap.PORT }} + true + {{ emailaddress }} + + {{ connection_settings.smtp.HOSTNAME }} + {{ connection_settings.smtp.PORT }} + true + {{ emailaddress }} + true + + + + Modoboa + Mail Configuration + {{ reverse_domain }}.mailconfig + {{ global_uuid }} + 1 + Configuration + + diff --git a/modoboa/autoconfig/tests.py b/modoboa/autoconfig/tests.py new file mode 100644 index 000000000..bec5b0c69 --- /dev/null +++ b/modoboa/autoconfig/tests.py @@ -0,0 +1,38 @@ +from django.test import TestCase +from django.urls import reverse + + +class ViewsTestCase(TestCase): + + def test_autoconfig(self): + url = reverse("autoconfig:autoconfig") + + resp = self.client.get(url) + self.assertEqual(resp.status_code, 404) + + resp = self.client.get(f"{url}?emailaddress=test@test.com") + self.assertEqual(resp.status_code, 200) + + self.assertIn(b'', resp.content) + + def test_autodiscover(self): + url = reverse("autoconfig:autodiscover") + + resp = self.client.post(url) + self.assertEqual(resp.status_code, 404) + + resp = self.client.post(url, {"EmailAddress": "test@test.com"}) + self.assertEqual(resp.status_code, 200) + + self.assertIn(b"test@test.com", resp.content) + + def test_mobileconfig(self): + url = reverse("autoconfig:mobileconfig") + + resp = self.client.get(url) + self.assertEqual(resp.status_code, 404) + + resp = self.client.get(f"{url}?email=test@test.com") + self.assertEqual(resp.status_code, 200) + + self.assertIn(b"test@test.com", resp.content) diff --git a/modoboa/autoconfig/urls.py b/modoboa/autoconfig/urls.py new file mode 100644 index 000000000..227fbf2fd --- /dev/null +++ b/modoboa/autoconfig/urls.py @@ -0,0 +1,21 @@ +"""Autoconfig urls.""" + +from django.urls import path + +from modoboa.autoconfig import views + +app_name = "autoconfig" + +urlpatterns = [ + path( + ".well-known/autoconfig/mail/config-v1.1.xml", + views.AutoConfigView.as_view(), + name="autoconfig", + ), + path( + "autodiscover/autodiscover.xml", + views.AutoDiscoverView.as_view(), + name="autodiscover", + ), + path("mobileconfig", views.MobileConfigView.as_view(), name="mobileconfig"), +] diff --git a/modoboa/autoconfig/views.py b/modoboa/autoconfig/views.py new file mode 100644 index 000000000..112a94d57 --- /dev/null +++ b/modoboa/autoconfig/views.py @@ -0,0 +1,86 @@ +import uuid + +from django.conf import settings +from django.http import Http404, HttpResponse +from django.template.loader import render_to_string +from django.utils.decorators import method_decorator +from django.views import generic +from django.views.decorators.csrf import csrf_exempt + +from modoboa.lib.email_utils import split_address + + +class ConfigBaseMixin: + + content_type = "application/xml" + + def get_common_context(self, emailaddress: str) -> dict: + local_part, domain = split_address(emailaddress) + return { + "emailaddress": emailaddress, + "domain": domain, + "connection_settings": settings.EMAIL_CLIENT_CONNECTION_SETTINGS, + } + + +class AutoConfigView(ConfigBaseMixin, generic.TemplateView): + + http_method_names = ["get"] + template_name = "autoconfig/autoconfig.xml" + + def get_context_data(self, **kwargs): + emailaddress = self.request.GET.get("emailaddress") + if not emailaddress: + raise Http404 + context = super().get_context_data(**kwargs) + context.update(self.get_common_context(emailaddress)) + return context + + +@method_decorator(csrf_exempt, name="dispatch") +class AutoDiscoverView(ConfigBaseMixin, generic.TemplateView): + + http_method_names = ["post"] + template_name = "autoconfig/autodiscover.xml" + + def get_context_data(self, **kwargs): + emailaddress = self.request.POST.get("EmailAddress") + if not emailaddress: + raise Http404 + context = super().get_context_data(**kwargs) + context.update(self.get_common_context(emailaddress)) + return context + + def post(self, request, *args, **kwargs): + context = self.get_context_data(**kwargs) + return self.render_to_response(context) + + +class MobileConfigView(ConfigBaseMixin, generic.TemplateView): + + content_type = "application/x-apple-aspen-config" + http_method_names = ["get"] + template_name = "autoconfig/mobileconfig.xml" + + def get_context_data(self, **kwargs): + emailaddress = self.request.GET.get("email") + if not emailaddress: + raise Http404 + context = super().get_context_data(**kwargs) + context.update(self.get_common_context(emailaddress)) + parts = context["domain"].split(".") + parts.reverse() + context.update( + { + "reverse_domain": ".".join(parts), + "content_uuid": uuid.uuid4(), + "global_uuid": uuid.uuid4(), + } + ) + return context + + def render_to_response(self, context, **responsse_kwargs): + content = render_to_string(self.template_name, context) + response = HttpResponse(content, content_type=self.content_type) + response["Content-Disposition"] = 'attachment; filename="mail.mobileconfig"' + return response diff --git a/modoboa/core/commands/templates/settings.py.tpl b/modoboa/core/commands/templates/settings.py.tpl index 84f5cf847..8eb4cf226 100644 --- a/modoboa/core/commands/templates/settings.py.tpl +++ b/modoboa/core/commands/templates/settings.py.tpl @@ -38,6 +38,19 @@ SITE_ID = 1 # The email address that error messages come from, such as those sent to ADMINS #SERVER_EMAIL = 'webmaster@example.net' +EMAIL_CLIENT_CONNECTION_SETTINGS = { + 'imap': { + 'HOSTNAME': '{{ allowed_host }}', + 'SOCKET_TYPE': 'SSL', + 'PORT': 993, + }, + 'smtp': { + 'HOSTNAME': '{{ allowed_host }}', + 'SOCKET_TYPE': 'STARTTLS', + 'PORT': 587 + } +} + # Security settings X_FRAME_OPTIONS = "SAMEORIGIN" @@ -75,6 +88,7 @@ MODOBOA_APPS = ( 'modoboa.core', 'modoboa.lib', 'modoboa.admin', + 'modoboa.autoconfig', 'modoboa.transport', 'modoboa.relaydomains', 'modoboa.limits', diff --git a/modoboa/urls.py b/modoboa/urls.py index 9b88d13f3..91c5b739b 100644 --- a/modoboa/urls.py +++ b/modoboa/urls.py @@ -13,6 +13,7 @@ from modoboa.core.extensions import exts_pool urlpatterns = [ path("", include("modoboa.core.urls")), + path("", include("modoboa.autoconfig.urls")), path( "accounts/password_reset/", core_views.PasswordResetView.as_view(), diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py index 24d83cd5b..3a9c79627 100644 --- a/test_project/test_project/settings.py +++ b/test_project/test_project/settings.py @@ -50,6 +50,15 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField" # SERVER_EMAIL = 'webmaster@example.net' EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" +EMAIL_CLIENT_CONNECTION_SETTINGS = { + "imap": { + "HOSTNAME": "localhost", + "SOCKET_TYPE": "SSL", + "PORT": 993, + }, + "smtp": {"HOSTNAME": "localhost", "SOCKET_TYPE": "STARTTLS", "PORT": 587}, +} + # Security settings X_FRAME_OPTIONS = "SAMEORIGIN" @@ -87,6 +96,7 @@ MODOBOA_APPS = ( "modoboa.core", "modoboa.lib", "modoboa.admin", + "modoboa.autoconfig", "modoboa.transport", "modoboa.relaydomains", "modoboa.limits", @@ -397,6 +407,7 @@ TEST_RUNNER = "modoboa.lib.test_runners.UnManagedModelTestRunner" # We force sqlite backend for tests because the generated database is # not the same as the one provided by amavis... + DATABASES["amavis"] = { # noqa "ENGINE": "django.db.backends.sqlite3", "NAME": "amavis.db", From d684dac7d7b98b8d418e6abf555d09712fa344d3 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 16 Sep 2025 17:45:50 +0200 Subject: [PATCH 2/6] Use plistlib to generate mobileconfig --- .../templates/autoconfig/autoconfig.xml | 4 +- .../templates/autoconfig/mobileconfig.xml | 37 ---------- modoboa/autoconfig/tests.py | 2 +- modoboa/autoconfig/views.py | 67 +++++++++++++------ 4 files changed, 49 insertions(+), 61 deletions(-) delete mode 100644 modoboa/autoconfig/templates/autoconfig/mobileconfig.xml diff --git a/modoboa/autoconfig/templates/autoconfig/autoconfig.xml b/modoboa/autoconfig/templates/autoconfig/autoconfig.xml index e8af4ac61..2477d2d38 100644 --- a/modoboa/autoconfig/templates/autoconfig/autoconfig.xml +++ b/modoboa/autoconfig/templates/autoconfig/autoconfig.xml @@ -9,7 +9,7 @@ {{ connection_settings.imap.HOSTNAME }} {{ connection_settings.imap.PORT }} {{ connection_settings.imap.SOCKET_TYPE }} - password-cleartext + plain {{ emailaddress }} @@ -17,7 +17,7 @@ {{ connection_settings.smtp.HOSTNAME }} {{ connection_settings.smtp.PORT }} {{ connection_settings.smtp.SOCKET_TYPE }} - password-cleartext + plain {{ emailaddress }} diff --git a/modoboa/autoconfig/templates/autoconfig/mobileconfig.xml b/modoboa/autoconfig/templates/autoconfig/mobileconfig.xml deleted file mode 100644 index 832efe86d..000000000 --- a/modoboa/autoconfig/templates/autoconfig/mobileconfig.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - com.apple.mail.managed - 1 - {{ reverse_domain }}.email - {{ content_uuid }} - Mail - - Modoboa - EmailTypeIMAP - {{ emailaddress }} - {{ connection_settings.imap.HOSTNAME }} - {{ connection_settings.imap.PORT }} - true - {{ emailaddress }} - - {{ connection_settings.smtp.HOSTNAME }} - {{ connection_settings.smtp.PORT }} - true - {{ emailaddress }} - true - - - - Modoboa - Mail Configuration - {{ reverse_domain }}.mailconfig - {{ global_uuid }} - 1 - Configuration - - diff --git a/modoboa/autoconfig/tests.py b/modoboa/autoconfig/tests.py index bec5b0c69..6dbe76419 100644 --- a/modoboa/autoconfig/tests.py +++ b/modoboa/autoconfig/tests.py @@ -32,7 +32,7 @@ class ViewsTestCase(TestCase): resp = self.client.get(url) self.assertEqual(resp.status_code, 404) - resp = self.client.get(f"{url}?email=test@test.com") + resp = self.client.get(f"{url}?emailaddress=test@test.com") self.assertEqual(resp.status_code, 200) self.assertIn(b"test@test.com", resp.content) diff --git a/modoboa/autoconfig/views.py b/modoboa/autoconfig/views.py index 112a94d57..2bb99e958 100644 --- a/modoboa/autoconfig/views.py +++ b/modoboa/autoconfig/views.py @@ -1,8 +1,8 @@ +import plistlib import uuid from django.conf import settings from django.http import Http404, HttpResponse -from django.template.loader import render_to_string from django.utils.decorators import method_decorator from django.views import generic from django.views.decorators.csrf import csrf_exempt @@ -56,31 +56,56 @@ class AutoDiscoverView(ConfigBaseMixin, generic.TemplateView): return self.render_to_response(context) -class MobileConfigView(ConfigBaseMixin, generic.TemplateView): +class MobileConfigView(generic.View): - content_type = "application/x-apple-aspen-config" + databases = "__all__" http_method_names = ["get"] - template_name = "autoconfig/mobileconfig.xml" - def get_context_data(self, **kwargs): - emailaddress = self.request.GET.get("email") + def get(self, request, *args, **kwargs): + emailaddress = self.request.GET.get("emailaddress") if not emailaddress: raise Http404 - context = super().get_context_data(**kwargs) - context.update(self.get_common_context(emailaddress)) + local_part, domain = split_address(emailaddress) + context = self.get_common_context(emailaddress) parts = context["domain"].split(".") parts.reverse() - context.update( - { - "reverse_domain": ".".join(parts), - "content_uuid": uuid.uuid4(), - "global_uuid": uuid.uuid4(), - } + reverse_domain = ".".join(parts) + imap_settings = settings.EMAIL_CLIENT_CONNECTION_SETTINGS["imap"] + smtp_settings = settings.EMAIL_CLIENT_CONNECTION_SETTINGS["smtp"] + profile = { + "PayloadType": "Configuration", + "PayloadVersion": 1, + "PayloadIdentifier": f"{reverse_domain}.mailprofile", + "PayloadUUID": uuid.uuid4(), + "PayloadDisplayName": f"{domain} Mail Configuration", + "PayloadOrganization": domain, + "PayloadContent": [ + { + "PayloadType": "com.apple.mail.managed", + "PayloadVersion": 1, + "PayloadIdentifier": f"{reverse_domain}.mailprofile.mail", + "PayloadUUID": uuid.uuid4(), + "PayloadDisplayName": "Mail", + "EmailAccountDescription": f"{domain} Mail", + "EmailAccountType": "EmailTypeIMAP", + "EmailAddress": emailaddress, + # incoming + "IncomingMailServerHostName": imap_settings["HOSTNAME"], + "IncomingMailServerPortNumber": imap_settings["PORT"], + "IncomingMailServerUseSSL": True, + "IncomingMailServerUsername": emailaddress, + # outgoing + "OutgoingMailServerHostName": smtp_settings["HOSTNAME"], + "OutgoingMailServerPortNumber": smtp_settings["PORT"], + "OutgoingMailServerUseSSL": True, + "OutgoingMailServerUsername": emailaddress, + "OutgoingPasswordSameAsIncomingPassword": True, + } + ], + } + plist_bytes = plistlib.dumps(profile, fmt=plistlib.FMT_XML) + resp = HttpResponse( + plist_bytes, content_type="application/x-apple-aspen-config" ) - return context - - def render_to_response(self, context, **responsse_kwargs): - content = render_to_string(self.template_name, context) - response = HttpResponse(content, content_type=self.content_type) - response["Content-Disposition"] = 'attachment; filename="mail.mobileconfig"' - return response + resp["Content-Disposition"] = 'attachment; filename="mail.mobileconfig"' + return resp From 1a444729c0107663e9f6a10de709d898df48e6f6 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 16 Sep 2025 18:03:16 +0200 Subject: [PATCH 3/6] Added upgrade instructions --- doc/upgrade.rst | 21 +++++++++++++++++++++ modoboa/autoconfig/tests.py | 2 ++ modoboa/autoconfig/urls.py | 2 +- modoboa/autoconfig/views.py | 1 - 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/doc/upgrade.rst b/doc/upgrade.rst index 6ccf12a03..f13462c34 100644 --- a/doc/upgrade.rst +++ b/doc/upgrade.rst @@ -130,6 +130,27 @@ Rebuild Virtual Environment Specific instructions ********************* +Version 2.6.0 +============= + +New built-in auto-configuration service. + +Required changes to :file:`settings.py` +--------------------------------------- + +- Add the following line to ``MODOBOA_APPS`` variable: + + .. sourcecode:: python + + 'modoboa.autoconfig' + +- Add the following hostnames to ``ALLOWED_HOSTS`` variable: + + .. sourcecode:: python + + 'autoconfig.', + 'autodiscover.', + Version 2.5.0 ============= diff --git a/modoboa/autoconfig/tests.py b/modoboa/autoconfig/tests.py index 6dbe76419..f10f5cf11 100644 --- a/modoboa/autoconfig/tests.py +++ b/modoboa/autoconfig/tests.py @@ -4,6 +4,8 @@ from django.urls import reverse class ViewsTestCase(TestCase): + databases = "__all__" + def test_autoconfig(self): url = reverse("autoconfig:autoconfig") diff --git a/modoboa/autoconfig/urls.py b/modoboa/autoconfig/urls.py index 227fbf2fd..b53299dc1 100644 --- a/modoboa/autoconfig/urls.py +++ b/modoboa/autoconfig/urls.py @@ -8,7 +8,7 @@ app_name = "autoconfig" urlpatterns = [ path( - ".well-known/autoconfig/mail/config-v1.1.xml", + "mail/config-v1.1.xml", views.AutoConfigView.as_view(), name="autoconfig", ), diff --git a/modoboa/autoconfig/views.py b/modoboa/autoconfig/views.py index 2bb99e958..630096612 100644 --- a/modoboa/autoconfig/views.py +++ b/modoboa/autoconfig/views.py @@ -58,7 +58,6 @@ class AutoDiscoverView(ConfigBaseMixin, generic.TemplateView): class MobileConfigView(generic.View): - databases = "__all__" http_method_names = ["get"] def get(self, request, *args, **kwargs): From 8a5c8723f6be583964e1487a9a04a3da711e7d3c Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 16 Sep 2025 18:13:51 +0200 Subject: [PATCH 4/6] Fixed mobileconfig view --- modoboa/autoconfig/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modoboa/autoconfig/views.py b/modoboa/autoconfig/views.py index 630096612..406439162 100644 --- a/modoboa/autoconfig/views.py +++ b/modoboa/autoconfig/views.py @@ -65,8 +65,7 @@ class MobileConfigView(generic.View): if not emailaddress: raise Http404 local_part, domain = split_address(emailaddress) - context = self.get_common_context(emailaddress) - parts = context["domain"].split(".") + parts = domain.split(".") parts.reverse() reverse_domain = ".".join(parts) imap_settings = settings.EMAIL_CLIENT_CONNECTION_SETTINGS["imap"] From 9c5dbcae32bfb47e81fd8e074bd045f0759e8c91 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 16 Sep 2025 18:53:24 +0200 Subject: [PATCH 5/6] Force string conversion --- modoboa/autoconfig/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modoboa/autoconfig/views.py b/modoboa/autoconfig/views.py index 406439162..932e8c1b5 100644 --- a/modoboa/autoconfig/views.py +++ b/modoboa/autoconfig/views.py @@ -74,7 +74,7 @@ class MobileConfigView(generic.View): "PayloadType": "Configuration", "PayloadVersion": 1, "PayloadIdentifier": f"{reverse_domain}.mailprofile", - "PayloadUUID": uuid.uuid4(), + "PayloadUUID": str(uuid.uuid4()), "PayloadDisplayName": f"{domain} Mail Configuration", "PayloadOrganization": domain, "PayloadContent": [ @@ -82,7 +82,7 @@ class MobileConfigView(generic.View): "PayloadType": "com.apple.mail.managed", "PayloadVersion": 1, "PayloadIdentifier": f"{reverse_domain}.mailprofile.mail", - "PayloadUUID": uuid.uuid4(), + "PayloadUUID": str(uuid.uuid4()), "PayloadDisplayName": "Mail", "EmailAccountDescription": f"{domain} Mail", "EmailAccountType": "EmailTypeIMAP", From bef79c668751d25a208e42a7d79a303306c9ea3d Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 16 Sep 2025 19:00:56 +0200 Subject: [PATCH 6/6] Fixed test --- modoboa/autoconfig/tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/modoboa/autoconfig/tests.py b/modoboa/autoconfig/tests.py index f10f5cf11..c43a43eef 100644 --- a/modoboa/autoconfig/tests.py +++ b/modoboa/autoconfig/tests.py @@ -36,5 +36,3 @@ class ViewsTestCase(TestCase): resp = self.client.get(f"{url}?emailaddress=test@test.com") self.assertEqual(resp.status_code, 200) - - self.assertIn(b"test@test.com", resp.content)