From 3fb01e9321226a99fb7a1421d33b985775e1b645 Mon Sep 17 00:00:00 2001 From: Vince Pike Date: Wed, 2 Dec 2020 12:51:38 -0500 Subject: [PATCH 01/32] Add some helpful tips to new developers Added a helpful description to new developers stuck. --- doc/contributing/getting_started.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/contributing/getting_started.rst b/doc/contributing/getting_started.rst index e5b841708..695e65960 100644 --- a/doc/contributing/getting_started.rst +++ b/doc/contributing/getting_started.rst @@ -24,6 +24,12 @@ setup, go to the next section. .. _venv_for_dev: +Prerequisites For Development +============================= +If you encounter errors about missing tools, please ensure you have them installed with the foollowing: + + $ sudo apt install librrd-dev + Prepare a virtual environment ============================= From 0f1a90aacb751830992ebcafc88af3a709b1cfc6 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Fri, 4 Dec 2020 09:37:30 +0100 Subject: [PATCH 02/32] Replaced CharField by TextField. see #2081 --- .../migrations/0018_auto_20201204_0935.py | 18 ++++++++++++++++++ modoboa/admin/models/alarm.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 modoboa/admin/migrations/0018_auto_20201204_0935.py diff --git a/modoboa/admin/migrations/0018_auto_20201204_0935.py b/modoboa/admin/migrations/0018_auto_20201204_0935.py new file mode 100644 index 000000000..35bdf2f62 --- /dev/null +++ b/modoboa/admin/migrations/0018_auto_20201204_0935.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.12 on 2020-12-04 08:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin', '0017_alarm'), + ] + + operations = [ + migrations.AlterField( + model_name='alarm', + name='title', + field=models.TextField(), + ), + ] diff --git a/modoboa/admin/models/alarm.py b/modoboa/admin/models/alarm.py index 12ea82d39..8168fe51d 100644 --- a/modoboa/admin/models/alarm.py +++ b/modoboa/admin/models/alarm.py @@ -33,7 +33,7 @@ class Alarm(models.Model): db_index=True ) - title = models.CharField(max_length=150) + title = models.TextField() internal_name = models.CharField(max_length=120) objects = models.Manager.from_queryset(AlarmQuerySet)() From 865ac83f867a0fc7f7eaa01fcca706afbbe9f201 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Dec 2020 05:01:10 +0000 Subject: [PATCH 03/32] Bump testfixtures from 6.15.0 to 6.16.0 Bumps [testfixtures](https://github.com/Simplistix/testfixtures) from 6.15.0 to 6.16.0. - [Release notes](https://github.com/Simplistix/testfixtures/releases) - [Changelog](https://github.com/Simplistix/testfixtures/blob/master/CHANGELOG.rst) - [Commits](https://github.com/Simplistix/testfixtures/compare/6.15.0...6.16.0) Signed-off-by: dependabot[bot] --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 6006e2bb4..541d71470 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,6 @@ argon2-cffi>=16.1.0 factory-boy<=2.12.0 httmock==1.4.0 -testfixtures==6.15.0 +testfixtures==6.16.0 tox asynctest From 3a9f825e8fc03ac720ef82def51c5833c169c301 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Dec 2020 08:52:27 +0000 Subject: [PATCH 04/32] Update mysqlclient requirement from <2.0.2 to <2.0.3 Updates the requirements on [mysqlclient](https://github.com/PyMySQL/mysqlclient) to permit the latest version. - [Release notes](https://github.com/PyMySQL/mysqlclient/releases) - [Changelog](https://github.com/PyMySQL/mysqlclient/blob/master/HISTORY.rst) - [Commits](https://github.com/PyMySQL/mysqlclient/commits) Signed-off-by: dependabot[bot] --- mysql-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-requirements.txt b/mysql-requirements.txt index 4b7bd761c..6eed28562 100644 --- a/mysql-requirements.txt +++ b/mysql-requirements.txt @@ -1 +1 @@ -mysqlclient<2.0.2 +mysqlclient<2.0.3 From 36a04fec0929938d723ec34430c862cc91f5c54a Mon Sep 17 00:00:00 2001 From: Vince Pike Date: Sat, 12 Dec 2020 02:46:46 -0500 Subject: [PATCH 05/32] Update the connection string for beginners (#2095) Co-authored-by: Christophe CHAUVET --- doc/manual_installation/modoboa.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/manual_installation/modoboa.rst b/doc/manual_installation/modoboa.rst index 90ba53228..1dfe0f6e6 100644 --- a/doc/manual_installation/modoboa.rst +++ b/doc/manual_installation/modoboa.rst @@ -183,6 +183,9 @@ you just have to run the following command: A database url should meet the following syntax ``://[user:pass@][host:port]/dbname`` **OR** ``sqlite:////full/path/to/your/database/file.sqlite``. + + Fox example, if you were using postgres, you could setup your command like this: + ``modoboa-admin.py deploy instance_name --collectstatic --domain example.com --dburl default:postgres://user:pass@[localhost]/modoboa`` The command will ask you a few questions, answer them and you're done. From 7d96a6b1f1f3621e88966f346f22b7bb9cf2f548 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Sat, 12 Dec 2020 08:31:12 +0000 Subject: [PATCH 06/32] Apply translations in de (#2085) translation completed for the source file '/modoboa/locale/en/LC_MESSAGES/djangojs.po' on the 'de' language. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> Co-authored-by: Christophe CHAUVET --- modoboa/locale/de/LC_MESSAGES/djangojs.po | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/modoboa/locale/de/LC_MESSAGES/djangojs.po b/modoboa/locale/de/LC_MESSAGES/djangojs.po index 3cfc96ec9..4eb8389b6 100644 --- a/modoboa/locale/de/LC_MESSAGES/djangojs.po +++ b/modoboa/locale/de/LC_MESSAGES/djangojs.po @@ -5,15 +5,16 @@ # # Translators: # Antoine Nguyen , 2020 +# Nicolas John, 2020 # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-03-09 16:48+0100\n" +"POT-Creation-Date: 2020-11-05 15:47+0100\n" "PO-Revision-Date: 2020-05-11 18:45+0000\n" -"Last-Translator: Antoine Nguyen , 2020\n" +"Last-Translator: Nicolas John, 2020\n" "Language-Team: German (https://www.transifex.com/tonio/teams/13749/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -41,7 +42,7 @@ msgstr "Warnung" msgid "Cancel" msgstr "Abbrechen" -#: static/js/global.js:342 +#: static/js/global.js:347 msgid "Internal error" msgstr "Interner Fehler" @@ -56,3 +57,11 @@ msgstr "Suche" #: static/js/searchbar.js:28 msgid "Search..." msgstr "Suchen..." + +#: static/js/twocols_nav.js:170 +msgid "Are you sure?" +msgstr "Sind Sie sicher?" + +#: static/js/twocols_nav.js:171 +msgid "This will invalidate your registered application." +msgstr "Dadurch wird Ihre registrierte Anwendung ungültig." From b531370766beeb1a8c051566985e334e04a32555 Mon Sep 17 00:00:00 2001 From: Christophe CHAUVET Date: Mon, 28 Dec 2020 08:45:13 +0100 Subject: [PATCH 07/32] Update Issue Template and links --- .../Bug_Report.md} | 17 ++++++++++++++++- .github/ISSUE_TEMPLATE/config.yml | 8 ++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) rename .github/{ISSUE_TEMPLATE.md => ISSUE_TEMPLATE/Bug_Report.md} (52%) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/Bug_Report.md similarity index 52% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/Bug_Report.md index b1c69ef2f..91c7d5a57 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/Bug_Report.md @@ -1,8 +1,16 @@ +--- +name: 'Bug Report' +about: 'Report a reproducible bug in the framework.' +title: '' +labels: '' +assignees: '' +--- + # Impacted versions * OS Type: Debian/Ubuntu, Centos/Redhat, Arch, Gentoo, Slack, ... * OS Version: Number or Name -* Database Type: PostgreSQL/MySQL +* Database Type: PostgreSQL / MySQL * Database version: X.y * Modoboa: X.X.x * installer used: Yes/No ? @@ -12,6 +20,13 @@ # Current behavior + + # Expected behavior # Video/Screenshot link (optional) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..5db193d51 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Modoboa Support + url: https://github.com/modoboa/modoboa/discussions + about: Please ask and answer questions here. + - name: Sponsoring + ur: https://modoboa.org/en/sponsoring/ + about: You love Modoboa and want to sponsor it From 74e661be8dce0f6bfcb1501ccf3dcb4c295c893e Mon Sep 17 00:00:00 2001 From: Christophe CHAUVET Date: Mon, 28 Dec 2020 14:13:54 +0100 Subject: [PATCH 08/32] Replace Framework by Software --- .github/ISSUE_TEMPLATE/Bug_Report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/Bug_Report.md b/.github/ISSUE_TEMPLATE/Bug_Report.md index 91c7d5a57..166f7db16 100644 --- a/.github/ISSUE_TEMPLATE/Bug_Report.md +++ b/.github/ISSUE_TEMPLATE/Bug_Report.md @@ -1,6 +1,6 @@ --- name: 'Bug Report' -about: 'Report a reproducible bug in the framework.' +about: 'Report a reproducible bug in the software.' title: '' labels: '' assignees: '' From b23ba59912350c0bb91cb8302aec610cfc0806e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Jan 2021 05:03:24 +0000 Subject: [PATCH 09/32] Update mysqlclient requirement from <2.0.3 to <2.0.4 Updates the requirements on [mysqlclient](https://github.com/PyMySQL/mysqlclient) to permit the latest version. - [Release notes](https://github.com/PyMySQL/mysqlclient/releases) - [Changelog](https://github.com/PyMySQL/mysqlclient/blob/master/HISTORY.rst) - [Commits](https://github.com/PyMySQL/mysqlclient/commits) Signed-off-by: dependabot[bot] --- mysql-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mysql-requirements.txt b/mysql-requirements.txt index 6eed28562..dee426737 100644 --- a/mysql-requirements.txt +++ b/mysql-requirements.txt @@ -1 +1 @@ -mysqlclient<2.0.3 +mysqlclient<2.0.4 From 9df210b86938fee19e7fe744825a658d7ee794f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Jan 2021 08:49:57 +0000 Subject: [PATCH 10/32] Bump testfixtures from 6.16.0 to 6.17.1 Bumps [testfixtures](https://github.com/Simplistix/testfixtures) from 6.16.0 to 6.17.1. - [Release notes](https://github.com/Simplistix/testfixtures/releases) - [Changelog](https://github.com/Simplistix/testfixtures/blob/master/CHANGELOG.rst) - [Commits](https://github.com/Simplistix/testfixtures/compare/6.16.0...6.17.1) Signed-off-by: dependabot[bot] --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 541d71470..d30e8202f 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,6 @@ argon2-cffi>=16.1.0 factory-boy<=2.12.0 httmock==1.4.0 -testfixtures==6.16.0 +testfixtures==6.17.1 tox asynctest From 633fd076261d67612377659584df5cb9758dbbfa Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Wed, 20 Jan 2021 07:30:01 +0000 Subject: [PATCH 11/32] Apply translations in zh_CN (#2121) translation completed for the source file '/modoboa/locale/en/LC_MESSAGES/djangojs.po' on the 'zh_CN' language. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> Co-authored-by: Christophe CHAUVET --- modoboa/locale/zh_CN/LC_MESSAGES/djangojs.po | 66 ++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 modoboa/locale/zh_CN/LC_MESSAGES/djangojs.po diff --git a/modoboa/locale/zh_CN/LC_MESSAGES/djangojs.po b/modoboa/locale/zh_CN/LC_MESSAGES/djangojs.po new file mode 100644 index 000000000..2c9fa073a --- /dev/null +++ b/modoboa/locale/zh_CN/LC_MESSAGES/djangojs.po @@ -0,0 +1,66 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +# Translators: +# 李 正 <364109980@qq.com>, 2021 +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-11-05 15:47+0100\n" +"PO-Revision-Date: 2020-05-11 18:45+0000\n" +"Last-Translator: 李 正 <364109980@qq.com>, 2021\n" +"Language-Team: Chinese (China) (https://www.transifex.com/tonio/teams/13749/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: bower_components/jquery-ui/jquery-ui.min.js:8 +msgid "'" +msgstr "'" + +#: bower_components/swagger-ui/dist/swagger-ui-bundle.js:133 +msgid "operation" +msgstr "操作" + +#: core/static/core/js/settings.js:16 +msgid "No more log entry to show" +msgstr "没有更多的日志可以显示" + +#: static/js/confirmation.js:44 +msgid "Warning" +msgstr "警告" + +#: static/js/confirmation.js:59 +msgid "Cancel" +msgstr "取消" + +#: static/js/global.js:347 +msgid "Internal error" +msgstr "内部错误" + +#: static/js/listing.js:21 +msgid "No more entry to show" +msgstr "没有更多的条目可以显示" + +#: static/js/listing.js:46 +msgid "Search" +msgstr "查找" + +#: static/js/searchbar.js:28 +msgid "Search..." +msgstr "查找..." + +#: static/js/twocols_nav.js:170 +msgid "Are you sure?" +msgstr "你确定吗?" + +#: static/js/twocols_nav.js:171 +msgid "This will invalidate your registered application." +msgstr "这会使您的注册申请无效。" From 3101f30b67783b51311c319f98b61366c03466b7 Mon Sep 17 00:00:00 2001 From: "transifex-integration[bot]" <43880903+transifex-integration[bot]@users.noreply.github.com> Date: Wed, 20 Jan 2021 07:39:21 +0000 Subject: [PATCH 12/32] Apply translations in zh_CN (#2122) translation completed for the source file '/modoboa/ldapsync/locale/en/LC_MESSAGES/django.po' on the 'zh_CN' language. Co-authored-by: transifex-integration[bot] <43880903+transifex-integration[bot]@users.noreply.github.com> --- .../locale/zh_CN/LC_MESSAGES/django.po | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 modoboa/ldapsync/locale/zh_CN/LC_MESSAGES/django.po diff --git a/modoboa/ldapsync/locale/zh_CN/LC_MESSAGES/django.po b/modoboa/ldapsync/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 000000000..d8c7f23d3 --- /dev/null +++ b/modoboa/ldapsync/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,38 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +# Translators: +# 李 正 <364109980@qq.com>, 2021 +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-05-06 13:15+0200\n" +"PO-Revision-Date: 2020-05-11 18:45+0000\n" +"Last-Translator: 李 正 <364109980@qq.com>, 2021\n" +"Language-Team: Chinese (China) (https://www.transifex.com/tonio/teams/13749/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: ldapsync/lib.py:84 +msgid "Failed to create LDAP account: {}" +msgstr "无法创建LDAP帐户: {}" + +#: ldapsync/lib.py:124 +msgid "Failed to update LDAP account: {}" +msgstr "无法更新LDAP帐户: {}" + +#: ldapsync/lib.py:138 +msgid "Failed to delete LDAP account: {}" +msgstr "无法删除LDAP帐户: {}" + +#: ldapsync/lib.py:149 +msgid "Failed to disable LDAP account: {}" +msgstr "无法关闭LDAP帐户: {}" From 59af8feb18aa1f19b445c623637490fa66eb43f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Jan 2021 08:58:26 +0000 Subject: [PATCH 13/32] Bump django-reversion from 3.0.8 to 3.0.9 Bumps [django-reversion](https://github.com/etianen/django-reversion) from 3.0.8 to 3.0.9. - [Release notes](https://github.com/etianen/django-reversion/releases) - [Changelog](https://github.com/etianen/django-reversion/blob/master/CHANGELOG.rst) - [Commits](https://github.com/etianen/django-reversion/compare/v3.0.8...v3.0.9) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3e9a0cccf..2d686696e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ django>=2.2,<2.3 django-braces==1.14.0 django-ckeditor==6.0.0 django-phonenumber-field[phonenumbers]==5.0.0 -django-reversion==3.0.8 +django-reversion==3.0.9 django-xforwardedfor-middleware==2.0 django-otp==1.0.2 From 0010035c89d36a1da07c211269d5992e43cd3d0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 26 Jan 2021 11:13:55 +0100 Subject: [PATCH 14/32] Disable internal alias too with the account (fix #2120) --- modoboa/admin/forms/account.py | 5 ++--- modoboa/admin/tests/test_account.py | 8 +++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/modoboa/admin/forms/account.py b/modoboa/admin/forms/account.py index f2ca42690..51d246480 100644 --- a/modoboa/admin/forms/account.py +++ b/modoboa/admin/forms/account.py @@ -206,9 +206,8 @@ class AccountFormGeneral(forms.ModelForm): account.role = self.cleaned_data["role"] if hasattr(account, "mailbox"): # Update forward status according to account status - models.Alias.objects.filter( - address=account.email, internal=False).update( - enabled=account.is_active) + models.Alias.objects.filter(address=account.email).update( + enabled=account.is_active) return account diff --git a/modoboa/admin/tests/test_account.py b/modoboa/admin/tests/test_account.py index 4eb722c45..0c4bb27e7 100644 --- a/modoboa/admin/tests/test_account.py +++ b/modoboa/admin/tests/test_account.py @@ -119,8 +119,10 @@ class AccountTestCase(ModoTestCase): account = User.objects.get(username=values["username"]) self.assertEqual(account.language, "fr") - def test_forward_updated_on_edit(self): + def test_aliases_update_on_disable(self): + """Check if aliases are updated when account is disabled.""" account = User.objects.get(username="user@test.com") + internal = models.Alias.objects.get(address=account.email, internal=True) forward = models.Alias.objects.create(address=account.email, internal=False) values = { "username": "user@test.com", @@ -133,12 +135,16 @@ class AccountTestCase(ModoTestCase): self.ajax_post( reverse("admin:account_change", args=[account.id]), values ) + internal.refresh_from_db() + self.assertFalse(internal.enabled) forward.refresh_from_db() self.assertFalse(forward.enabled) values["is_active"] = True self.ajax_post( reverse("admin:account_change", args=[account.id]), values ) + internal.refresh_from_db() + self.assertTrue(internal.enabled) forward.refresh_from_db() self.assertTrue(forward.enabled) From 63a8e8435351cd5aeee47c322146b085c7f5978b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 26 Jan 2021 12:25:03 +0100 Subject: [PATCH 15/32] Resynchronize the package index in tests --- .github/workflows/modoboa.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/modoboa.yml b/.github/workflows/modoboa.yml index cfd765302..37be44fae 100644 --- a/.github/workflows/modoboa.yml +++ b/.github/workflows/modoboa.yml @@ -66,7 +66,9 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - sudo apt-get install postfix librrd-dev rrdtool libldap2-dev libsasl2-dev libssl-dev + sudo apt-get update -y \ + && sudo apt-get install -y \ + postfix librrd-dev rrdtool libldap2-dev libsasl2-dev libssl-dev python -m pip install --upgrade pip pip install -r requirements.txt pip install -r ldap-requirements.txt From 3eebd3fc158cfc8a20a6031aca012f87b5aa0ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Thu, 28 Jan 2021 15:21:03 +0100 Subject: [PATCH 16/32] Add migration to disable disabled accounts' aliases --- .../0018_update_disabled_accounts_aliases.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 modoboa/admin/migrations/0018_update_disabled_accounts_aliases.py diff --git a/modoboa/admin/migrations/0018_update_disabled_accounts_aliases.py b/modoboa/admin/migrations/0018_update_disabled_accounts_aliases.py new file mode 100644 index 000000000..b12909e49 --- /dev/null +++ b/modoboa/admin/migrations/0018_update_disabled_accounts_aliases.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.17 on 2021-01-28 13:40 + +from django.db import migrations + + +def update_disabled_accounts_aliases(apps, schema_editor): + User = apps.get_model('core', 'User') + Alias = apps.get_model('admin', 'Alias') + + disabled_accounts = User.objects.filter( + mailbox__isnull=False, is_active=False) + + Alias.objects.filter( + address__in=list(disabled_accounts.values_list('email', flat=True)), + enabled=True, + ).update(enabled=False) + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin', '0017_alarm'), + ('core', '0022_user_tfa_enabled'), + ] + + operations = [ + migrations.RunPython(update_disabled_accounts_aliases), + ] From 2abfb505b5048835ec3a601f9fe46c519d276a86 Mon Sep 17 00:00:00 2001 From: Akshat Soni Date: Tue, 2 Feb 2021 15:40:22 +0530 Subject: [PATCH 17/32] Update Dockerfile.dev The old version of pip install caused some issues during installation in docker so adding the command to upgrade pip --- Dockerfile.dev | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile.dev b/Dockerfile.dev index 52eca494f..d54fab93b 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -13,6 +13,7 @@ RUN python3 -m venv $VIRTUAL_ENV WORKDIR /tmp COPY requirements.txt /tmp COPY test-requirements.txt /tmp +RUN python -m pip install -U pip RUN pip install -r requirements.txt -r test-requirements.txt FROM base as run From 9cecc79a02352c53f67b49308d7342ee8481952d Mon Sep 17 00:00:00 2001 From: Sebastian Lohff Date: Thu, 4 Feb 2021 17:10:44 +0100 Subject: [PATCH 18/32] Raise domain name length to RFC1035 maximum (#2147) RFC1035 Section 2.3.4. specifies a maximum domain name length of 255 octets in protocol. As each label is prefixed by its length and the domain-root (.) has a length of 0 we have a maximum of 253 octets. For modoboa to support all these domains in its DB scheme we need to raise the maximum domain name length to 253 as well. --- .../migrations/0019_auto_20210130_2226.py | 18 ++++++++++++++++++ modoboa/admin/models/domain.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 modoboa/admin/migrations/0019_auto_20210130_2226.py diff --git a/modoboa/admin/migrations/0019_auto_20210130_2226.py b/modoboa/admin/migrations/0019_auto_20210130_2226.py new file mode 100644 index 000000000..7f3c902e7 --- /dev/null +++ b/modoboa/admin/migrations/0019_auto_20210130_2226.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.17 on 2021-01-30 22:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin', '0018_update_disabled_accounts_aliases'), + ] + + operations = [ + migrations.AlterField( + model_name='domain', + name='name', + field=models.CharField(help_text='The domain name', max_length=253, unique=True, verbose_name='name'), + ), + ] diff --git a/modoboa/admin/models/domain.py b/modoboa/admin/models/domain.py index b7db53700..282f9f887 100644 --- a/modoboa/admin/models/domain.py +++ b/modoboa/admin/models/domain.py @@ -23,7 +23,7 @@ from . import mixins class Domain(mixins.MessageLimitMixin, AdminObject): """Mail domain.""" - name = models.CharField(ugettext_lazy("name"), max_length=100, unique=True, + name = models.CharField(ugettext_lazy("name"), max_length=253, unique=True, help_text=ugettext_lazy("The domain name")) quota = models.PositiveIntegerField( default=0, From abd3ba537e5c1ff923ad9af085bff1ca1070e471 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 26 Jan 2021 17:24:33 +0100 Subject: [PATCH 19/32] Add MD5-CRYPT to the fallback password schemes Password should never be stored in plain-text. As Dovecot could be installed on another host, this adds MD5-CRYPT as a weak but common scheme and as an alternative to PLAIN. --- modoboa/core/password_hashers/__init__.py | 2 +- modoboa/core/tests/test_authentication.py | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/modoboa/core/password_hashers/__init__.py b/modoboa/core/password_hashers/__init__.py index dbb399731..23982d4f3 100644 --- a/modoboa/core/password_hashers/__init__.py +++ b/modoboa/core/password_hashers/__init__.py @@ -43,6 +43,6 @@ def get_dovecot_schemes(): for scheme in schemes.split()] if not supported_schemes: - supported_schemes = ['{PLAIN}'] + supported_schemes = ['{MD5-CRYPT}', '{PLAIN}'] return supported_schemes diff --git a/modoboa/core/tests/test_authentication.py b/modoboa/core/tests/test_authentication.py index 567edc01a..d08e1421b 100644 --- a/modoboa/core/tests/test_authentication.py +++ b/modoboa/core/tests/test_authentication.py @@ -19,9 +19,9 @@ from modoboa.core.password_hashers import ( from modoboa.lib.tests import NO_SMTP, ModoTestCase from .. import factories, models +DOVEADM_TEST_PATH = "{}/doveadm".format(os.path.dirname(__file__)) + -@override_settings( - DOVEADM_LOOKUP_PATH=["{}/doveadm".format(os.path.dirname(__file__))]) class AuthenticationTestCase(ModoTestCase): """Validate authentication scenarios.""" @@ -89,6 +89,7 @@ class AuthenticationTestCase(ModoTestCase): self.assertEqual(response.status_code, 302) self.assertTrue(response.url.endswith(reverse("core:dashboard"))) + @override_settings(DOVEADM_LOOKUP_PATH=[DOVEADM_TEST_PATH]) def test_password_schemes(self): """Validate password scheme changes.""" username = "user@test.com" @@ -130,6 +131,7 @@ class AuthenticationTestCase(ModoTestCase): self.assertTrue(user.password.startswith(pw_hash.scheme)) @skipUnless(argon2, "argon2-cffi not installed") + @override_settings(DOVEADM_LOOKUP_PATH=[DOVEADM_TEST_PATH]) def test_password_argon2_parameter_change(self): """Validate hash parameter update on login works with argon2.""" username = "user@test.com" @@ -169,8 +171,9 @@ class AuthenticationTestCase(ModoTestCase): self.assertEqual(parameters.memory_cost, 1000) self.assertEqual(parameters.parallelism, 2) - def test_supported_schemes(self): - """Validate dovecot supported schemes.""" + @override_settings(DOVEADM_LOOKUP_PATH=[DOVEADM_TEST_PATH]) + def test_dovecot_supported_schemes(self): + """Validate dovecot supported schemes with fake output.""" supported_schemes = get_dovecot_schemes() self.assertEqual(supported_schemes, ["{MD5}", @@ -204,6 +207,11 @@ class AuthenticationTestCase(ModoTestCase): "{SHA256-CRYPT}", "{SHA512-CRYPT}"]) + def test_dovecot_default_schemes(self): + """Check default scheme if doveadm is not found.""" + supported_schemes = get_dovecot_schemes() + self.assertEqual(supported_schemes, ["{MD5-CRYPT}", "{PLAIN}"]) + class PasswordResetTestCase(ModoTestCase): """Test password reset service.""" From 5cbbd4655b937af12c779a59d93e7c036f8a6df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 27 Jan 2021 10:06:30 +0100 Subject: [PATCH 20/32] Allow one to define Dovecot's schemes in settings --- modoboa/core/password_hashers/__init__.py | 29 +++++++++++++---------- modoboa/core/tests/test_authentication.py | 8 ++++++- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/modoboa/core/password_hashers/__init__.py b/modoboa/core/password_hashers/__init__.py index 23982d4f3..0a0b3f778 100644 --- a/modoboa/core/password_hashers/__init__.py +++ b/modoboa/core/password_hashers/__init__.py @@ -2,6 +2,7 @@ Password hashers for Modoboa. """ +from django.conf import settings from django.utils.encoding import smart_text from modoboa.core.password_hashers.advanced import ( # NOQA:F401 @@ -32,17 +33,21 @@ def get_password_hasher(scheme): def get_dovecot_schemes(): - """Return schemes supported by dovecot""" - supported_schemes = None - try: - _, schemes = doveadm_cmd("pw -l") - except OSError: - pass - else: - supported_schemes = ["{{{}}}".format(smart_text(scheme)) - for scheme in schemes.split()] + """Return schemes supported by Dovecot. - if not supported_schemes: - supported_schemes = ['{MD5-CRYPT}', '{PLAIN}'] + It first try to get supported schemes fron the settings, then from the + doveadm output, and fallback to {MD5-CRYPT} and {PLAIN} if the command + is not found. - return supported_schemes + :return: A list of supported '{SCHEME}' + """ + schemes = getattr(settings, "DOVECOT_SUPPORTED_SCHEMES", None) + + if not schemes: + try: + _, schemes = doveadm_cmd("pw -l") + except OSError: + schemes = "MD5-CRYPT PLAIN" + + return ["{{{}}}".format(smart_text(scheme)) + for scheme in schemes.split()] diff --git a/modoboa/core/tests/test_authentication.py b/modoboa/core/tests/test_authentication.py index d08e1421b..f7aa8b3ab 100644 --- a/modoboa/core/tests/test_authentication.py +++ b/modoboa/core/tests/test_authentication.py @@ -173,7 +173,7 @@ class AuthenticationTestCase(ModoTestCase): @override_settings(DOVEADM_LOOKUP_PATH=[DOVEADM_TEST_PATH]) def test_dovecot_supported_schemes(self): - """Validate dovecot supported schemes with fake output.""" + """Validate Dovecot supported schemes with fake command output.""" supported_schemes = get_dovecot_schemes() self.assertEqual(supported_schemes, ["{MD5}", @@ -207,6 +207,12 @@ class AuthenticationTestCase(ModoTestCase): "{SHA256-CRYPT}", "{SHA512-CRYPT}"]) + @override_settings(DOVECOT_SUPPORTED_SCHEMES="SHA1 SHA512-CRYPT") + def test_dovecot_supported_schemes_from_settings(self): + """Validate dovecot supported schemes from the settings.""" + supported_schemes = get_dovecot_schemes() + self.assertEqual(supported_schemes, ["{SHA1}", "{SHA512-CRYPT}"]) + def test_dovecot_default_schemes(self): """Check default scheme if doveadm is not found.""" supported_schemes = get_dovecot_schemes() From 82ec811356c746984046285cdb71a693c045c970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 27 Jan 2021 10:16:59 +0100 Subject: [PATCH 21/32] Add and document missing settings in template --- .../core/commands/templates/settings.py.tpl | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/modoboa/core/commands/templates/settings.py.tpl b/modoboa/core/commands/templates/settings.py.tpl index cab8a30e3..d393e9500 100644 --- a/modoboa/core/commands/templates/settings.py.tpl +++ b/modoboa/core/commands/templates/settings.py.tpl @@ -114,15 +114,15 @@ MIDDLEWARE = ( ) AUTHENTICATION_BACKENDS = ( - # 'modoboa.lib.authbackends.LDAPBackend', - # 'modoboa.lib.authbackends.SMTPBackend', + #'modoboa.lib.authbackends.LDAPBackend', + #'modoboa.lib.authbackends.SMTPBackend', 'django.contrib.auth.backends.ModelBackend', ) # SMTP authentication -# AUTH_SMTP_SERVER_ADDRESS = 'localhost' -# AUTH_SMTP_SERVER_PORT = 25 -# AUTH_SMTP_SECURED_MODE = None # 'ssl' or 'starttls' are accepted +#AUTH_SMTP_SERVER_ADDRESS = 'localhost' +#AUTH_SMTP_SERVER_PORT = 25 +#AUTH_SMTP_SECURED_MODE = None # 'ssl' or 'starttls' are accepted TEMPLATES = [ @@ -202,9 +202,15 @@ SPECTACULAR_SETTINGS = { } # Modoboa settings -# MODOBOA_CUSTOM_LOGO = os.path.join(MEDIA_URL, "custom_logo.png") -# DOVECOT_LOOKUP_PATH = ('/path/to/dovecot', ) +#MODOBOA_CUSTOM_LOGO = os.path.join(MEDIA_URL, "custom_logo.png") + +# Path to Dovecot binaries in case of a non-standard installation +#DOVECOT_LOOKUP_PATH = ('/path/to/dovecot', ) +#DOVEADM_LOOKUP_PATH = ('/path/to/doveadm', ) + +# List of supported schemes if doveadm is not available, given by: doveadm pw -l +#DOVECOT_SUPPORTED_SCHEMES = 'SHA512-CRYPT SHA256-CRYPT' MODOBOA_API_URL = 'https://api.modoboa.org/1/' From a7ced6634c997a6a8b86fc4fc45872007b1ee042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Wed, 27 Jan 2021 10:44:19 +0100 Subject: [PATCH 22/32] Document the settings related to Dovecot --- doc/configuration.rst | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/doc/configuration.rst b/doc/configuration.rst index df9bb4b5c..e41a5caf7 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -31,7 +31,7 @@ The *admin* application exposes several parameters, they are presented below: |Authentication type |General |The backend used for |Local | | | |authentication | | +--------------------+---------------------+-------------------------------+--------------------+ -|Default password |General |Scheme used to crypt |crypt | +|Default password |General |Scheme used to crypt |sha512crypt | |scheme | |mailbox passwords | | +--------------------+---------------------+-------------------------------+--------------------+ |Rounds |General |Number of rounds (only used by |70000 | @@ -144,6 +144,13 @@ The *admin* application exposes several parameters, they are presented below: | | | | | +--------------------+---------------------+-------------------------------+--------------------+ +.. warning:: + + If Dovecot is not running on the same host than Modoboa, you will have + to define which password schemes are supported. To do so, open the + :file:`settings.py` file and add a ``DOVECOT_SUPPORTED_SCHEMES`` + variable with the output of the command: ``doveadm pw -l``. + .. note:: If you are not familiar with virtual domain hosting, you should @@ -202,7 +209,11 @@ Host configuration .. note:: This section is only relevant when Modoboa handles mailboxes - renaming and removal from the filesystem. + renaming and removal from the filesystem, which requires that + Dovecot is installed and running on this host. If it is installed + at a non-standard directory, paths to its binaries can be set in the + :file:`settings.py` file with the ``DOVECOT_LOOKUP_PATH`` and + ``DOVEADM_LOOKUP_PATH`` variables. To manipulate mailboxes on the filesystem, you must allow the user who runs Modoboa to execute commands as the user who owns mailboxes. From 1a677d5c7e3696909066811501e22e097f3ee048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 2 Feb 2021 18:39:29 +0100 Subject: [PATCH 23/32] Ignore E265 in settings.py template --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index 427c34916..947d97f8f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,3 +21,5 @@ max-line-length = 80 [flake8] exclude = migrations max-line-length = 80 +per-file-ignores = + modoboa/core/commands/templates/settings.py.tpl: E265 From f08157ff6a202d6790a022a7a0ae71606f6efb83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 26 Jan 2021 12:01:00 +0100 Subject: [PATCH 24/32] Accept rrdtool version of current Debian package --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2d686696e..ff2f1cc0d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,7 +31,7 @@ chardet ovh oath redis -rrdtool>=0.1.11 +rrdtool>=0.1.10 qrcode aiosmtplib From 7d5f87aaab43e48bb7a82c33c0cf2f6ef5a6421e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Tue, 26 Jan 2021 12:17:24 +0100 Subject: [PATCH 25/32] Document the system packages which can be installed --- doc/manual_installation/modoboa.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/manual_installation/modoboa.rst b/doc/manual_installation/modoboa.rst index 1dfe0f6e6..f939d3d21 100644 --- a/doc/manual_installation/modoboa.rst +++ b/doc/manual_installation/modoboa.rst @@ -55,9 +55,14 @@ following system packages according to your distribution: .. note:: Alternatively, you could rely on your distribution packages for the Modoboa - dependencies which require compilation - e.g. ``psycopg2`` - if the version + dependencies which require compilation - e.g. ``rrdtool`` - if the version is compatible. In this case, you have to create your virtual environment - with the ``--system-site-packages`` option. + with the ``--system-site-packages`` option, and the required system + packages will be: + + +---------------------------------------+ + | python3-wheel python3-rrdtool rrdtool | + +---------------------------------------+ Then, install Modoboa by running: @@ -183,7 +188,7 @@ you just have to run the following command: A database url should meet the following syntax ``://[user:pass@][host:port]/dbname`` **OR** ``sqlite:////full/path/to/your/database/file.sqlite``. - + Fox example, if you were using postgres, you could setup your command like this: ``modoboa-admin.py deploy instance_name --collectstatic --domain example.com --dburl default:postgres://user:pass@[localhost]/modoboa`` From 39c40c18ef8ed801dadf28a0ce1e0097d13f2f22 Mon Sep 17 00:00:00 2001 From: Sebastian Lohff Date: Tue, 9 Feb 2021 22:09:43 +0100 Subject: [PATCH 26/32] Handle NameTooLong by logging an error get_dns_records() could be fed with a name that is too long for the DNS protocol. In this case a NameTooLong error is thrown, which we can catch and log an error for. This can happen for example if a DNS name that is already at the limit is used in combination with "manage.py modo check_mx", which appends an autodiscover to the domain, which then exceeds the DNS name limit. --- modoboa/admin/lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modoboa/admin/lib.py b/modoboa/admin/lib.py index 3fe459e66..995ee514f 100644 --- a/modoboa/admin/lib.py +++ b/modoboa/admin/lib.py @@ -198,6 +198,8 @@ def get_dns_records(name, typ, resolver=None): logger.warning( _("DNS resolution timeout, unable to query %s at the moment") % name, exc_info=e) + except dns.name.NameTooLong as e: + logger.error(_("DNS name is too long: %s" % name), exc_info=e) else: return dns_answers return None From 6e9d631204d8be5ff3868cda301f7406c530b5f6 Mon Sep 17 00:00:00 2001 From: Akshat Soni Date: Wed, 10 Feb 2021 09:11:03 +0530 Subject: [PATCH 27/32] Update Dockerfile.dev --- Dockerfile.dev | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index d54fab93b..3936a0a5f 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -13,8 +13,7 @@ RUN python3 -m venv $VIRTUAL_ENV WORKDIR /tmp COPY requirements.txt /tmp COPY test-requirements.txt /tmp -RUN python -m pip install -U pip -RUN pip install -r requirements.txt -r test-requirements.txt +RUN python -m pip install -U pip && pip install -r requirements.txt -r test-requirements.txt FROM base as run COPY --from=build $VIRTUAL_ENV $VIRTUAL_ENV From e6f4987512c0c271e65e3a47ecdb14bf248bee21 Mon Sep 17 00:00:00 2001 From: Akshat Soni Date: Wed, 10 Feb 2021 09:59:56 +0530 Subject: [PATCH 28/32] Update Dockerfile.dev --- Dockerfile.dev | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 3936a0a5f..6fc20ecaa 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -13,7 +13,8 @@ RUN python3 -m venv $VIRTUAL_ENV WORKDIR /tmp COPY requirements.txt /tmp COPY test-requirements.txt /tmp -RUN python -m pip install -U pip && pip install -r requirements.txt -r test-requirements.txt +RUN python -m pip install -U pip +RUN pip install -r requirements.txt -r test-requirements.txt FROM base as run COPY --from=build $VIRTUAL_ENV $VIRTUAL_ENV From 233a8fe9fd2e5c9842be6059ec799a4787e909d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Lebleu?= Date: Mon, 8 Feb 2021 19:13:34 +0100 Subject: [PATCH 29/32] Wrap ModelViewSet requests to a revision block --- modoboa/admin/api.py | 11 ++++++----- modoboa/admin/tests/test_api.py | 6 ++++-- modoboa/lib/viewsets.py | 11 +++++++++++ modoboa/relaydomains/viewsets.py | 3 ++- 4 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 modoboa/lib/viewsets.py diff --git a/modoboa/admin/api.py b/modoboa/admin/api.py index 7a1b04e6c..8f0267c2a 100644 --- a/modoboa/admin/api.py +++ b/modoboa/admin/api.py @@ -13,6 +13,7 @@ from rest_framework.response import Response from modoboa.core import models as core_models from modoboa.core import sms_backends +from modoboa.lib.viewsets import RevisionModelMixin from . import lib, models, serializers @@ -31,7 +32,7 @@ from . import lib, models, serializers summary="Create a new domain" ) ) -class DomainViewSet(viewsets.ModelViewSet): +class DomainViewSet(RevisionModelMixin, viewsets.ModelViewSet): """Domain viewset.""" permission_classes = [IsAuthenticated, DjangoModelPermissions, ] @@ -46,7 +47,7 @@ class DomainViewSet(viewsets.ModelViewSet): instance.delete(self.request.user) -class DomainAliasViewSet(viewsets.ModelViewSet): +class DomainAliasViewSet(RevisionModelMixin, viewsets.ModelViewSet): """ViewSet for DomainAlias.""" permission_classes = [IsAuthenticated, DjangoModelPermissions, ] @@ -62,7 +63,7 @@ class DomainAliasViewSet(viewsets.ModelViewSet): return queryset -class AccountViewSet(viewsets.ModelViewSet): +class AccountViewSet(RevisionModelMixin, viewsets.ModelViewSet): """ViewSet for User/Mailbox.""" filter_backends = [filters.SearchFilter] @@ -157,7 +158,7 @@ class AccountViewSet(viewsets.ModelViewSet): return Response(body) -class AliasViewSet(viewsets.ModelViewSet): +class AliasViewSet(RevisionModelMixin, viewsets.ModelViewSet): """ create: Create a new alias instance. @@ -182,7 +183,7 @@ class AliasViewSet(viewsets.ModelViewSet): return queryset -class SenderAddressViewSet(viewsets.ModelViewSet): +class SenderAddressViewSet(RevisionModelMixin, viewsets.ModelViewSet): """View set for SenderAddress model.""" permission_classes = [IsAuthenticated, DjangoModelPermissions, ] diff --git a/modoboa/admin/tests/test_api.py b/modoboa/admin/tests/test_api.py index 982ff5aa6..727154239 100644 --- a/modoboa/admin/tests/test_api.py +++ b/modoboa/admin/tests/test_api.py @@ -10,6 +10,7 @@ from django.test import override_settings from django.urls import reverse from rest_framework.authtoken.models import Token +from reversion.models import Version from modoboa.admin import models as admin_models from modoboa.core import factories as core_factories, models as core_models @@ -51,8 +52,9 @@ class DomainAPITestCase(ModoAPITestCase): url, {"name": "test3.com", "quota": 0, "default_mailbox_quota": 10} ) self.assertEqual(response.status_code, 201) - self.assertTrue( - models.Domain.objects.filter(name="test3.com").exists()) + domain = models.Domain.objects.get(name="test3.com") + self.assertEqual(Version.objects.get_for_object(domain).count(), 1) + response = self.client.post(url, {}) self.assertEqual(response.status_code, 400) self.assertIn("name", response.data) diff --git a/modoboa/lib/viewsets.py b/modoboa/lib/viewsets.py new file mode 100644 index 000000000..b97fd9a1d --- /dev/null +++ b/modoboa/lib/viewsets.py @@ -0,0 +1,11 @@ +"""ViewSet related mixins and tools.""" + +from reversion.views import RevisionMixin + + +class RevisionModelMixin(RevisionMixin): + """ + A mixin to wrap every request in a revision block. + """ + + pass diff --git a/modoboa/relaydomains/viewsets.py b/modoboa/relaydomains/viewsets.py index d7de1b82d..35841af6a 100644 --- a/modoboa/relaydomains/viewsets.py +++ b/modoboa/relaydomains/viewsets.py @@ -4,10 +4,11 @@ from rest_framework import viewsets from rest_framework.permissions import DjangoModelPermissions, IsAuthenticated from modoboa.admin import models as admin_models +from modoboa.lib.viewsets import RevisionModelMixin from . import serializers -class RelayDomainViewSet(viewsets.ModelViewSet): +class RelayDomainViewSet(RevisionModelMixin, viewsets.ModelViewSet): """RelayDomain viewset.""" permission_classes = [IsAuthenticated, DjangoModelPermissions, ] From 1827bce52d8002060b68ec24752080f5af7a2a76 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 11 Feb 2021 17:16:21 +0100 Subject: [PATCH 30/32] Run doveadm command as vmail user. see #2084 --- modoboa/admin/app_settings.py | 12 +----------- modoboa/admin/models/mailbox.py | 12 +----------- modoboa/core/app_settings.py | 9 ++++++--- modoboa/core/commands/templates/settings.py.tpl | 2 ++ modoboa/lib/sysutils.py | 11 +++++++---- 5 files changed, 17 insertions(+), 29 deletions(-) diff --git a/modoboa/admin/app_settings.py b/modoboa/admin/app_settings.py index f1275c709..8de7dca47 100644 --- a/modoboa/admin/app_settings.py +++ b/modoboa/admin/app_settings.py @@ -145,14 +145,6 @@ class AdminParametersForm(param_forms.AdminParametersForm): ) ) - mailboxes_owner = forms.CharField( - label=ugettext_lazy("Mailboxes owner"), - initial="vmail", - help_text=ugettext_lazy( - "The UNIX account who owns mailboxes on the filesystem" - ) - ) - default_mailbox_quota = forms.IntegerField( label=ugettext_lazy("Default mailbox quota"), initial=0, @@ -198,8 +190,7 @@ class AdminParametersForm(param_forms.AdminParametersForm): # Visibility rules visibility_rules = { "valid_mxs": "enable_mx_checks=True", - "domains_must_have_authorized_mx": "enable_mx_checks=True", - "mailboxes_owner": "handle_mailboxes=True", + "domains_must_have_authorized_mx": "enable_mx_checks=True" } def __init__(self, *args, **kwargs): @@ -234,7 +225,6 @@ class AdminParametersForm(param_forms.AdminParametersForm): hide_fields = True if hide_fields: del self.fields["handle_mailboxes"] - del self.fields["mailboxes_owner"] def clean_default_domain_quota(self): """Ensure quota is a positive integer.""" diff --git a/modoboa/admin/models/mailbox.py b/modoboa/admin/models/mailbox.py index cbea1a775..1b5ae5625 100644 --- a/modoboa/admin/models/mailbox.py +++ b/modoboa/admin/models/mailbox.py @@ -1,8 +1,5 @@ """Models related to mailboxes management.""" -import os -import pwd - from reversion import revisions as reversion from django.contrib.contenttypes.models import ContentType @@ -153,14 +150,7 @@ class Mailbox(mixins.MessageLimitMixin, AdminObject): if not admin_params.get("handle_mailboxes"): return None if self.__mail_home is None: - curuser = pwd.getpwuid(os.getuid()).pw_name - mbowner = admin_params["mailboxes_owner"] - options = {} - if curuser != mbowner: - options["sudo_user"] = mbowner - code, output = doveadm_cmd( - "user -f home %s" % self.full_address, **options - ) + code, output = doveadm_cmd("user -f home %s" % self.full_address) if code: raise lib_exceptions.InternalError( _("Failed to retrieve mailbox location (%s)") % output) diff --git a/modoboa/core/app_settings.py b/modoboa/core/app_settings.py index c545614c8..3f90b4140 100644 --- a/modoboa/core/app_settings.py +++ b/modoboa/core/app_settings.py @@ -49,9 +49,7 @@ class GeneralParametersForm(param_forms.AdminParametersForm): password_scheme = forms.ChoiceField( label=ugettext_lazy("Default password scheme"), - choices=[(hasher.name, ugettext_lazy(hasher.label)) - for hasher in PasswordHasher.get_password_hashers() - if hasher().scheme in get_dovecot_schemes()], + choices=[], initial="sha512crypt", help_text=ugettext_lazy("Scheme used to crypt mailbox passwords"), ) @@ -528,6 +526,11 @@ class GeneralParametersForm(param_forms.AdminParametersForm): self._add_visibilty_rules( sms_backends.get_all_backend_visibility_rules() ) + self.fields["password_scheme"].choices = [ + (hasher.name, ugettext_lazy(hasher.label)) + for hasher in PasswordHasher.get_password_hashers() + if hasher().scheme in get_dovecot_schemes() + ] def _add_dynamic_fields(self): new_fields = OrderedDict() diff --git a/modoboa/core/commands/templates/settings.py.tpl b/modoboa/core/commands/templates/settings.py.tpl index c604ca92b..726860ee9 100644 --- a/modoboa/core/commands/templates/settings.py.tpl +++ b/modoboa/core/commands/templates/settings.py.tpl @@ -197,6 +197,8 @@ REST_FRAMEWORK = { # DOVECOT_LOOKUP_PATH = ('/path/to/dovecot', ) +# DOVECOT_USER = 'vmail' + MODOBOA_API_URL = 'https://api.modoboa.org/1/' DISABLE_DASHBOARD_EXTERNAL_QUERIES = False diff --git a/modoboa/lib/sysutils.py b/modoboa/lib/sysutils.py index 05e84c41f..d290a04a7 100644 --- a/modoboa/lib/sysutils.py +++ b/modoboa/lib/sysutils.py @@ -5,6 +5,7 @@ This module extra functions/shortcuts to communicate with the system import inspect import os +import pwd import re import subprocess @@ -42,8 +43,7 @@ def exec_cmd(cmd, sudo_user=None, pinput=None, capture_output=True, **kwargs): return process.returncode, output -def doveadm_cmd(params, sudo_user=None, pinput=None, - capture_output=True, **kwargs): +def doveadm_cmd(params, pinput=None, capture_output=True, **kwargs): """Execute doveadm command. Run doveadm command using the current user. Set :keyword:`sudo_user` if @@ -69,14 +69,17 @@ def doveadm_cmd(params, sudo_user=None, pinput=None, if os.path.isfile(fpath) and os.access(fpath, os.X_OK): dpath = fpath break + dovecot_user = getattr(settings, "DOVECOT_USER", "vmail") + curuser = pwd.getpwuid(os.getuid()).pw_name + sudo_user = dovecot_user if curuser != dovecot_user else None if dpath: return exec_cmd("{} {}".format(dpath, params), sudo_user=sudo_user, pinput=pinput, capture_output=capture_output, **kwargs) - else: - raise OSError("doveadm command not found") + + raise OSError("doveadm command not found") def guess_extension_name(): From b0e308034c7b09543067d84a90cc16d0e9da9c05 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 11 Feb 2021 17:32:10 +0100 Subject: [PATCH 31/32] Renamed migrations. --- ...unts_aliases.py => 0019_update_disabled_accounts_aliases.py} | 2 +- .../{0019_auto_20210130_2226.py => 0020_auto_20210130_2226.py} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename modoboa/admin/migrations/{0018_update_disabled_accounts_aliases.py => 0019_update_disabled_accounts_aliases.py} (93%) rename modoboa/admin/migrations/{0019_auto_20210130_2226.py => 0020_auto_20210130_2226.py} (86%) diff --git a/modoboa/admin/migrations/0018_update_disabled_accounts_aliases.py b/modoboa/admin/migrations/0019_update_disabled_accounts_aliases.py similarity index 93% rename from modoboa/admin/migrations/0018_update_disabled_accounts_aliases.py rename to modoboa/admin/migrations/0019_update_disabled_accounts_aliases.py index b12909e49..fe3f727d5 100644 --- a/modoboa/admin/migrations/0018_update_disabled_accounts_aliases.py +++ b/modoboa/admin/migrations/0019_update_disabled_accounts_aliases.py @@ -19,7 +19,7 @@ def update_disabled_accounts_aliases(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('admin', '0017_alarm'), + ('admin', '0018_auto_20201204_0935'), ('core', '0022_user_tfa_enabled'), ] diff --git a/modoboa/admin/migrations/0019_auto_20210130_2226.py b/modoboa/admin/migrations/0020_auto_20210130_2226.py similarity index 86% rename from modoboa/admin/migrations/0019_auto_20210130_2226.py rename to modoboa/admin/migrations/0020_auto_20210130_2226.py index 7f3c902e7..ea9e90eb7 100644 --- a/modoboa/admin/migrations/0019_auto_20210130_2226.py +++ b/modoboa/admin/migrations/0020_auto_20210130_2226.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('admin', '0018_update_disabled_accounts_aliases'), + ('admin', '0019_update_disabled_accounts_aliases'), ] operations = [ From 302a4a2f4dfddffaa0b3dd8d31813fff4e28d044 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Thu, 11 Feb 2021 20:49:23 +0100 Subject: [PATCH 32/32] Better error handling for doveadm call. --- modoboa/core/password_hashers/__init__.py | 8 ++++++-- modoboa/core/tests/test_authentication.py | 11 +++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/modoboa/core/password_hashers/__init__.py b/modoboa/core/password_hashers/__init__.py index 0a0b3f778..b94bccae7 100644 --- a/modoboa/core/password_hashers/__init__.py +++ b/modoboa/core/password_hashers/__init__.py @@ -42,12 +42,16 @@ def get_dovecot_schemes(): :return: A list of supported '{SCHEME}' """ schemes = getattr(settings, "DOVECOT_SUPPORTED_SCHEMES", None) + default_schemes = "MD5-CRYPT PLAIN" if not schemes: try: - _, schemes = doveadm_cmd("pw -l") + retcode, schemes = doveadm_cmd("pw -l") except OSError: - schemes = "MD5-CRYPT PLAIN" + schemes = default_schemes + else: + if retcode: + schemes = default_schemes return ["{{{}}}".format(smart_text(scheme)) for scheme in schemes.split()] diff --git a/modoboa/core/tests/test_authentication.py b/modoboa/core/tests/test_authentication.py index f7aa8b3ab..6467cf7ac 100644 --- a/modoboa/core/tests/test_authentication.py +++ b/modoboa/core/tests/test_authentication.py @@ -171,9 +171,16 @@ class AuthenticationTestCase(ModoTestCase): self.assertEqual(parameters.memory_cost, 1000) self.assertEqual(parameters.parallelism, 2) - @override_settings(DOVEADM_LOOKUP_PATH=[DOVEADM_TEST_PATH]) - def test_dovecot_supported_schemes(self): + @mock.patch("modoboa.lib.sysutils.exec_cmd") + def test_dovecot_supported_schemes(self, exec_cmd_mock): """Validate Dovecot supported schemes with fake command output.""" + exec_cmd_mock.return_value = ( + 0, + "MD5 MD5-CRYPT SHA SHA1 SHA256 SHA512 SMD5 SSHA SSHA256 SSHA512 " + "PLAIN CLEAR CLEARTEXT PLAIN-TRUNC CRAM-MD5 SCRAM-SHA-1 HMAC-MD5 " + "DIGEST-MD5 PLAIN-MD4 PLAIN-MD5 LDAP-MD5 LANMAN NTLM OTP SKEY RPA " + "PBKDF2 CRYPT SHA256-CRYPT SHA512-CRYPT" + ) supported_schemes = get_dovecot_schemes() self.assertEqual(supported_schemes, ["{MD5}",