Create Basic Django Framework with API, SQLite, PostgreSQL, MySQL and SSO with OIDC
This commit is contained in:
parent
38bb91285d
commit
37168500d1
15 changed files with 196 additions and 74 deletions
23
.env-example
Normal file
23
.env-example
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Django core
|
||||||
|
DEBUG=True
|
||||||
|
SECRET_KEY=dev-secret-key
|
||||||
|
DJANGO_SETTINGS_MODULE=config.settings
|
||||||
|
|
||||||
|
# Superuser (optional)
|
||||||
|
DJANGO_SUPERUSER_USERNAME=admin
|
||||||
|
DJANGO_SUPERUSER_EMAIL=admin@example.com
|
||||||
|
DJANGO_SUPERUSER_PASSWORD=changeme
|
||||||
|
|
||||||
|
# OIDC / SSO
|
||||||
|
SSO_ENABLED=False
|
||||||
|
OIDC_RP_CLIENT_ID=django-app
|
||||||
|
OIDC_RP_CLIENT_SECRET=changeme
|
||||||
|
OIDC_OP_DISCOVERY_ENDPOINT=https://auth.example.com/.well-known/openid-configuration
|
||||||
|
|
||||||
|
# Database settings (Default: SQLite)
|
||||||
|
DB_ENGINE=sqlite
|
||||||
|
DB_NAME=db.sqlite3
|
||||||
|
DB_USER=
|
||||||
|
DB_PASSWORD=
|
||||||
|
DB_HOST=
|
||||||
|
DB_PORT=
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1 +1,4 @@
|
||||||
.venv
|
.venv
|
||||||
|
.startserver.sh
|
||||||
|
.env
|
||||||
|
sb.sqlite3
|
Binary file not shown.
Binary file not shown.
16
api/views.py
16
api/views.py
|
@ -1,8 +1,20 @@
|
||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import api_view, permission_classes
|
||||||
from rest_framework.permissions import AllowAny
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
||||||
@api_view(["GET"])
|
@api_view(["GET"])
|
||||||
@permission_classes([AllowAny]) # erstmal offen, später absichern
|
@permission_classes([AllowAny]) # This endpoint is deliberately open to everyone
|
||||||
def ping(request):
|
def ping(request):
|
||||||
return Response({"status": "ok"})
|
return Response({"status": "ok"})
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(["GET"])
|
||||||
|
@permission_classes([IsAuthenticated]) # Requires either session (OIDC) or basic authentication
|
||||||
|
def secure_ping(request):
|
||||||
|
return Response({
|
||||||
|
"status": "ok",
|
||||||
|
"user": request.user.username, # The authenticated username
|
||||||
|
# Indicates whether the request was authenticated via session (OIDC) or via basic auth
|
||||||
|
"auth_via": request.auth.__class__.__name__ if request.auth else "session/basic"
|
||||||
|
})
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,42 +1,47 @@
|
||||||
import environ
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import environ
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# ---------------------------------------------------------------------------
|
||||||
|
# Base project paths
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
# Load Env-Vars
|
# ---------------------------------------------------------------------------
|
||||||
|
# Environment configuration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
env = environ.Env()
|
env = environ.Env()
|
||||||
environ.Env.read_env(os.path.join(BASE_DIR, ".env"))
|
environ.Env.read_env(os.path.join(BASE_DIR, ".env"))
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# ---------------------------------------------------------------------------
|
||||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
# Security settings
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = env(
|
SECRET_KEY = env(
|
||||||
"SECRET_KEY",
|
"SECRET_KEY",
|
||||||
default="django-insecure-lbfv*h@=mjj#xq^!k@-5f2oiq@u6ms9t6=3&nr+!#itih%jh^l"
|
default="django-insecure-lbfv*h@=mjj#xq^!k@-5f2oiq@u6ms9t6=3&nr+!#itih%jh^l" # fallback only for development
|
||||||
)
|
)
|
||||||
|
DEBUG = env.bool("DEBUG", default=False)
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = env.bool("DEBUG", default=True)
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
|
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# Application definition
|
# Installed apps
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
# Django built-in apps
|
||||||
"django.contrib.admin",
|
"django.contrib.admin",
|
||||||
"django.contrib.auth",
|
"django.contrib.auth",
|
||||||
"django.contrib.contenttypes",
|
"django.contrib.contenttypes",
|
||||||
"django.contrib.sessions",
|
"django.contrib.sessions",
|
||||||
"django.contrib.messages",
|
"django.contrib.messages",
|
||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
|
|
||||||
|
# Third-party apps
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Middleware
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
"django.middleware.security.SecurityMiddleware",
|
"django.middleware.security.SecurityMiddleware",
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
@ -47,12 +52,19 @@ MIDDLEWARE = [
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# URL & WSGI configuration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
ROOT_URLCONF = "config.urls"
|
ROOT_URLCONF = "config.urls"
|
||||||
|
WSGI_APPLICATION = "config.wsgi.application"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Templates
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
"DIRS": [],
|
"DIRS": [], # can be extended if custom templates are needed
|
||||||
"APP_DIRS": True,
|
"APP_DIRS": True,
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
"context_processors": [
|
"context_processors": [
|
||||||
|
@ -65,89 +77,100 @@ TEMPLATES = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
WSGI_APPLICATION = "config.wsgi.application"
|
# ---------------------------------------------------------------------------
|
||||||
|
# Database configuration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
DB_ENGINE = env("DB_ENGINE", default="sqlite").lower()
|
||||||
|
|
||||||
|
if DB_ENGINE == "postgres":
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
|
"NAME": env("DB_NAME", default="postgres"),
|
||||||
|
"USER": env("DB_USER", default="postgres"),
|
||||||
|
"PASSWORD": env("DB_PASSWORD", default=""),
|
||||||
|
"HOST": env("DB_HOST", default="localhost"),
|
||||||
|
"PORT": env("DB_PORT", default="5432"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Database
|
elif DB_ENGINE == "mysql":
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.mysql",
|
||||||
|
"NAME": env("DB_NAME", default="mysql"),
|
||||||
|
"USER": env("DB_USER", default="root"),
|
||||||
|
"PASSWORD": env("DB_PASSWORD", default=""),
|
||||||
|
"HOST": env("DB_HOST", default="localhost"),
|
||||||
|
"PORT": env("DB_PORT", default="3306"),
|
||||||
|
"OPTIONS": {
|
||||||
|
"charset": "utf8mb4", # recommended for full Unicode support
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else: # default: SQLite
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
"NAME": BASE_DIR / "db.sqlite3",
|
"NAME": BASE_DIR / "db.sqlite3", # fixed filename for simplicity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# Password validation
|
# Authentication & password validation
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
||||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
|
||||||
},
|
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
|
||||||
{
|
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
||||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
LANGUAGE_CODE = "en-us"
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
TIME_ZONE = "UTC"
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files
|
||||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
STATIC_URL = "static/"
|
STATIC_URL = "static/"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Django REST framework configuration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
"DEFAULT_PERMISSION_CLASSES": [
|
"DEFAULT_PERMISSION_CLASSES": [
|
||||||
"rest_framework.permissions.IsAuthenticated",
|
"rest_framework.permissions.IsAuthenticated", # all endpoints protected by default
|
||||||
],
|
],
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": [
|
"DEFAULT_AUTHENTICATION_CLASSES": [
|
||||||
"rest_framework.authentication.SessionAuthentication",
|
"rest_framework.authentication.SessionAuthentication", # required for OIDC/session login
|
||||||
"rest_framework.authentication.BasicAuthentication",
|
"rest_framework.authentication.BasicAuthentication", # allows Basic Auth for API clients
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
# OIDC Vars
|
# ---------------------------------------------------------------------------
|
||||||
|
# OpenID Connect (SSO) configuration
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
SSO_ENABLED = env.bool("SSO_ENABLED", default=False)
|
SSO_ENABLED = env.bool("SSO_ENABLED", default=False)
|
||||||
|
|
||||||
if SSO_ENABLED:
|
if SSO_ENABLED:
|
||||||
INSTALLED_APPS += [
|
INSTALLED_APPS += ["mozilla_django_oidc"]
|
||||||
"mozilla_django_oidc",
|
MIDDLEWARE += ["mozilla_django_oidc.middleware.SessionRefresh"]
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE += [
|
|
||||||
"mozilla_django_oidc.middleware.SessionRefresh",
|
|
||||||
]
|
|
||||||
|
|
||||||
LOGIN_URL = "/oidc/authenticate/"
|
LOGIN_URL = "/oidc/authenticate/"
|
||||||
LOGIN_REDIRECT_URL = "/"
|
LOGIN_REDIRECT_URL = "/"
|
||||||
LOGOUT_REDIRECT_URL = "/"
|
LOGOUT_REDIRECT_URL = "/"
|
||||||
|
|
||||||
|
|
||||||
OIDC_RP_CLIENT_ID = env("OIDC_RP_CLIENT_ID", default="django-app")
|
OIDC_RP_CLIENT_ID = env("OIDC_RP_CLIENT_ID", default="django-app")
|
||||||
OIDC_RP_CLIENT_SECRET = env("OIDC_RP_CLIENT_SECRET", default="changeme")
|
OIDC_RP_CLIENT_SECRET = env("OIDC_RP_CLIENT_SECRET", default="changeme")
|
||||||
OIDC_OP_DISCOVERY_ENDPOINT = env(
|
OIDC_OP_DISCOVERY_ENDPOINT = env(
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from api.views import ping
|
from api.views import ping, secure_ping
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("api/ping/", ping),
|
path("api/ping/", ping), # Public healthcheck endpoint
|
||||||
|
path("api/secure-ping/", secure_ping) # Protected API endpoint
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Add OIDC routes only if Single Sign-On is enabled
|
||||||
if settings.SSO_ENABLED:
|
if settings.SSO_ENABLED:
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
path("oidc/", include("mozilla_django_oidc.urls")),
|
path("oidc/", include("mozilla_django_oidc.urls")),
|
||||||
|
|
BIN
db.sqlite3
Normal file
BIN
db.sqlite3
Normal file
Binary file not shown.
9
docker-compose-sqlite.yml
Normal file
9
docker-compose-sqlite.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
services:
|
||||||
|
risk-management:
|
||||||
|
build: .
|
||||||
|
container_name: django_web
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
- ./data/app/:/app
|
|
@ -1,6 +1,8 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
export DJANGO_SETTINGS_MODULE=config.settings
|
||||||
|
|
||||||
echo "Running migrations..."
|
echo "Running migrations..."
|
||||||
python manage.py migrate --noinput
|
python manage.py migrate --noinput
|
||||||
|
|
||||||
|
|
47
startserver.sh
Executable file
47
startserver.sh
Executable file
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Startup script for the Django project
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# This script ensures that the development environment is ready to run.
|
||||||
|
#
|
||||||
|
# Features:
|
||||||
|
# - Runs database migrations
|
||||||
|
# - Creates a superuser if it does not already exist
|
||||||
|
# - Starts the Django development server
|
||||||
|
#
|
||||||
|
# Note:
|
||||||
|
# This script is for local development only.
|
||||||
|
# In production you should use a proper WSGI/ASGI server (e.g., Gunicorn, Daphne).
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
set -e # Exit immediately if a command exits with a non-zero status
|
||||||
|
|
||||||
|
# Load environment variables from .env if available
|
||||||
|
if [ -f ".env" ]; then
|
||||||
|
export $(grep -v '^#' .env | xargs)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure we are in the project root directory
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
echo ">>> Running migrations..."
|
||||||
|
python manage.py migrate --noinput
|
||||||
|
|
||||||
|
echo ">>> Checking if superuser exists..."
|
||||||
|
python manage.py shell <<EOF
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
User = get_user_model()
|
||||||
|
username = "${DJANGO_SUPERUSER_USERNAME:-admin}"
|
||||||
|
if not User.objects.filter(username=username).exists():
|
||||||
|
print(f"Creating superuser '{username}'...")
|
||||||
|
User.objects.create_superuser(
|
||||||
|
username=username,
|
||||||
|
email="${DJANGO_SUPERUSER_EMAIL:-admin@example.com}",
|
||||||
|
password="${DJANGO_SUPERUSER_PASSWORD:-changeme}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f"Superuser '{username}' already exists.")
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo ">>> Starting Django development server at http://127.0.0.1:8000 ..."
|
||||||
|
python manage.py runserver 0.0.0.0:8000
|
Loading…
Add table
Reference in a new issue