From 2ff91abe827cd2b25df18f4dcbba1f5891a5f639 Mon Sep 17 00:00:00 2001 From: Kumi Date: Tue, 25 Oct 2022 14:40:48 +0000 Subject: [PATCH] Migrate to AutoSecretKey Implement OIDC login --- .gitignore | 3 +- common/templatetags/oidc.py | 13 +++ frontend/templates/registration/login.html | 9 +- kumify/settings.py | 99 +++++++++++++++++----- kumify/urls.py | 3 +- localsettings.dist.py | 42 --------- requirements.txt | 3 +- settings.dist.ini | 68 +++++++++++++++ 8 files changed, 171 insertions(+), 69 deletions(-) create mode 100644 common/templatetags/oidc.py delete mode 100644 localsettings.dist.py create mode 100644 settings.dist.ini diff --git a/.gitignore b/.gitignore index 3976f97..9aadee2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ __pycache__/ db.sqlite3 migrations/ venv/ -localsettings.py \ No newline at end of file +localsettings.py +settings.ini \ No newline at end of file diff --git a/common/templatetags/oidc.py b/common/templatetags/oidc.py new file mode 100644 index 0000000..c87aa53 --- /dev/null +++ b/common/templatetags/oidc.py @@ -0,0 +1,13 @@ +from django import template +from django.conf import settings + +register = template.Library() + + +@register.simple_tag +def use_oidc(): + return settings.USE_OIDC + +@register.simple_tag +def oidc_provider(): + return settings.OIDC_PROVIDER_NAME \ No newline at end of file diff --git a/frontend/templates/registration/login.html b/frontend/templates/registration/login.html index 0db63ec..d3412b8 100644 --- a/frontend/templates/registration/login.html +++ b/frontend/templates/registration/login.html @@ -1,4 +1,5 @@ {% extends "registration/base.html" %} +{% load oidc %} {% block content %}
@@ -19,7 +20,13 @@ -
+ {% use_oidc as use_oidc %} + {% if use_oidc %} +
or
+ + Login via {% oidc_provider %} + + {% endif %}
diff --git a/kumify/settings.py b/kumify/settings.py index 6e834d2..1a7bd3c 100644 --- a/kumify/settings.py +++ b/kumify/settings.py @@ -1,19 +1,28 @@ # You shouldn't have to change anything in here, ever. -# Use localsettings.py in the project's root directory instead. +# Use settings.ini in the project's root directory instead. # If you make any changes in here, you may have trouble updating your Kumify installation. from pathlib import Path -from localsettings import * +from autosecretkey import AutoSecretKey # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent +CONFIG_FILE = AutoSecretKey(BASE_DIR / "settings.ini") + +SECRET_KEY = CONFIG_FILE.secret_key +DEBUG = CONFIG_FILE.config.getboolean("KUMIFY", "Debug", fallback=False) + +ALLOWED_HOSTS = [CONFIG_FILE.config.get("KUMIFY", "Host")] +CSRF_TRUSTED_ORIGINS = [f"https://{ALLOWED_HOSTS[0]}"] + +TIME_ZONE = CONFIG_FILE.config.get("KUMIFY", "TimeZone", fallback="UTC") # Application definition try: - ENABLED_MODULES + ENABLED_MODULES # TODO: Move this to settings.ini except NameError: ENABLED_MODULES = [ 'cbt', @@ -42,6 +51,7 @@ INSTALLED_APPS = [ 'colorfield', 'multiselectfield', 'dbsettings', + 'mozilla_django_oidc', ] + CORE_MODULES + ENABLED_MODULES MIDDLEWARE = [ @@ -76,30 +86,40 @@ WSGI_APPLICATION = 'kumify.wsgi.application' # Database -# https://docs.djangoproject.com/en/3.1/ref/settings/#databases +# https://docs.djangoproject.com/en/4.0/ref/settings/#databases -DATABASES = { - 'default': { - 'ENGINE': 'django.contrib.gis.db.backends.spatialite', - 'NAME': BASE_DIR / 'db.sqlite3', - } if not DB_HOST else { - 'ENGINE': 'django.contrib.gis.db.backends.mysql', - 'NAME': DB_NAME, - 'USER': DB_USER, - 'PASSWORD': DB_PASS, - 'HOST': DB_HOST, - 'PORT': DB_PORT, - 'OPTIONS': { - 'charset': 'utf8mb4', - 'sql_mode': 'traditional', +if "MySQL" in CONFIG_FILE.config: + DATABASES = { + 'default': { + 'ENGINE': 'django.contrib.gis.db.backends.mysql', + 'NAME': CONFIG_FILE.config.get("MySQL", "Database"), + 'USER': CONFIG_FILE.config.get("MySQL", "Username"), + 'PASSWORD': CONFIG_FILE.config.get("MySQL", "Password"), + 'HOST': CONFIG_FILE.config.get("MySQL", "Host", fallback="localhost"), + 'PORT': CONFIG_FILE.config.getint("MySQL", "Port", fallback=3306), + 'OPTIONS': { + 'charset': 'utf8mb4', + 'sql_mode': 'traditional', + } + } + } + +else: + DATABASES = { + 'default': { + 'ENGINE': 'django.contrib.gis.db.backends.spatialite', + 'NAME': BASE_DIR / 'db.sqlite3', } } -} # Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators +AUTHENTICATION_BACKENDS = [ + 'django.contrib.auth.backends.ModelBackend', +] + AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', @@ -135,15 +155,48 @@ USE_TZ = True DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/3.1/howto/static-files/ +# https://docs.djangoproject.com/en/4.0/howto/static-files/ STATIC_URL = '/static/' -STATIC_ROOT = None if DEBUG else STATIC_ROOT +STATIC_ROOT = None if DEBUG else CONFIG_FILE.config.get("KUMIFY", "StaticRoot", fallback=BASE_DIR / "static") LOGIN_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = "/" -DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' if not AWS_ACCESS_KEY_ID else 'storages.backends.s3boto3.S3Boto3Storage' -STATICFILES_STORAGE = 'storages.backends.s3boto3.S3StaticStorage' if AWS_ACCESS_KEY_ID else 'django.contrib.staticfiles.storage.StaticFilesStorage' +if "S3" in CONFIG_FILE.config: + DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' + STATICFILES_STORAGE = 'storages.backends.s3boto3.S3StaticStorage' + AWS_ACCESS_KEY_ID = CONFIG_FILE.config.get("S3", "AccessKey") + AWS_SECRET_ACCESS_KEY = CONFIG_FILE.config.get("S3", "SecretKey") + AWS_STORAGE_BUCKET_NAME = CONFIG_FILE.config.get("S3", "Bucket") + AWS_S3_ENDPOINT_URL = CONFIG_FILE.config.get("S3", "Endpoint") + + +# OpenID Connect +# https://mozilla-django-oidc.readthedocs.io/ + +USE_OIDC = False + +if "OIDC" in CONFIG_FILE.config: + USE_OIDC = True + + OIDC_PROVIDER_NAME = CONFIG_FILE.config.get("OIDC", "ProviderName", fallback="OpenID Connect") + + AUTHENTICATION_BACKENDS.append('mozilla_django_oidc.auth.OIDCAuthenticationBackend') + + OIDC_RP_CLIENT_ID = CONFIG_FILE.config.get("OIDC", "ClientID") + OIDC_RP_CLIENT_SECRET = CONFIG_FILE.config.get("OIDC", "ClientSecret") + + if (opsk := CONFIG_FILE.config.get("OIDC", "OPSignKey", fallback="")): + OIDC_RP_SIGN_ALGO = "RS256" + OIDC_RP_IDP_SIGN_KEY = opsk + elif (jwks := CONFIG_FILE.config.get("OIDC", "JWKSEndpoint", fallback="")): + OIDC_RP_SIGN_ALGO = "RS256" + OIDC_OP_JWKS_ENDPOINT = jwks + + OIDC_OP_AUTHORIZATION_ENDPOINT = CONFIG_FILE.config.get("OIDC", "AuthorizationEndpoint") + OIDC_OP_TOKEN_ENDPOINT = CONFIG_FILE.config.get("OIDC", "TokenEndpoint") + OIDC_OP_USER_ENDPOINT = CONFIG_FILE.config.get("OIDC", "UserInfoEndpoint") \ No newline at end of file diff --git a/kumify/urls.py b/kumify/urls.py index 2cb2f3d..e3163e7 100644 --- a/kumify/urls.py +++ b/kumify/urls.py @@ -26,5 +26,6 @@ urlpatterns = [ path('cron/', include("cronhandler.urls", "cron")), path('webhooks/telegram/', TelegramWebhookView.as_view()), path('dreams/', include("dreams.urls", "dreams")), - path('gpslog/', include("gpslog.urls")) + path('gpslog/', include("gpslog.urls")), + path('oidc/', include('mozilla_django_oidc.urls')), ] diff --git a/localsettings.dist.py b/localsettings.dist.py deleted file mode 100644 index 179305e..0000000 --- a/localsettings.dist.py +++ /dev/null @@ -1,42 +0,0 @@ -# The secret key must be a long random string. -# You may use the django.core.management.utils.get_random_secret_key() function to generate one, or just smash your keyboard real hard a few times. - -SECRET_KEY = "longrandomstring" - -# Putting the system in debug mode will give you a lot of output if an error occurs, but it potentially exposes sensitive information like passwords. -# Only set this to True if you really need to, especially if you are running a public instance. - -DEBUG = False - -# Specify the time zone you are in. This will affect the times displayed in the application. - -TIME_ZONE = "Europe/Vienna" - -# You may set this variable to a list of domain names that are allowed to be used to access your instance. - -ALLOWED_HOSTS = ["*"] # Rationale: The application should be running behind a reverse proxy anyway if it's public - let that handle which hosts are allowed - -# If you are using an external server to make your instance public, we need to store some static files somewhere. -# Enter the appropriate directory and make sure your webserver serves that location at /static/ - -STATIC_ROOT = '/var/www/html/static/' - -# By default, all files, including uploads, are stored locally. -# You may use an S3 compatible storage instead in order to increase reliability and decrease disk usage. -# If AWS_ACCESS_KEY_ID is set to None, local storage will be used. -# See https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html for all options you can use here. -# (NB: Only options starting with "AWS_" are allowed here, the storage configuration will be handled automatically.) - -AWS_ACCESS_KEY_ID = None -AWS_SECRET_ACCESS_KEY = None -AWS_STORAGE_BUCKET_NAME = None -AWS_S3_ENDPOINT_URL = None - -# By default, this application uses a local sqlite3 database file. You can choose to use a MariaDB/MySQL database instead. -# If DB_HOST is set to None, the sqlite3 database will be used. - -DB_HOST = None # Host name of the database server -DB_PORT = 3306 # Port of the database server - the default value usually works -DB_NAME = None # Name of the database to be used -DB_USER = None # User name to authenticate with -DB_PASS = None # Password to authenticate with diff --git a/requirements.txt b/requirements.txt index 464c36f..29a8e96 100644 --- a/requirements.txt +++ b/requirements.txt @@ -18,4 +18,5 @@ bokeh panel scipy channels -django-autosecretkey \ No newline at end of file +django-autosecretkey +mozilla-django-oidc \ No newline at end of file diff --git a/settings.dist.ini b/settings.dist.ini new file mode 100644 index 0000000..fb68755 --- /dev/null +++ b/settings.dist.ini @@ -0,0 +1,68 @@ +[KUMIFY] +# Putting the system in debug mode will give you a lot of output if an error occurs, but it potentially exposes sensitive information like passwords. +# Only set this to 1 (= True) if you really need to, especially if you are running a public instance. + +Debug = 0 + +# Specify the time zone you are in. This will affect the times displayed in the application. + +TimeZone = Europe/Vienna + +# Set this to the domain name you are using for Kumify + +Host = kumify.lan + +# If you are not using S3 storage (see below), we need to store your static files somewhere locally +# Enter the appropriate directory and make sure your webserver serves that location at /static/ +# If unset, the "static" subdirectory of the Kumify base directory is going to be used + +StaticRoot = '/var/www/html/static/' + + +# By default, all files, including uploads, are stored locally. +# You may use an S3 compatible storage instead in order to increase reliability and decrease local disk usage. +# If this section is commented out, local storage will be used. + +# [S3] +# AccessKey = kumify +# SecretKey = !!!verysecret!!! +# Bucket = kumify +# Endpoint = https://minio.kumify.lan + + +# By default, this application uses a local sqlite3 database file. You can choose to use a MariaDB/MySQL database instead. +# If this section is commented out, the local sqlite3 database is used + +# [MySQL] +# Database = kumify +# Username = kumify +# Password = secret123! +# Host = localhost +# Port = 3306 + + +# By default, Kumify uses local user authentication only +# In order to allow users to authenticate using an OpenID Connect provider, comment in this section and set the values accordingly + +# [OIDC] +# Optionally, enter the name of the OIDC provider, so it can be displayed on buttons + +# ProviderName = OpenID Connect + +# ClientID = Your client ID +# ClientSecret = Your client secret + +# To use the RS256 algorihm, set one of the following two settings + +# OPSignKey = OP signing key in PEM or DER format +# JWKSEndpoint = https://kumidc.lan/openid/jwks + +# These URLs need to correspond to your ID provider + +# AuthorizationEndpoint = https://kumidc.lan/openid/authorize +# TokenEndpoint = https://kumidc.lan/openid/token +# UserInfoEndpoint = https://kumidc.lan/openid/userinfo + +# If you want to allow users who do not yet have a Kumify account to log in using the OIDC provider, uncomment the following setting and set it to 1. + +# CreateUsers = 0 \ No newline at end of file