From 094873ad6f3c5c12e27d166d2a36717357b3524e Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Thu, 18 Jul 2024 21:07:31 +0200 Subject: [PATCH 1/6] Fixed load_initial_data --- .../management/commands/load_initial_data.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/modoboa/core/management/commands/load_initial_data.py b/modoboa/core/management/commands/load_initial_data.py index ae51e60b3..99874903f 100644 --- a/modoboa/core/management/commands/load_initial_data.py +++ b/modoboa/core/management/commands/load_initial_data.py @@ -112,12 +112,17 @@ class Command(BaseCommand): allowed_host = input("What will be the hostname used to access Modoboa? ") if not allowed_host: allowed_host = "localhost" - frontend_application = app_model.objects.filter(name="Modoboa frontend") + frontend_application_qs = app_model.objects.all() + frontend_application = None + for app in frontend_application_qs: + if app.name == "Modoboa frontend": + frontend_application = app + break frontend_path = getattr(settings, "NEW_ADMIN_URL", "new-admin") base_uri = f"https://{allowed_host}/{frontend_path}" redirect_uri = f"{base_uri}/login/logged" client_id = "" - if not frontend_application.exists(): + if frontend_application is None: if options["dev"]: base_uri = "https://localhost:3000/" redirect_uri = "https://localhost:3000/login/logged" @@ -136,13 +141,14 @@ class Command(BaseCommand): "authorization-code", ) else: - client_id = frontend_application.first().client_id + client_id = frontend_application.client_id base_frontend_dir = os.path.join( - os.path.dirname(__file__), "../../frontend_dist/" + os.path.dirname(__file__), "../../../frontend_dist/" ) - frontend_target_dir = "{}/frontend".format(options["name"]) + frontend_target_dir = "{}/frontend".format(settings.BASE_DIR) if os.path.exists(base_frontend_dir): + shutil.rmtree(frontend_target_dir, ignore_errors=True) shutil.copytree(base_frontend_dir, frontend_target_dir) with open(f"{frontend_target_dir}/config.json", "w") as fp: fp.write( @@ -150,7 +156,7 @@ class Command(BaseCommand): "API_BASE_URL": "https://{allowed_host}/api/v2", "OAUTH_AUTHORITY_URL": "https://{allowed_host}/api/o", "OAUTH_CLIENT_ID": "{client_id}", - "OAUTH_REDIRECT_URI": "{redirect_uri}" + "OAUTH_REDIRECT_URI": "{redirect_uri}", "OAUTH_POST_REDIRECT_URI": "{base_uri}" }} """ From bb3b5dfd7657a5b7e45b7099789b8383c59255c8 Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Thu, 18 Jul 2024 21:14:02 +0200 Subject: [PATCH 2/6] Updated doc --- doc/upgrade.rst | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/doc/upgrade.rst b/doc/upgrade.rst index 06e648e06..e9c715cba 100644 --- a/doc/upgrade.rst +++ b/doc/upgrade.rst @@ -20,6 +20,7 @@ version only requires a few actions. In every case, you will need to apply the specific actions if the version you are installing requires it, and then apply the general ones. +.. _post_upgrade_commands: In case you use a dedicated user and/or a virtualenv, do not forget to use them: @@ -37,6 +38,7 @@ Then, run the following commands: > python manage.py migrate > python manage.py collectstatic > python manage.py check --deploy + > python manage.py load_initial_data Once done, check if the version you are installing requires :ref:`specific_upgrade_instructions`. @@ -146,7 +148,7 @@ Version 2.3.0 Pre update ---------- - +@TODO : Update this with SQL changes Before your start upgrading modoboa, run the following commands from your virtual environment: @@ -259,7 +261,21 @@ by default. 'modoboa.lib.middleware.RequestCatcherMiddleware', ) -- Add the following content after ``MEDIA_ROOT``: +- Add ``'oauth2_provider.contrib.rest_framework.OAuth2Authentication'`` + at the top of the list of ``DEFAULT_AUTHENTICATION_CLASSES`` + (inside ``REST_FRAMEWORK`` section): + + .. sourcecode:: python + + 'DEFAULT_AUTHENTICATION_CLASSES': ( + 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', + 'rest_framework.authentication.TokenAuthentication', + 'rest_framework.authentication.SessionAuthentication', + ), + + +- Run the commands indacated :ref:`in the first section ` + then add the following content after ``MEDIA_ROOT``: .. sourcecode:: python @@ -282,19 +298,6 @@ by default. # If CORS fail, you might want to try to set it to True #CORS_ORIGIN_ALLOW_ALL = False -- Add ``'oauth2_provider.contrib.rest_framework.OAuth2Authentication'`` - at the top of the list of ``DEFAULT_AUTHENTICATION_CLASSES`` - (inside ``REST_FRAMEWORK`` section): - - .. sourcecode:: python - - 'DEFAULT_AUTHENTICATION_CLASSES': ( - 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', - 'rest_framework.authentication.TokenAuthentication', - 'rest_framework.authentication.SessionAuthentication', - ), - - SORBS DNS --------- From 9d923ae3e76ab8afb774d6b5458846a67223055b Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Thu, 18 Jul 2024 21:30:56 +0200 Subject: [PATCH 3/6] Updated RSA key gen for OIDC --- modoboa/core/checks.py | 10 ++++++++++ modoboa/core/commands/deploy.py | 21 ++------------------ modoboa/core/utils.py | 35 +++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 19 deletions(-) create mode 100644 modoboa/core/checks.py diff --git a/modoboa/core/checks.py b/modoboa/core/checks.py new file mode 100644 index 000000000..a4f0f5bf6 --- /dev/null +++ b/modoboa/core/checks.py @@ -0,0 +1,10 @@ +from django.core.checks import register, Info +from django.conf import settings + +from modoboa.core.utils import generate_rsa_private_key + + +@register(deploy=True) +def generate_rsa_private_key_check(app_configs, **kwargs): + if generate_rsa_private_key(settings.BASE_DIR): + return Info("Generated the RSA key for OIDC.") diff --git a/modoboa/core/commands/deploy.py b/modoboa/core/commands/deploy.py index 76a2fd7b7..16954c381 100644 --- a/modoboa/core/commands/deploy.py +++ b/modoboa/core/commands/deploy.py @@ -9,8 +9,6 @@ import string import subprocess import sys -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import rsa import dj_database_url import django @@ -19,6 +17,7 @@ from django.template import Context, Template from django.utils.encoding import smart_str from modoboa.core.commands import Command +from modoboa.core.utils import generate_rsa_private_key from modoboa.lib.api_client import ModoAPIClient from modoboa.lib.sysutils import exec_cmd @@ -199,22 +198,6 @@ class DeployCommand(Command): extra_settings.append(extension[1]) return extra_settings - def generate_rsa_private_key(self, storage_path: str): - """Generate RSA private key for OIDC support.""" - private_key = rsa.generate_private_key( - public_exponent=65537, - key_size=4096, - ) - pem = private_key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption(), - ) - content = bytes(pem) - content = content.replace(b"\n", b"\\n") - with open(f"{storage_path}/.env", "wb") as fp: - fp.write(b'OIDC_RSA_PRIVATE_KEY="' + content + b'"\n') - def handle(self, parsed_args): django.setup() management.call_command("startproject", parsed_args.name, verbosity=False) @@ -285,7 +268,7 @@ class DeployCommand(Command): ) with open("%s/settings.py" % path, "w") as fp: fp.write(tpl) - self.generate_rsa_private_key(parsed_args.name) + generate_rsa_private_key(parsed_args.name) shutil.copyfile("%s/urls.py.tpl" % self._templates_dir, "%s/urls.py" % path) os.mkdir("%s/media" % parsed_args.name) diff --git a/modoboa/core/utils.py b/modoboa/core/utils.py index 1ef002d64..22e0371a3 100644 --- a/modoboa/core/utils.py +++ b/modoboa/core/utils.py @@ -1,6 +1,11 @@ """Utility functions.""" from pkg_resources import parse_version +import os +import environ + +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa from django.utils.translation import gettext as _ @@ -49,3 +54,33 @@ def check_for_updates(): update_avail = True break return update_avail, extensions + + +def generate_rsa_private_key(storage_path: str) -> bool: + """Generate RSA private key for OIDC support. + + :return: False if the key was not generated + and True if it was.""" + + env_path = os.path.join(storage_path, ".env") + + env = environ.Env(OIDC_RSA_PRIVATE_KEY=(str, "NONE")) + environ.Env.read_env(env_path) + + if env("OIDC_RSA_PRIVATE_KEY") != "NONE": + return False + + private_key = rsa.generate_private_key( + public_exponent=65537, + key_size=4096, + ) + pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption(), + ) + content = bytes(pem) + content = content.replace(b"\n", b"\\n") + with open(env_path, "wb") as fp: + fp.write(b'OIDC_RSA_PRIVATE_KEY="' + content + b'"\n') + return True From 52ff2f216a707e19fdf64ab85abb62e01f02d021 Mon Sep 17 00:00:00 2001 From: Spitfireap Date: Thu, 18 Jul 2024 21:40:22 +0200 Subject: [PATCH 4/6] Updated doc --- doc/upgrade.rst | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/doc/upgrade.rst b/doc/upgrade.rst index e9c715cba..99a755976 100644 --- a/doc/upgrade.rst +++ b/doc/upgrade.rst @@ -20,7 +20,6 @@ version only requires a few actions. In every case, you will need to apply the specific actions if the version you are installing requires it, and then apply the general ones. -.. _post_upgrade_commands: In case you use a dedicated user and/or a virtualenv, do not forget to use them: @@ -29,6 +28,13 @@ use them: > sudo -u -i bash > source /bin/activate + +.. _post_upgrade_commands: + +.. note:: + + Check the :ref:`specific_upgrade_instructions` before running the following commands. + Then, run the following commands: .. sourcecode:: bash @@ -148,8 +154,8 @@ Version 2.3.0 Pre update ---------- -@TODO : Update this with SQL changes -Before your start upgrading modoboa, run the following commands from +@TODO : Update this with SQL changes, clarify "before you start upgrading modoboa" +Before you start upgrading modoboa, run the following commands from your virtual environment: .. sourcecode:: bash @@ -274,7 +280,7 @@ by default. ), -- Run the commands indacated :ref:`in the first section ` +- Run the commands indicated :ref:`in the first section ` then add the following content after ``MEDIA_ROOT``: .. sourcecode:: python @@ -986,7 +992,7 @@ This release introduces an optional LDAP synchronization process. If you want to use it, please follow the :ref:`dedicated procedure `. Version 1.13.1 -======= +============== Upgrade postfix maps files as follows: From 9ec349cf666a73f9d8f32cd86995291da98f817a Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Fri, 19 Jul 2024 10:13:42 +0200 Subject: [PATCH 5/6] Fixed deploy command --- modoboa/core/checks.py | 34 +++++++++++++++++++++++--- modoboa/core/checks/__init__.py | 2 -- modoboa/core/checks/settings_checks.py | 26 -------------------- modoboa/core/utils.py | 3 ++- 4 files changed, 33 insertions(+), 32 deletions(-) delete mode 100644 modoboa/core/checks/__init__.py delete mode 100644 modoboa/core/checks/settings_checks.py diff --git a/modoboa/core/checks.py b/modoboa/core/checks.py index a4f0f5bf6..60237af77 100644 --- a/modoboa/core/checks.py +++ b/modoboa/core/checks.py @@ -1,10 +1,38 @@ -from django.core.checks import register, Info +from django.core.checks import register, Info, Warning from django.conf import settings +from django.utils.translation import gettext as _ from modoboa.core.utils import generate_rsa_private_key +W001 = Warning( + _( + "You have USE_TZ set to False, this may result in issues during " + "transitions between summer/winter time (ie the same local time occuring " + "twice due to clock change)." + ), + hint=_("Set `USE_TZ = True` in settings.py"), + id="modoboa.W001", +) + + @register(deploy=True) -def generate_rsa_private_key_check(app_configs, **kwargs): +def check_use_tz_enabled(app_configs, **kwargs): + """Ensure USE_TZ is enabled in settings.py + + When USE_TZ is enabled all date/times are stored in UTC. + Fixes #1086 - https://github.com/modoboa/modoboa/issues/1086 + """ + errors = [] + if not settings.USE_TZ: + errors.append(W001) + return errors + + +@register(deploy=True) +def check_rsa_private_key_exists(app_configs, **kwargs): + """ + Ensure an RSA private key exists to enable OIDC. + """ if generate_rsa_private_key(settings.BASE_DIR): - return Info("Generated the RSA key for OIDC.") + return Info("An RSA private key has been generated for OIDC.") diff --git a/modoboa/core/checks/__init__.py b/modoboa/core/checks/__init__.py deleted file mode 100644 index 9b716b17c..000000000 --- a/modoboa/core/checks/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Import these to force registration of checks -from . import settings_checks # NOQA:F401 diff --git a/modoboa/core/checks/settings_checks.py b/modoboa/core/checks/settings_checks.py deleted file mode 100644 index f72969226..000000000 --- a/modoboa/core/checks/settings_checks.py +++ /dev/null @@ -1,26 +0,0 @@ -from django.conf import settings -from django.core.checks import Warning, register -from django.utils.translation import gettext as _ - -W001 = Warning( - _( - "You have USE_TZ set to False, this may result in issues during " - "transitions between summer/winter time (ie the same local time occuring " - "twice due to clock change)." - ), - hint=_("Set `USE_TZ = True` in settings.py"), - id="modoboa.W001", -) - - -@register(deploy=True) -def check_use_tz_enabled(app_configs, **kwargs): - """Ensure USE_TZ is enabled in settings.py - - When USE_TZ is enabled all date/times are stored in UTC. - Fixes #1086 - https://github.com/modoboa/modoboa/issues/1086 - """ - errors = [] - if not settings.USE_TZ: - errors.append(W001) - return errors diff --git a/modoboa/core/utils.py b/modoboa/core/utils.py index 22e0371a3..f096b3d63 100644 --- a/modoboa/core/utils.py +++ b/modoboa/core/utils.py @@ -11,7 +11,6 @@ from django.utils.translation import gettext as _ from modoboa.core.extensions import exts_pool from modoboa.lib.api_client import ModoAPIClient -from . import models def parse_map_file(path): @@ -28,6 +27,8 @@ def parse_map_file(path): def check_for_updates(): """Check if a new version of Modoboa is available.""" + from . import models + local_config = models.LocalConfig.objects.first() client = ModoAPIClient() extensions = exts_pool.list_all() From 36d78582c6f545b72f2187daf9c39e34f455857d Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Fri, 19 Jul 2024 10:34:28 +0200 Subject: [PATCH 6/6] Added test for rsa check --- modoboa/core/checks.py | 4 +++- modoboa/core/tests/test_checks.py | 29 ++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/modoboa/core/checks.py b/modoboa/core/checks.py index 60237af77..fb445a9e7 100644 --- a/modoboa/core/checks.py +++ b/modoboa/core/checks.py @@ -34,5 +34,7 @@ def check_rsa_private_key_exists(app_configs, **kwargs): """ Ensure an RSA private key exists to enable OIDC. """ + msgs = [] if generate_rsa_private_key(settings.BASE_DIR): - return Info("An RSA private key has been generated for OIDC.") + msgs.append(Info("An RSA private key has been generated for OIDC.")) + return msgs diff --git a/modoboa/core/tests/test_checks.py b/modoboa/core/tests/test_checks.py index 02bdfeb60..b3beda5dd 100644 --- a/modoboa/core/tests/test_checks.py +++ b/modoboa/core/tests/test_checks.py @@ -1,18 +1,37 @@ +import os +import shutil +import tempfile + from django.test import SimpleTestCase from django.test.utils import override_settings -from ..checks import settings_checks +from modoboa.core import checks class CheckSessionCookieSecureTest(SimpleTestCase): + + def setUp(self): + self.workdir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.workdir) + @override_settings(USE_TZ=False) def test_use_tz_false(self): """If USE_TZ is off provide one warning.""" - self.assertEqual( - settings_checks.check_use_tz_enabled(None), [settings_checks.W001] - ) + self.assertEqual(checks.check_use_tz_enabled(None), [checks.W001]) @override_settings(USE_TZ=True) def test_use_tz_true(self): """If USE_TZ is on, there's no warning about it.""" - self.assertEqual(settings_checks.check_use_tz_enabled(None), []) + self.assertEqual(checks.check_use_tz_enabled(None), []) + + def test_rsa_private_key_exists(self): + with override_settings(BASE_DIR=self.workdir): + os.environ["OIDC_RSA_PRIVATE_KEY"] = "NONE" + msgs = checks.check_rsa_private_key_exists(None) + self.assertEqual(len(msgs), 1) + self.assertTrue(os.path.exists(f"{self.workdir}/.env")) + os.environ["OIDC_RSA_PRIVATE_KEY"] = "KEY" + msgs = checks.check_rsa_private_key_exists(None) + self.assertEqual(len(msgs), 0)