\n"
+"Language-Team: Swedish (http://www.transifex.com/tonio/modoboa/language/"
+"sv/)\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: auth_backends.py:45
+#, python-format
+msgid "Connection to IMAP server failed: %s"
+msgstr "Anslutning till IMAP server misslyckades: %s"
+
+#: checks.py:11
+msgid ""
+"Automatic domain/mailbox creation is disabled which means IMAP "
+"authentication won't work."
+msgstr ""
+
+#: checks.py:13
+msgid ""
+"Go to the online parameters panel (admin tab) and activate this feature."
+msgstr ""
+
+#: forms.py:15
+msgid "OfflineIMAP settings"
+msgstr "OfflineIMAP inställningar"
+
+#: forms.py:18
+msgid "Concurrent sync jobs"
+msgstr "Samtidiga synkroniserings jobb"
+
+#: forms.py:20
+msgid "The maximum number of concurrent synchronization jobs"
+msgstr "Det maximala antalet samtidiga synkroniserings jobb"
+
+#: forms.py:23
+#, fuzzy
+#| msgid "OfflineIMAP settings"
+msgid "OfflineIMAP Filter settings"
+msgstr "OfflineIMAP inställningar"
+
+#: forms.py:26
+msgid "Create Folders"
+msgstr ""
+
+#: forms.py:28
+msgid "Allow Creation of missing folders during sync"
+msgstr ""
+
+#: forms.py:33
+msgid "Folder Filter Exclusions"
+msgstr ""
+
+#: forms.py:36
+msgid ""
+"Use a regular expression to explicitly include folders in sync. Example: "
+"^Trash$|Del"
+msgstr ""
+
+#: forms.py:43
+msgid "Folder Filter Inclusions"
+msgstr ""
+
+#: forms.py:46
+msgid ""
+"A comma seperated list of folders to explicitly include in sync even if "
+"filtered by the Folder Filter Exclusions. Example: debian.user, debian."
+"personal "
+msgstr ""
+
+#: handlers.py:17
+#, fuzzy
+#| msgid "IMAP migration"
+msgid "IMAP migrations"
+msgstr "IMAP migrering"
+
+#: modo_extension.py:16
+msgid "IMAP migration using OfflineIMAP"
+msgstr "IMAP migrering med hjälp av OfflineIMAP"
+
+#: modo_extension.py:19
+msgid "Migrate existing mailboxes using IMAP and OfflineIMAP"
+msgstr "Migrera existerande brevlådor med hjälp av IMAP och OfflineIMAP"
+
+#: modo_extension.py:25 templates/modoboa_imap_migration/index.html:6
+msgid "IMAP migration"
+msgstr "IMAP migrering"
+
+#~ msgid "Connection settings"
+#~ msgstr "Anslutningsinställningar"
+
+#~ msgid "Server address"
+#~ msgstr "Server adress"
+
+#~ msgid "Address of your IMAP server"
+#~ msgstr "Adress till din IMAP server"
+
+#~ msgid "Use a secured connection"
+#~ msgstr "Använd en säker anslutning"
+
+#~ msgid "Use a secured connection to access IMAP server"
+#~ msgstr "Använd en säker anslutning för åtkomst till IMAP server"
+
+#~ msgid "Server port"
+#~ msgstr "Server port"
+
+#~ msgid "Listening port of your IMAP server"
+#~ msgstr "Lyssningsport för din IMAP server"
+
+#~ msgid "Cancel this account's migration"
+#~ msgstr "Avbryt detta kontos migrering"
+
+#~ msgid "Cancel the migration for %s?"
+#~ msgstr "Avbryt migreringen för %s?"
+
+#~ msgid "Unknow migration"
+#~ msgstr "Okänd migrering"
+
+#~ msgid "Migration cancelled successfully"
+#~ msgstr "Migrering avbröts framgångsrikt"
diff --git a/modoboa/imap_migration/management/__init__.py b/modoboa/imap_migration/management/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/imap_migration/management/commands/__init__.py b/modoboa/imap_migration/management/commands/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/imap_migration/management/commands/generate_offlineimap_config.py b/modoboa/imap_migration/management/commands/generate_offlineimap_config.py
new file mode 100644
index 000000000..6e3cdc90a
--- /dev/null
+++ b/modoboa/imap_migration/management/commands/generate_offlineimap_config.py
@@ -0,0 +1,44 @@
+"""A management command to create an offlineimap configuration file."""
+
+import os
+import stat
+
+from django.core.management.base import BaseCommand
+from django.template.loader import render_to_string
+
+from modoboa.admin.app_settings import load_admin_settings
+from modoboa.parameters import tools as param_tools
+
+from ...models import Migration
+
+
+class Command(BaseCommand):
+ """Command definition."""
+
+ help = "Generate an offlineimap configuration file."
+
+ def add_arguments(self, parser):
+ """Add extra arguments to command line."""
+ parser.add_argument(
+ "--output", default="/tmp/offlineimap.conf",
+ help="Path of the generated file")
+
+ def handle(self, *args, **options):
+ """Entry point."""
+ load_admin_settings()
+ conf = dict(
+ param_tools.get_global_parameters("imap_migration"))
+ context = {
+ "imap_create_folders": conf["create_folders"],
+ "imap_folder_filter_exclude": conf["folder_filter_exclude"],
+ "imap_folder_filter_include": ", ".join(
+ "'{0}'".format(w)
+ for w in conf["folder_filter_include"].split(",")),
+ "migrations": Migration.objects.select_related(
+ "provider", "mailbox__domain"),
+ }
+ with open(options["output"], "w") as fpo:
+ content = render_to_string(
+ "imap_migration/offlineimap.conf", context)
+ fpo.write(content)
+ os.chmod(options["output"], stat.S_IRUSR | stat.S_IWUSR)
diff --git a/modoboa/imap_migration/migrations/0001_initial.py b/modoboa/imap_migration/migrations/0001_initial.py
new file mode 100644
index 000000000..416ae4c91
--- /dev/null
+++ b/modoboa/imap_migration/migrations/0001_initial.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('admin', '__first__'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Migration',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('_password', models.CharField(max_length=100)),
+ ('mailbox', models.ForeignKey(to='admin.Mailbox', on_delete=models.CASCADE)),
+ ],
+ options={
+ },
+ bases=(models.Model,),
+ ),
+ ]
diff --git a/modoboa/imap_migration/migrations/0002_password.py b/modoboa/imap_migration/migrations/0002_password.py
new file mode 100644
index 000000000..83c455926
--- /dev/null
+++ b/modoboa/imap_migration/migrations/0002_password.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('imap_migration', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='migration',
+ name='_password',
+ field=models.CharField(max_length=255),
+ ),
+ ]
diff --git a/modoboa/imap_migration/migrations/0003_auto_20190207_0919.py b/modoboa/imap_migration/migrations/0003_auto_20190207_0919.py
new file mode 100644
index 000000000..eb0867b77
--- /dev/null
+++ b/modoboa/imap_migration/migrations/0003_auto_20190207_0919.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.18 on 2019-02-07 08:19
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+def create_default_provider(apps, schema_editor):
+ """Create default provider based on old settings."""
+ EmailProvider = apps.get_model("imap_migration", "EmailProvider")
+ Migration = apps.get_model("imap_migration", "Migration")
+ LocalConfig = apps.get_model("core", "LocalConfig")
+ lc = LocalConfig.objects.first()
+ if not lc:
+ return
+ params = lc._parameters.get("imap_migration")
+ if params is None:
+ return
+ provider = EmailProvider.objects.create(
+ name="Default",
+ address=params["server_address"],
+ port=params["server_port"],
+ secured=params["secured"]
+ )
+ for migration in Migration.objects.select_related("mailbox__domain"):
+ migration.provider = provider
+ migration.username = (
+ migration.mailbox.address + "@" + migration.mailbox.domain.name)
+ migration.save(update_fields=["provider", "username"])
+
+
+def backwards(apps, schema_editor):
+ pass
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('admin', '0014_auto_20181017_1628'),
+ ('imap_migration', '0002_password'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='EmailProvider',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=50)),
+ ('address', models.CharField(max_length=200)),
+ ('port', models.PositiveIntegerField(default=143)),
+ ('secured', models.BooleanField(default=False)),
+ ],
+ options={
+ 'ordering': ['name'],
+ },
+ ),
+ migrations.CreateModel(
+ name='EmailProviderDomain',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100, unique=True)),
+ ('new_domain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='admin.Domain')),
+ ('provider', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='imap_migration.EmailProvider')),
+ ],
+ options={
+ 'ordering': ['provider', 'name'],
+ },
+ ),
+ migrations.AddField(
+ model_name='migration',
+ name='username',
+ field=models.CharField(max_length=254, null=True),
+ ),
+ migrations.AddField(
+ model_name='migration',
+ name='provider',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='imap_migration.EmailProvider'),
+ ),
+ migrations.RunPython(create_default_provider, backwards)
+ ]
diff --git a/modoboa/imap_migration/migrations/0004_auto_20190207_0939.py b/modoboa/imap_migration/migrations/0004_auto_20190207_0939.py
new file mode 100644
index 000000000..4b1f5f59b
--- /dev/null
+++ b/modoboa/imap_migration/migrations/0004_auto_20190207_0939.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.18 on 2019-02-07 08:39
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('imap_migration', '0003_auto_20190207_0919'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='migration',
+ name='provider',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='imap_migration.EmailProvider'),
+ ),
+ migrations.AlterField(
+ model_name='migration',
+ name='username',
+ field=models.CharField(max_length=254),
+ ),
+ ]
diff --git a/modoboa/imap_migration/migrations/0005_auto_20210401_0938.py b/modoboa/imap_migration/migrations/0005_auto_20210401_0938.py
new file mode 100644
index 000000000..f49ac7383
--- /dev/null
+++ b/modoboa/imap_migration/migrations/0005_auto_20210401_0938.py
@@ -0,0 +1,24 @@
+# Generated by Django 2.2.12 on 2021-04-01 07:38
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('imap_migration', '0004_auto_20190207_0939'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='emailproviderdomain',
+ name='provider',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='domains', to='imap_migration.EmailProvider'),
+ ),
+ migrations.AlterField(
+ model_name='migration',
+ name='username',
+ field=models.CharField(max_length=254, unique=True),
+ ),
+ ]
diff --git a/modoboa/imap_migration/migrations/__init__.py b/modoboa/imap_migration/migrations/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/imap_migration/models.py b/modoboa/imap_migration/models.py
new file mode 100644
index 000000000..d3660acfa
--- /dev/null
+++ b/modoboa/imap_migration/models.py
@@ -0,0 +1,55 @@
+"""
+IMAP migration models.
+"""
+
+from django.db import models
+
+from modoboa.admin.models import Mailbox
+from modoboa.lib.cryptutils import encrypt, decrypt
+
+
+class EmailProvider(models.Model):
+ """Email provider model."""
+
+ name = models.CharField(max_length=50)
+ address = models.CharField(max_length=200)
+ port = models.PositiveIntegerField(default=143)
+ secured = models.BooleanField(default=False)
+
+ class Meta:
+ ordering = ["name"]
+
+
+class EmailProviderDomain(models.Model):
+ """Email provider domain class."""
+
+ provider = models.ForeignKey(
+ EmailProvider, on_delete=models.CASCADE, related_name="domains")
+ name = models.CharField(max_length=100, unique=True)
+ new_domain = models.ForeignKey(
+ "admin.Domain", on_delete=models.SET_NULL, null=True, blank=True)
+
+ class Meta:
+ ordering = ["provider", "name"]
+
+
+class Migration(models.Model):
+ """Represent mailboxes to migrate."""
+
+ provider = models.ForeignKey(EmailProvider, on_delete=models.CASCADE)
+ mailbox = models.ForeignKey(Mailbox, on_delete=models.CASCADE)
+ username = models.CharField(max_length=254, unique=True)
+ _password = models.CharField(max_length=255)
+
+ def __str__(self):
+ return self.username
+
+ @property
+ def password(self):
+ """Password getter."""
+ return decrypt(self._password)
+
+ @password.setter
+ def password(self, value):
+ """Password setter."""
+ self._password = encrypt(value)
diff --git a/modoboa/imap_migration/templates/imap_migration/index.html b/modoboa/imap_migration/templates/imap_migration/index.html
new file mode 100644
index 000000000..2ce37d2f9
--- /dev/null
+++ b/modoboa/imap_migration/templates/imap_migration/index.html
@@ -0,0 +1,27 @@
+{% extends "fluid.html" %}
+
+{% load i18n lib_tags static %}
+{% load render_bundle from webpack_loader %}
+
+{% block pagetitle %}{% trans "IMAP migration" %}{% endblock %}
+
+{% block extra_css %}
+ {% if not debug %}
+ {% render_bundle 'chunk-vendors' 'css' 'IMAP_MIGRATION' %}
+ {% render_bundle 'app' 'css' 'IMAP_MIGRATION' %}
+ {% endif %}
+{% endblock %}
+
+{% block extra_js %}
+ {% if not debug %}
+ {% render_bundle 'chunk-vendors' 'js' 'IMAP_MIGRATION' %}
+ {% endif %}
+ {% render_bundle 'app' 'js' 'IMAP_MIGRATION' %}
+{% endblock %}
+
+{% block container_content %}
+
+
+
+
+{% endblock %}
diff --git a/modoboa/imap_migration/templates/imap_migration/offlineimap.conf b/modoboa/imap_migration/templates/imap_migration/offlineimap.conf
new file mode 100644
index 000000000..e6db6e1a5
--- /dev/null
+++ b/modoboa/imap_migration/templates/imap_migration/offlineimap.conf
@@ -0,0 +1,40 @@
+[general]
+accounts = {{ migrations|join:", " }}
+
+{% for migration in migrations %}
+[Account {{ migration }}]
+localrepository = Local_{{ migration }}
+remoterepository = Remote_{{ migration }}
+
+[Repository Local_{{ migration }}]
+type = IMAP
+ssl = false
+remotehost = localhost
+remoteuser = {{ migration }}
+remotepass = {{ migration.password }}
+{% if imap_create_folders == False %}
+createfolders = False
+{% endif %}
+
+[Repository Remote_{{ migration }}]
+type = IMAP
+remotehost = {{ migration.provider.address }}
+remoteport = {{ migration.provider.port }}
+{% if migration.provider.secured %}
+ssl = yes
+sslcacertfile = /etc/ssl/certs/ca-certificates.crt
+{% else %}
+ssl = no
+{% endif %}
+remoteuser = {{ migration }}
+remotepass = {{ migration.password }}
+readonly = true
+{% autoescape off %}
+{% if imap_folder_filter_exclude|length > 0 %}
+folderfilter = lambda folder: not re.search('({{ imap_folder_filter_exclude }})', folder)
+{% endif %}
+{% if imap_folder_filter_include|length > 2 %}
+folderincludes = [{{ imap_folder_filter_include }}]
+{% endif %}
+{% endautoescape %}
+{% endfor %}
diff --git a/modoboa/imap_migration/tests.py b/modoboa/imap_migration/tests.py
new file mode 100644
index 000000000..57128d4ad
--- /dev/null
+++ b/modoboa/imap_migration/tests.py
@@ -0,0 +1,204 @@
+"""IMAP migration tests."""
+
+import os
+import shutil
+import tempfile
+from unittest import mock
+
+from six.moves import configparser
+
+from django.core.management import call_command
+from django.urls import reverse
+
+from modoboa.admin import factories as admin_factories
+from modoboa.admin import models as admin_models
+from modoboa.core import factories as core_factories
+from modoboa.core import models as core_models
+from modoboa.lib.tests import ModoTestCase, ModoAPITestCase
+
+from . import checks
+from . import factories
+from . import models
+
+
+class DataMixin(object):
+ """A mixin to provide test data."""
+
+ @classmethod
+ def setUpTestData(cls):
+ super(DataMixin, cls).setUpTestData()
+ admin_factories.populate_database()
+ cls.mb = admin_models.Mailbox.objects.get(
+ user__username="user@test.com")
+ cls.migration = factories.MigrationFactory(
+ password="Toto1234", mailbox=cls.mb, username="user@test.com")
+
+
+# class ViewsTestCase(DataMixin, ModoTestCase):
+# """Views test cases."""
+
+# def test_index(self):
+# """Test index view."""
+# url = reverse("modoboa_imap_migration:index")
+# response = self.client.get(url)
+# self.assertContains(response, '')
+
+
+class AuthenticationTestCase(DataMixin, ModoTestCase):
+ """IMAP authentication test case."""
+
+ @mock.patch("imaplib.IMAP4")
+ def test_authentication(self, mock_imap):
+ """Check IMAP authentication."""
+ factories.EmailProviderDomainFactory(name="test.com")
+ mock_imap.return_value.login.return_value = ["OK", b""]
+ url = reverse("core:login")
+ data = {"username": "new_user@test.com", "password": "Toto1234"}
+ response = self.client.post(url, data)
+ self.assertEqual(response.status_code, 302)
+ user = core_models.User.objects.get(username="new_user@test.com")
+ self.assertEqual(user.mailbox.domain.name, "test.com")
+
+ mock_imap.return_value.login.return_value = ["NO", b"Error"]
+ data = {"username": "new_user@test.com", "password": "Toto123"}
+ response = self.client.post(url, data)
+ self.assertEqual(response.status_code, 401)
+
+ mock_imap.return_value.login.return_value = ["OK", b""]
+ data = {"username": "new_user@test2.com", "password": "Toto1234"}
+ response = self.client.post(url, data)
+ self.assertEqual(response.status_code, 401)
+
+ self.set_global_parameter(
+ "auto_create_domain_and_mailbox", False, app="admin")
+ data = {"username": "new_user2@test.com", "password": "Toto1234"}
+ response = self.client.post(url, data)
+ self.assertEqual(response.status_code, 401)
+
+ @mock.patch("imaplib.IMAP4")
+ def test_authenticate_and_rename(self, mock_imap):
+ """Check address renaming."""
+ domain = admin_models.Domain.objects.get(name="test.com")
+ factories.EmailProviderDomainFactory(
+ name="gmail.com", new_domain=domain)
+ mock_imap.return_value.login.return_value = ["OK", b""]
+ url = reverse("core:login")
+ data = {"username": "new_user@gmail.com", "password": "Toto1234"}
+ response = self.client.post(url, data)
+ self.assertEqual(response.status_code, 302)
+ user = core_models.User.objects.get(username="new_user@test.com")
+ self.assertEqual(user.mailbox.domain.name, "test.com")
+
+ @mock.patch("imaplib.IMAP4")
+ def test_authenticate_conflicts(self, mock_imap):
+ """Check potential conflicts."""
+ core_factories.UserFactory(
+ username="admin2@test.com", groups=("DomainAdmins", ),
+ password="{PLAIN}toto"
+ )
+ domain = admin_models.Domain.objects.get(name="test.com")
+ factories.EmailProviderDomainFactory(
+ name="gmail.com", new_domain=domain)
+ mock_imap.return_value.login.return_value = ["OK", b""]
+ url = reverse("core:login")
+ data = {"username": "admin@gmail.com", "password": "Toto1234"}
+ response = self.client.post(url, data)
+ self.assertEqual(response.status_code, 401)
+
+ data = {"username": "admin2@gmail.com", "password": "Toto1234"}
+ response = self.client.post(url, data)
+ self.assertEqual(response.status_code, 401)
+
+
+class ManagementCommandTestCase(DataMixin, ModoTestCase):
+ """Management command test cases."""
+
+ def setUp(self):
+ super(ManagementCommandTestCase, self).setUp()
+ self.workdir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ shutil.rmtree(self.workdir)
+
+ def test_generate_offlineimap_config(self):
+ """Test generate_offlineimap_config command."""
+ 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)
+ self.assertTrue(conf.has_section("Account user@test.com"))
+
+
+class ViewSetTestCase(DataMixin, ModoAPITestCase):
+ """ViewSet related tests."""
+
+ def test_create_provider(self):
+ url = reverse("api:emailprovider-list")
+ data = {
+ "name": "Google",
+ "address": "imap.google.com",
+ "port": 143,
+ "secured": True,
+ "domains": [
+ {"id": 1000,
+ "name": "gmail.com"}
+ ]
+ }
+ response = self.client.post(url, data, format="json")
+ self.assertEqual(response.status_code, 201)
+ self.assertTrue(
+ models.EmailProviderDomain.objects.filter(name="gmail.com")
+ .exists()
+ )
+
+ def test_update_provider(self):
+ provider_domain = factories.EmailProviderDomainFactory(
+ name="outlook.com")
+ url = reverse(
+ "api:emailprovider-detail", args=[provider_domain.provider.pk])
+ data = {
+ "id": provider_domain.provider.id,
+ "name": provider_domain.provider.name,
+ "address": provider_domain.provider.address,
+ "port": provider_domain.provider.port,
+ "domains": [
+ {"id": provider_domain.id,
+ "name": "hotmail.com",
+ "new_domain": None},
+ {"name": "gmail.com",
+ "new_domain": admin_models.Domain.objects.get(
+ name="test.com").pk}
+ ]
+ }
+ response = self.client.put(url, data, format="json")
+ self.assertEqual(response.status_code, 200)
+ provider_domain.refresh_from_db()
+ self.assertEqual(provider_domain.name, data["domains"][0]["name"])
+ self.assertTrue(
+ models.EmailProviderDomain.objects.filter(name="gmail.com")
+ .exists()
+ )
+ data["domains"].pop()
+ response = self.client.put(url, data, format="json")
+ self.assertEqual(response.status_code, 200)
+ self.assertFalse(
+ models.EmailProviderDomain.objects.filter(name="gmail.com")
+ .exists()
+ )
+
+
+class ChecksTestCase(ModoTestCase):
+ """Deploy checks test case."""
+
+ def test_auto_creation_check(self):
+ self.assertEqual(
+ checks.check_auto_creation_is_enabled(None),
+ []
+ )
+ self.set_global_parameter(
+ "auto_create_domain_and_mailbox", False, app="admin")
+ self.assertEqual(
+ checks.check_auto_creation_is_enabled(None),
+ [checks.W001]
+ )
diff --git a/modoboa/imap_migration/urls.py b/modoboa/imap_migration/urls.py
new file mode 100644
index 000000000..3c51b274d
--- /dev/null
+++ b/modoboa/imap_migration/urls.py
@@ -0,0 +1,11 @@
+"""Custom urls."""
+
+from django.urls import path
+
+from . import views
+
+app_name = "imap_migration"
+
+urlpatterns = [
+ path("", views.IndexView.as_view(), name="index"),
+]
diff --git a/modoboa/imap_migration/views.py b/modoboa/imap_migration/views.py
new file mode 100644
index 000000000..d50cf61d3
--- /dev/null
+++ b/modoboa/imap_migration/views.py
@@ -0,0 +1,19 @@
+"""Views for the IMAP migration extension."""
+
+from django.contrib.auth import mixins as auth_mixins
+
+from django.views import generic
+
+
+class IndexView(auth_mixins.LoginRequiredMixin,
+ auth_mixins.PermissionRequiredMixin,
+ generic.TemplateView):
+ """Index view."""
+
+ permission_required = "modoboa.imap_migration.add_migration"
+ template_name = "modoboa.imap_migration/index.html"
+
+ def get_context_data(self, **kwargs):
+ """Set menu selection."""
+ kwargs.update({"selection": "imap_migrations"})
+ return super(IndexView, self).get_context_data(**kwargs)
diff --git a/modoboa/parameters/api/v2/tests.py b/modoboa/parameters/api/v2/tests.py
index 60d663fa2..a4ffc6e3c 100644
--- a/modoboa/parameters/api/v2/tests.py
+++ b/modoboa/parameters/api/v2/tests.py
@@ -12,7 +12,7 @@ class ParametersAPITestCase(ModoAPITestCase):
resp = self.client.get(url)
self.assertEqual(resp.status_code, 200)
# core, admin, limits, pdf credentials
- self.assertEqual(len(resp.json()), 5)
+ self.assertEqual(len(resp.json()), 6)
def test_get_structure(self):
url = reverse("v2:parameter-structure")
diff --git a/modoboa/urls.py b/modoboa/urls.py
index d30c04bc9..9eaee8196 100644
--- a/modoboa/urls.py
+++ b/modoboa/urls.py
@@ -30,6 +30,7 @@ urlpatterns = [
path('pdfcredentials', include("modoboa.pdfcredentials.urls")),
path('stats/', include("modoboa.maillog.urls")),
path('dmarc/', include("modoboa.dmarc.urls")),
+ path('imap_migration/', include("modoboa.imap_migration.urls")),
path('user/forward/', user_views.forward, name="user_forward"),
diff --git a/modoboa/urls_api_v1.py b/modoboa/urls_api_v1.py
index 7697ba96b..2077cec23 100644
--- a/modoboa/urls_api_v1.py
+++ b/modoboa/urls_api_v1.py
@@ -9,6 +9,7 @@ app_name = "api"
urlpatterns = [
path('', include("modoboa.core.api.v1.urls")),
path('', include("modoboa.admin.api.v1.urls")),
+ path('', include("modoboa.imap_migration.api.v1.urls")),
path('', include("modoboa.limits.api.v1.urls")),
path('', include("modoboa.relaydomains.api.v1.urls"))
]
diff --git a/modoboa/urls_api_v2.py b/modoboa/urls_api_v2.py
index 4e95c8350..91ab85b39 100644
--- a/modoboa/urls_api_v2.py
+++ b/modoboa/urls_api_v2.py
@@ -10,13 +10,14 @@ urlpatterns = [
path('', include("modoboa.core.api.v2.urls")),
path('', include("modoboa.admin.api.v2.urls")),
path('', include("modoboa.parameters.api.v2.urls")),
+ path('', include("modoboa.imap_migration.api.v2.urls")),
path('', include("modoboa.limits.api.v1.urls")),
path('', include("modoboa.relaydomains.api.v1.urls")),
path('', include("modoboa.dmarc.api.v2.urls")),
path('', include("modoboa.dnstools.api.v2.urls")),
path('', include("modoboa.maillog.api.v2.urls")),
path('', include("modoboa.transport.api.v2.urls")),
- path('', include("modoboa.pdfcredentials.api.v2.urls")),
+ path('', include("modoboa.pdfcredentials.api.v2.urls"))
]
urlpatterns += exts_pool.get_urls(category="api")
diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py
index 1f7e5aa6c..e742d6931 100644
--- a/test_project/test_project/settings.py
+++ b/test_project/test_project/settings.py
@@ -91,6 +91,7 @@ MODOBOA_APPS = (
'modoboa.maillog',
'modoboa.pdfcredentials',
'modoboa.dmarc',
+ 'modoboa.imap_migration',
# Modoboa extensions here.
)
@@ -126,6 +127,7 @@ AUTHENTICATION_BACKENDS = (
# 'modoboa.lib.authbackends.LDAPBackend',
# 'modoboa.lib.authbackends.SMTPBackend',
'django.contrib.auth.backends.ModelBackend',
+ 'modoboa.imap_migration.auth_backends.IMAPBackend',
)
# SMTP authentication