From c00d67809a8fb602fbeeb0e73aedd7e7c85507f6 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Tue, 6 Jan 2026 09:42:22 +0100 Subject: [PATCH] Make sure passwords are properly generated in config file. fix #3599 #3844 --- modoboa/imap_migration/models.py | 6 +++--- .../templates/imap_migration/offlineimap.conf | 4 ++-- modoboa/imap_migration/tests.py | 21 +++++++++++++++++++ modoboa/lib/cryptutils.py | 4 ++-- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/modoboa/imap_migration/models.py b/modoboa/imap_migration/models.py index eda2fe41c..fd383306a 100644 --- a/modoboa/imap_migration/models.py +++ b/modoboa/imap_migration/models.py @@ -47,11 +47,11 @@ class Migration(models.Model): return self.username @property - def password(self): + def password(self) -> str: """Password getter.""" - return decrypt(self._password) + return decrypt(self._password).replace("%", "%%") @password.setter - def password(self, value): + def password(self, value: str) -> None: """Password setter.""" self._password = encrypt(value) diff --git a/modoboa/imap_migration/templates/imap_migration/offlineimap.conf b/modoboa/imap_migration/templates/imap_migration/offlineimap.conf index e80582896..d4f2d45d0 100644 --- a/modoboa/imap_migration/templates/imap_migration/offlineimap.conf +++ b/modoboa/imap_migration/templates/imap_migration/offlineimap.conf @@ -11,7 +11,7 @@ type = IMAP ssl = false remotehost = localhost remoteuser = {{ migration.mailbox }} -remotepass = {{ migration.password }} +remotepass = {% autoescape off %}{{ migration.password }}{% endautoescape %} {% if imap_create_folders == False %} createfolders = False {% endif %} @@ -27,7 +27,7 @@ sslcacertfile = /etc/ssl/certs/ca-certificates.crt ssl = no {% endif %} remoteuser = {{ migration }} -remotepass = {{ migration.password }} +remotepass = {% autoescape off %}{{ migration.password }}{% endautoescape %} readonly = true {% autoescape off %} {% if imap_folder_filter_exclude|length > 0 %} diff --git a/modoboa/imap_migration/tests.py b/modoboa/imap_migration/tests.py index 0841a091b..606c0b828 100644 --- a/modoboa/imap_migration/tests.py +++ b/modoboa/imap_migration/tests.py @@ -115,6 +115,27 @@ class ManagementCommandTestCase(DataMixin, ModoTestCase): conf.read(path) self.assertTrue(conf.has_section("Account user@test.com")) + def test_password_escaping(self): + """Check that passwords are escaped when needed.""" + self.migration.password = "%Test1234" + self.migration.save() + path = os.path.join(self.workdir, "offlineimap.conf") + call_command("generate_offlineimap_config", "--output", path) + self.assertTrue(os.path.exists(path)) + conf = configparser.ConfigParser() + conf.read(path) + section = "Repository Local_user@test.com" + self.assertEqual(conf.get(section, "remotepass"), "%Test1234") + + self.migration.password = "Test&1234" + self.migration.save() + call_command("generate_offlineimap_config", "--output", path) + self.assertTrue(os.path.exists(path)) + conf = configparser.ConfigParser() + conf.read(path) + section = "Repository Local_user@test.com" + self.assertEqual(conf.get(section, "remotepass"), self.migration.password) + class ViewSetTestCase(DataMixin, ModoAPITestCase): """ViewSet related tests.""" diff --git a/modoboa/lib/cryptutils.py b/modoboa/lib/cryptutils.py index 303aed64a..3b7a7e4bb 100644 --- a/modoboa/lib/cryptutils.py +++ b/modoboa/lib/cryptutils.py @@ -27,12 +27,12 @@ def _get_fernet(): return Fernet(secret_key) -def encrypt(clear_value): +def encrypt(clear_value: bytes) -> str: """Encrypt a value using secret_key.""" return smart_str(_get_fernet().encrypt(smart_bytes(clear_value))) -def decrypt(encrypted_value): +def decrypt(encrypted_value: bytes) -> str: """Decrypt a value using secret_key.""" return smart_str(_get_fernet().decrypt(smart_bytes(encrypted_value)))