""" Django system checks for Alliance Auth """ from typing import List from django import db from django.core.checks import CheckMessage, Error, register, Warning from allianceauth.utils.cache import get_redis_client from django.utils import timezone from packaging.version import InvalidVersion, Version as Pep440Version from celery import current_app from django.conf import settings from sqlite3.dbapi2 import sqlite_version_info """ A = System Packages B = Configuration """ @register() def django_settings(app_configs, **kwargs) -> List[CheckMessage]: """ Check that Django settings are correctly configured :param app_configs: :type app_configs: :param kwargs: :type kwargs: :return: :rtype: """ errors: List[CheckMessage] = [] # Check for SITE_URL if hasattr(settings, "SITE_URL"): # Check if SITE_URL is empty if settings.SITE_URL == "": errors.append( Error( msg="'SITE_URL' is empty.", hint="Make sure to set 'SITE_URL' to the URL of your Auth instance. (Without trailing slash)", id="allianceauth.checks.B011", ) ) # Check if SITE_URL has a trailing slash elif settings.SITE_URL[-1] == "/": errors.append( Warning( msg="'SITE_URL' has a trailing slash. This may lead to incorrect links being generated by Auth.", hint="", id="allianceauth.checks.B005", ) ) # SITE_URL not found else: errors.append( Error( msg="No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules.", hint="", id="allianceauth.checks.B006", ) ) # Check for CSRF_TRUSTED_ORIGINS if hasattr(settings, "CSRF_TRUSTED_ORIGINS") and hasattr(settings, "SITE_URL"): # Check if SITE_URL is not in CSRF_TRUSTED_ORIGINS if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS: errors.append( Warning( msg="'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified.", hint="", id="allianceauth.checks.B007", ) ) # CSRF_TRUSTED_ORIGINS not found else: errors.append( Error( msg="No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified", hint="", id="allianceauth.checks.B008", ) ) # Check for ESI_USER_CONTACT_EMAIL if hasattr(settings, "ESI_USER_CONTACT_EMAIL"): # Check if ESI_USER_CONTACT_EMAIL is empty if settings.ESI_USER_CONTACT_EMAIL == "": errors.append( Error( msg="'ESI_USER_CONTACT_EMAIL' is empty. A valid email is required as maintainer contact for CCP.", hint="", id="allianceauth.checks.B009", ) ) # ESI_USER_CONTACT_EMAIL not found else: errors.append( Error( msg="No 'ESI_USER_CONTACT_EMAIL' found is settings. A valid email is required as maintainer contact for CCP.", hint="", id="allianceauth.checks.B010", ) ) return errors @register() def system_package_redis(app_configs, **kwargs) -> List[CheckMessage]: """ Check that Redis is a supported version :param app_configs: :type app_configs: :param kwargs: :type kwargs: :return: :rtype: """ allianceauth_redis_install_link = "https://allianceauth.readthedocs.io/en/latest/installation/allianceauth.html#redis-and-other-tools" errors: List[CheckMessage] = [] try: redis_version = Pep440Version(get_redis_client().info()["redis_version"]) except InvalidVersion: errors.append(Warning("Unable to confirm Redis Version")) return errors if ( redis_version.major == 7 and redis_version.minor == 2 and timezone.now() > timezone.datetime(year=2025, month=8, day=31, tzinfo=timezone.utc) ): errors.append( Error( msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested", hint=allianceauth_redis_install_link, id="allianceauth.checks.A001", ) ) elif redis_version.major == 7 and redis_version.minor == 0: errors.append( Warning( msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested", hint=allianceauth_redis_install_link, id="allianceauth.checks.A002", ) ) elif redis_version.major == 6 and redis_version.minor == 2: errors.append( Warning( msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested", hint=allianceauth_redis_install_link, id="allianceauth.checks.A018", ) ) elif redis_version.major in [6, 5]: errors.append( Error( msg=f"Redis {redis_version.public} EOL", hint=allianceauth_redis_install_link, id="allianceauth.checks.A003", ) ) return errors @register() def system_package_mysql(app_configs, **kwargs) -> List[CheckMessage]: """ Check that MySQL is a supported version :param app_configs: :type app_configs: :param kwargs: :type kwargs: :return: :rtype: """ mysql_quick_guide_link = "https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/" errors: List[CheckMessage] = [] for connection in db.connections.all(): if connection.vendor == "mysql": try: mysql_version = Pep440Version( ".".join(str(i) for i in connection.mysql_version) ) except InvalidVersion: errors.append(Warning("Unable to confirm MySQL Version")) return errors # MySQL 8 if mysql_version.major == 8: if mysql_version.minor == 4 and timezone.now() > timezone.datetime( year=2032, month=4, day=30, tzinfo=timezone.utc ): errors.append( Error( msg=f"MySQL {mysql_version.public} EOL", hint=mysql_quick_guide_link, id="allianceauth.checks.A004", ) ) elif mysql_version.minor == 3: errors.append( Warning( msg=f"MySQL {mysql_version.public} Non LTS", hint=mysql_quick_guide_link, id="allianceauth.checks.A005", ) ) elif mysql_version.minor == 2: errors.append( Warning( msg=f"MySQL {mysql_version.public} Non LTS", hint=mysql_quick_guide_link, id="allianceauth.checks.A006", ) ) elif mysql_version.minor == 1: errors.append( Error( msg=f"MySQL {mysql_version.public} EOL", hint=mysql_quick_guide_link, id="allianceauth.checks.A007", ) ) elif mysql_version.minor == 0 and timezone.now() > timezone.datetime( year=2026, month=4, day=30, tzinfo=timezone.utc ): errors.append( Error( msg=f"MySQL {mysql_version.public} EOL", hint=mysql_quick_guide_link, id="allianceauth.checks.A008", ) ) # MySQL below 8 # This will also catch Mariadb 5.x elif mysql_version.major < 8: errors.append( Error( msg=f"MySQL or MariaDB {mysql_version.public} EOL", hint=mysql_quick_guide_link, id="allianceauth.checks.A009", ) ) return errors @register() def system_package_mariadb(app_configs, **kwargs) -> List[CheckMessage]: """ Check that MariaDB is a supported version :param app_configs: :type app_configs: :param kwargs: :type kwargs: :return: :rtype: """ mariadb_download_link = "https://mariadb.org/download/?t=repo-config" errors: List[CheckMessage] = [] for connection in db.connections.all(): # TODO: Find a way to determine MySQL vs. MariaDB if connection.vendor == "mysql": try: mariadb_version = Pep440Version( ".".join(str(i) for i in connection.mysql_version) ) except InvalidVersion: errors.append(Warning("Unable to confirm MariaDB Version")) return errors # MariaDB 11 if mariadb_version.major == 11: if mariadb_version.minor == 4 and timezone.now() > timezone.datetime( year=2029, month=5, day=19, tzinfo=timezone.utc ): errors.append( Error( msg=f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A010", ) ) elif mariadb_version.minor == 2: errors.append( Warning( msg=f"MariaDB {mariadb_version.public} Non LTS", hint=mariadb_download_link, id="allianceauth.checks.A018", ) ) if timezone.now() > timezone.datetime( year=2024, month=11, day=21, tzinfo=timezone.utc ): errors.append( Error( msg=f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A011", ) ) elif mariadb_version.minor == 1: errors.append( Warning( msg=f"MariaDB {mariadb_version.public} Non LTS", hint=mariadb_download_link, id="allianceauth.checks.A019", ) ) if timezone.now() > timezone.datetime( year=2024, month=8, day=21, tzinfo=timezone.utc ): errors.append( Error( msg=f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A012", ) ) # Demote versions down here once EOL elif mariadb_version.minor in [0, 3]: errors.append( Error( msg=f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A013", ) ) # MariaDB 10 elif mariadb_version.major == 10: if mariadb_version.minor == 11 and timezone.now() > timezone.datetime( year=2028, month=2, day=10, tzinfo=timezone.utc ): errors.append( Error( msg=f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A014", ) ) elif mariadb_version.minor == 6 and timezone.now() > timezone.datetime( year=2026, month=7, day=6, tzinfo=timezone.utc ): errors.append( Error( msg=f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A0015", ) ) elif mariadb_version.minor == 5 and timezone.now() > timezone.datetime( year=2025, month=6, day=24, tzinfo=timezone.utc ): errors.append( Error( msg=f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A016", ) ) # Demote versions down here once EOL elif mariadb_version.minor in [0, 1, 2, 3, 4, 7, 9, 10]: errors.append( Error( msg=f"MariaDB {mariadb_version.public} EOL", hint=mariadb_download_link, id="allianceauth.checks.A017", ) ) return errors @register() def system_package_sqlite(app_configs, **kwargs) -> List[CheckMessage]: """ Check that SQLite is a supported version :param app_configs: :type app_configs: :param kwargs: :type kwargs: :return: :rtype: """ errors: List[CheckMessage] = [] for connection in db.connections.all(): if connection.vendor == "sqlite": try: sqlite_version = Pep440Version( ".".join(str(i) for i in sqlite_version_info) ) except InvalidVersion: errors.append(Warning("Unable to confirm SQLite Version")) return errors if sqlite_version.major == 3 and sqlite_version.minor < 27: errors.append( Error( msg=f"SQLite {sqlite_version.public} Unsupported by Django", hint="https://pkgs.org/download/sqlite3", id="allianceauth.checks.A020", ) ) return errors @register() def sql_settings(app_configs, **kwargs) -> List[CheckMessage]: """ Check that SQL settings are correctly configured :param app_configs: :type app_configs: :param kwargs: :type kwargs: :return: :rtype: """ errors: List[CheckMessage] = [] for connection in db.connections.all(): if connection.vendor == "mysql": try: if connection.settings_dict["OPTIONS"]["charset"] != "utf8mb4": errors.append( Error( msg=f"SQL Charset is not set to utf8mb4 DB: {connection.alias}", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", id="allianceauth.checks.B001", ) ) except KeyError: errors.append( Error( msg=f"SQL Charset is not set to utf8mb4 DB: {connection.alias}", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", id="allianceauth.checks.B001", ) ) # This hasn't actually been set on AA yet # try: # if ( # connection.settings_dict["OPTIONS"]["collation"] # != "utf8mb4_unicode_ci" # ): # errors.append( # Error( # msg=f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}", # hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", # id="allianceauth.checks.B001", # ) # ) # except KeyError: # errors.append( # Error( # msg=f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}", # hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8", # id="allianceauth.checks.B001", # ) # ) # if connection.vendor == "sqlite": return errors @register() def celery_settings(app_configs, **kwargs) -> List[CheckMessage]: """ Check that Celery settings are correctly configured :param app_configs: :type app_configs: :param kwargs: :type kwargs: :return: :rtype: """ errors: List[CheckMessage] = [] try: if current_app.conf.broker_transport_options != { "priority_steps": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "queue_order_strategy": "priority", }: errors.append( Error( msg="Celery Priorities are not set correctly", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc", id="allianceauth.checks.B003", ) ) except KeyError: errors.append( Error( msg="Celery Priorities are not set", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc", id="allianceauth.checks.B003", ) ) try: if not current_app.conf.broker_connection_retry_on_startup: errors.append( Error( msg="Celery broker_connection_retry_on_startup not set correctly", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280", id="allianceauth.checks.B004", ) ) except KeyError: errors.append( Error( msg="Celery broker_connection_retry_on_startup not set", hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280", id="allianceauth.checks.B004", ) ) return errors # IDEAS # Any other celery things weve manually changed over the years # I'd be happy to add Community App checks, old versions the owners dont want to support etc. # Check Default Collation on DB # Check Charset Collation on all tables