Merge pull request #3856 from modoboa/fix/imap-migration-password-escaping

Make sure passwords are properly generated in config file.
This commit is contained in:
Antoine Nguyen
2026-01-06 09:56:06 +01:00
committed by GitHub
4 changed files with 28 additions and 7 deletions
+3 -3
View File
@@ -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)
@@ -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 %}
+21
View File
@@ -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."""
+2 -2
View File
@@ -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)))