diff --git a/docker/dovecot_config/auth-custom.conf b/docker/dovecot_config/auth-custom.conf index 923b0f36c..d7a430050 100644 --- a/docker/dovecot_config/auth-custom.conf +++ b/docker/dovecot_config/auth-custom.conf @@ -5,6 +5,10 @@ auth_debug = yes auth_verbose = yes auth_debug_passwords = yes +# Disabling SSL (dev) +ssl=no +disable_plaintext_auth=no + passdb { driver = oauth2 mechanisms = xoauth2 oauthbearer diff --git a/frontend/src/api/repository.js b/frontend/src/api/repository.js index 9c23ab68a..15725b251 100644 --- a/frontend/src/api/repository.js +++ b/frontend/src/api/repository.js @@ -36,8 +36,10 @@ _axios.interceptors.response.use( return Promise.reject(error) } if (error.response.status !== 401 || router.currentRoute.name === 'Login') { - const busStore = useBusStore() - busStore.displayNotification({ msg: error.response.data, type: 'error' }) + if (error.response.status !== 518) { + const busStore = useBusStore() + busStore.displayNotification({ msg: error.response.data, type: 'error' }) + } return Promise.reject(error) } const refreshToken = Cookies.get('refreshToken') diff --git a/frontend/src/views/account/FiltersView.vue b/frontend/src/views/account/FiltersView.vue index b4621b002..5762f0485 100644 --- a/frontend/src/views/account/FiltersView.vue +++ b/frontend/src/views/account/FiltersView.vue @@ -200,6 +200,9 @@ function getFilterSetName(filterSet) { if (filterSet.active) { result += ' (' + $gettext('active') + ')' } + else { + result += ' (' + $gettext('inactive') + ')' + } return result } @@ -216,7 +219,12 @@ function closeFilterForm() { function fetchFilterSets() { accountApi.getFilterSets().then(resp => { - filterSets.value = resp.data + if (resp.data.length > 0 && resp.data[0].active && resp.data[0].name === null) { + // No filter is active + filterSets.value = resp.data.slice(1) + } else { + filterSets.value = resp.data + } if (!currentFilterSet.value && route.params.filterset) { for (const filterSet of filterSets.value) { if (filterSet.name === route.params.filterset) { @@ -233,6 +241,12 @@ function fetchFilters(filterSetName) { accountApi.getFilters(filterSetName).then(resp => { filters.value = resp.data loadingFilters.value = false + }).catch(error => { + if (error.response.status === 518) { // Server is a teapot (sieve file contains custom code or is broken) + // Let the user see with rawmode + rawMode.value = true + busStore.displayNotification({ msg: $gettext('Failed to display template. Using raw mode'), type: 'warning' }) + } }) } diff --git a/modoboa/core/checks.py b/modoboa/core/checks.py index a1e6b7348..ca61a40af 100644 --- a/modoboa/core/checks.py +++ b/modoboa/core/checks.py @@ -1,4 +1,4 @@ -from django.db.utils import ProgrammingError +from django.db.utils import ProgrammingError, OperationalError from django.core.checks import register, Info, Warning from django.conf import settings from django.utils.translation import gettext as _ @@ -53,7 +53,7 @@ def check_password_hasher(app_configs, **kwargs): msgs = [] try: hasher = check_for_deprecated_password_schemes() - except ProgrammingError: + except (ProgrammingError, OperationalError): # This is probably a fresh install... return [] if hasher: diff --git a/modoboa/sievefilters/api/v2/tests.py b/modoboa/sievefilters/api/v2/tests.py index 3c95743b4..bd33fc83a 100644 --- a/modoboa/sievefilters/api/v2/tests.py +++ b/modoboa/sievefilters/api/v2/tests.py @@ -137,6 +137,9 @@ class FilterSetViewSetTestCase(ModoAPITestCase): resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertEqual(len(resp.json()), 5) + url = reverse("v2:filterset-get-filters", args=["complex_script"]) + resp = self.client.get(url) + self.assertEqual(resp.status_code, 518) def test_add_filter(self): self.authenticate() diff --git a/modoboa/sievefilters/api/v2/viewsets.py b/modoboa/sievefilters/api/v2/viewsets.py index 2d701d7da..52d7fca9f 100644 --- a/modoboa/sievefilters/api/v2/viewsets.py +++ b/modoboa/sievefilters/api/v2/viewsets.py @@ -15,7 +15,7 @@ from rest_framework.decorators import action from modoboa.lib.viewsets import HasMailbox from modoboa.sievefilters import constants -from modoboa.sievefilters.lib import SieveClient, SieveClientError +from modoboa.sievefilters.lib import SieveClient, SieveClientError, SieveActionTemplateNotFound from modoboa.sievefilters.rfc6266 import build_header from modoboa.sievefilters.api.v2 import serializers @@ -154,7 +154,10 @@ class FilterSetViewSet(viewsets.ViewSet): fset = sclient.getscript(pk, format="fset") except SieveClientError: return response.Response(status=404) - serializer = serializers.FilterSerializer.from_filters(fset.filters) + try: + serializer = serializers.FilterSerializer.from_filters(fset.filters) + except SieveActionTemplateNotFound: + return HttpResponse(status=518) return response.Response(serializer.data) @extend_schema( diff --git a/modoboa/sievefilters/lib.py b/modoboa/sievefilters/lib.py index 481b17b8f..066035f68 100644 --- a/modoboa/sievefilters/lib.py +++ b/modoboa/sievefilters/lib.py @@ -19,6 +19,10 @@ class SieveClientError(ModoboaException): http_code = 400 +class SieveActionTemplateNotFound(Exception): + pass + + class SieveClient: """Sieve client.""" @@ -93,4 +97,4 @@ def find_action_template(action: str): for tpl in constants.ACTION_TEMPLATES: if tpl["name"] == action: return tpl - raise RuntimeError(f"action {action} not defined") + raise SieveActionTemplateNotFound(f"action {action} not defined") diff --git a/modoboa/sievefilters/mocks.py b/modoboa/sievefilters/mocks.py index e1e2b3632..baad7283f 100644 --- a/modoboa/sievefilters/mocks.py +++ b/modoboa/sievefilters/mocks.py @@ -39,6 +39,15 @@ if anyof (header :contains "Subject" "toto", not header :contains "To" "peƩ") { } """ +SAMPLE_COMPLEX_SCRIPT = """require ["imap4flags"]; + +# Filter: Test42 +if anyof (header :contains "From" "TOBESEEN") { + setflag "\\seen"; +} + +""" + class ManagesieveClientMock: """Fake managesieve client.""" @@ -50,6 +59,7 @@ class ManagesieveClientMock: "main_script": SAMPLE_SIEVE_SCRIPT, "second_script": SAMPLE_SIEVE_SCRIPT, "third_script": SAMPLE_SIEVE_SCRIPT2, + "complex_script": SAMPLE_COMPLEX_SCRIPT, } def connect(self, username, password, **kwargs):