Compare commits
No commits in common. "74423fde3944fe097d2a3e6dc8ee3fdf73e56260" and "fbfeeaefeaf681df6c043a304b9f14e53903d61f" have entirely different histories.
74423fde39
...
fbfeeaefea
|
@ -1,5 +0,0 @@
|
||||||
root = true
|
|
||||||
|
|
||||||
[*.html]
|
|
||||||
indent_size = 4
|
|
||||||
indent_style = space
|
|
20
.env.example
20
.env.example
|
@ -1,20 +0,0 @@
|
||||||
ALLOWED_HOSTS=.localhost,127.0.0.1,[::1]
|
|
||||||
|
|
||||||
DATABASE_URL=
|
|
||||||
|
|
||||||
DEBUG=False
|
|
||||||
|
|
||||||
OIDC_ADDITIONAL_SCOPES=
|
|
||||||
OIDC_OP_AUTHORIZATION_ENDPOINT=
|
|
||||||
#OIDC_OP_JWKS_ENDPOINT=
|
|
||||||
OIDC_OP_TOKEN_ENDPOINT=
|
|
||||||
OIDC_OP_USER_ENDPOINT=
|
|
||||||
OIDC_ROLES_CLAIM=
|
|
||||||
OIDC_RP_CLIENT_ID=
|
|
||||||
OIDC_RP_CLIENT_SECRET=
|
|
||||||
#OIDC_RP_SIGN_ALGO=
|
|
||||||
OIDC_SUPERUSER_ROLE=
|
|
||||||
|
|
||||||
SECRET_KEY=
|
|
||||||
|
|
||||||
TIME_ZONE=UTC
|
|
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
services:
|
|
||||||
keycloak:
|
|
||||||
image: quay.io/keycloak/keycloak:25.0
|
|
||||||
restart: unless-stopped
|
|
||||||
command:
|
|
||||||
- start-dev
|
|
||||||
environment:
|
|
||||||
KEYCLOAK_ADMIN: admin
|
|
||||||
KEYCLOAK_ADMIN_PASSWORD: admin
|
|
||||||
ports:
|
|
||||||
- 127.0.0.1:8080:8080
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
from django.contrib.auth.admin import UserAdmin
|
|
||||||
|
|
||||||
from .models import User
|
|
||||||
|
|
||||||
admin.site.register(User, UserAdmin)
|
|
|
@ -1,8 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class CoreConfig(AppConfig):
|
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
|
||||||
label = "lelcsc_core"
|
|
||||||
name = "lelcsc.core"
|
|
||||||
verbose_name = "core"
|
|
|
@ -1,61 +0,0 @@
|
||||||
from django.conf import settings
|
|
||||||
from django.db import transaction
|
|
||||||
from mozilla_django_oidc.auth import (
|
|
||||||
OIDCAuthenticationBackend as BaseOIDCAuthenticationBackend,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .models import OIDCUser
|
|
||||||
|
|
||||||
|
|
||||||
def get_roles(claims):
|
|
||||||
roles = claims
|
|
||||||
|
|
||||||
roles_path = settings.OIDC_ROLES_CLAIM.split(".")
|
|
||||||
for key in roles_path:
|
|
||||||
roles = roles.get(key)
|
|
||||||
if roles is None:
|
|
||||||
return []
|
|
||||||
|
|
||||||
return roles
|
|
||||||
|
|
||||||
|
|
||||||
class OIDCAuthenticationBackend(BaseOIDCAuthenticationBackend):
|
|
||||||
@transaction.atomic
|
|
||||||
def create_user(self, claims):
|
|
||||||
is_superuser = settings.OIDC_SUPERUSER_ROLE in get_roles(claims)
|
|
||||||
|
|
||||||
user = self.UserModel.objects.create_user(
|
|
||||||
claims.get("preferred_username"),
|
|
||||||
claims.get("email"),
|
|
||||||
first_name=claims.get("given_name", ""),
|
|
||||||
last_name=claims.get("family_name", ""),
|
|
||||||
is_staff=is_superuser,
|
|
||||||
is_superuser=is_superuser,
|
|
||||||
)
|
|
||||||
|
|
||||||
OIDCUser.objects.create(uuid=claims.get("sub"), user=user)
|
|
||||||
|
|
||||||
return user
|
|
||||||
|
|
||||||
def update_user(self, user, claims):
|
|
||||||
user.username = claims.get("preferred_username")
|
|
||||||
user.email = claims.get("email")
|
|
||||||
user.first_name = claims.get("given_name", "")
|
|
||||||
user.last_name = claims.get("family_name", "")
|
|
||||||
user.is_superuser = user.is_staff = settings.OIDC_SUPERUSER_ROLE in get_roles(
|
|
||||||
claims
|
|
||||||
)
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
return user
|
|
||||||
|
|
||||||
def filter_users_by_claims(self, claims):
|
|
||||||
uuid = claims.get("sub")
|
|
||||||
if not uuid:
|
|
||||||
return self.UserModel.objects.none()
|
|
||||||
|
|
||||||
try:
|
|
||||||
oidc_user = OIDCUser.objects.get(uuid=uuid)
|
|
||||||
return [oidc_user.user]
|
|
||||||
except OIDCUser.DoesNotExist:
|
|
||||||
return self.UserModel.objects.none()
|
|
|
@ -1,147 +0,0 @@
|
||||||
# Generated by Django 5.1.1 on 2024-09-14 19:26
|
|
||||||
|
|
||||||
import django.contrib.auth.models
|
|
||||||
import django.contrib.auth.validators
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
("auth", "0012_alter_user_first_name_max_length"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="User",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
("password", models.CharField(max_length=128, verbose_name="password")),
|
|
||||||
(
|
|
||||||
"last_login",
|
|
||||||
models.DateTimeField(
|
|
||||||
blank=True, null=True, verbose_name="last login"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"is_superuser",
|
|
||||||
models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
|
||||||
verbose_name="superuser status",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"username",
|
|
||||||
models.CharField(
|
|
||||||
error_messages={
|
|
||||||
"unique": "A user with that username already exists."
|
|
||||||
},
|
|
||||||
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
|
||||||
max_length=150,
|
|
||||||
unique=True,
|
|
||||||
validators=[
|
|
||||||
django.contrib.auth.validators.UnicodeUsernameValidator()
|
|
||||||
],
|
|
||||||
verbose_name="username",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"first_name",
|
|
||||||
models.CharField(
|
|
||||||
blank=True, max_length=150, verbose_name="first name"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"last_name",
|
|
||||||
models.CharField(
|
|
||||||
blank=True, max_length=150, verbose_name="last name"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"email",
|
|
||||||
models.EmailField(
|
|
||||||
blank=True, max_length=254, verbose_name="email address"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"is_staff",
|
|
||||||
models.BooleanField(
|
|
||||||
default=False,
|
|
||||||
help_text="Designates whether the user can log into this admin site.",
|
|
||||||
verbose_name="staff status",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"is_active",
|
|
||||||
models.BooleanField(
|
|
||||||
default=True,
|
|
||||||
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
|
||||||
verbose_name="active",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"date_joined",
|
|
||||||
models.DateTimeField(
|
|
||||||
default=django.utils.timezone.now, verbose_name="date joined"
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"groups",
|
|
||||||
models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
|
||||||
related_name="user_set",
|
|
||||||
related_query_name="user",
|
|
||||||
to="auth.group",
|
|
||||||
verbose_name="groups",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"user_permissions",
|
|
||||||
models.ManyToManyField(
|
|
||||||
blank=True,
|
|
||||||
help_text="Specific permissions for this user.",
|
|
||||||
related_name="user_set",
|
|
||||||
related_query_name="user",
|
|
||||||
to="auth.permission",
|
|
||||||
verbose_name="user permissions",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
"verbose_name": "user",
|
|
||||||
"verbose_name_plural": "users",
|
|
||||||
"abstract": False,
|
|
||||||
},
|
|
||||||
managers=[
|
|
||||||
("objects", django.contrib.auth.models.UserManager()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="OIDCUser",
|
|
||||||
fields=[
|
|
||||||
("uuid", models.UUIDField(primary_key=True, serialize=False)),
|
|
||||||
(
|
|
||||||
"user",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,11 +0,0 @@
|
||||||
from django.contrib.auth.models import AbstractUser
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class OIDCUser(models.Model):
|
|
||||||
uuid = models.UUIDField(primary_key=True)
|
|
||||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,5 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block page_title %}{% translate "Client Error" %}{% endblock %}
|
|
|
@ -1,5 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block page_title %}{% translate "Forbidden" %}{% endblock %}
|
|
|
@ -1,5 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block page_title %}{% translate "Not Found" %}{% endblock %}
|
|
|
@ -1,5 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block page_title %}{% translate "Server Error" %}{% endblock %}
|
|
|
@ -1,23 +0,0 @@
|
||||||
{% load django_bootstrap5 %}
|
|
||||||
{% load i18n %}
|
|
||||||
{% load static %}
|
|
||||||
{% get_current_language as LANGUAGE_CODE %}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="{{ LANGUAGE_CODE }}">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
||||||
<title>{% block title %}{% block page_title %}{% endblock %} – lelcsc{% endblock %}</title>
|
|
||||||
<link rel="icon" href="data:,%89PNG%0D%0A%1A%0A">
|
|
||||||
<link rel="stylesheet" href="{% static 'bootstrap.min.css' %}">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{% block body %}
|
|
||||||
{% block messages %}
|
|
||||||
{% bootstrap_messages %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
{% endblock %}
|
|
||||||
{% endblock %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,13 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% load django_bootstrap5 %}
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block page_title %}{% translate "Overview" %}{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<form action="{% url 'oidc_logout' %}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{% bootstrap_button button_type="submit" content="Log out" %}
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
|
@ -1,5 +0,0 @@
|
||||||
{% extends "base.html" %}
|
|
||||||
|
|
||||||
{% load i18n %}
|
|
||||||
|
|
||||||
{% block page_title %}{% translate "Unauthorized" %}{% endblock %}
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
|
@ -1,8 +0,0 @@
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from . import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("", views.index, name="index"),
|
|
||||||
path("unauthorized", views.unauthorized, name="unauthorized"),
|
|
||||||
]
|
|
|
@ -1,11 +0,0 @@
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.shortcuts import render
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def index(request):
|
|
||||||
return render(request, "core/index.html", {})
|
|
||||||
|
|
||||||
|
|
||||||
def unauthorized(request):
|
|
||||||
return render(request, "core/unauthorized.html", {})
|
|
|
@ -12,25 +12,20 @@ https://docs.djangoproject.com/en/5.1/ref/settings/
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from environ import Env
|
|
||||||
|
|
||||||
env = Env(DEBUG=(bool, False))
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
Env.read_env(BASE_DIR / ".env")
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = env("SECRET_KEY")
|
SECRET_KEY = "django-insecure-ed5#_cecl9g_7c%c@!v7#0bz)8y(nscc80%z%b^sm&rw1!%m+j"
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = env("DEBUG")
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS")
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
@ -47,7 +42,6 @@ INSTALLED_APPS = [
|
||||||
"django_bootstrap5",
|
"django_bootstrap5",
|
||||||
"mozilla_django_oidc",
|
"mozilla_django_oidc",
|
||||||
# local
|
# local
|
||||||
"lelcsc.core",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
@ -85,11 +79,10 @@ WSGI_APPLICATION = "lelcsc.wsgi.application"
|
||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": (
|
"default": {
|
||||||
env.db("DATABASE_URL")
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
if env("DATABASE_URL")
|
"NAME": BASE_DIR / "db.sqlite3",
|
||||||
else {"ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3"}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -117,7 +110,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
|
||||||
LANGUAGE_CODE = "en-us"
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
TIME_ZONE = env("TIME_ZONE")
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
|
@ -133,31 +126,3 @@ STATIC_URL = "static/"
|
||||||
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = [
|
|
||||||
"lelcsc.core.auth.OIDCAuthenticationBackend",
|
|
||||||
]
|
|
||||||
|
|
||||||
AUTH_USER_MODEL = "lelcsc_core.User"
|
|
||||||
|
|
||||||
LOGIN_REDIRECT_URL = "/"
|
|
||||||
|
|
||||||
LOGIN_URL = "oidc_authentication_init"
|
|
||||||
|
|
||||||
LOGOUT_REDIRECT_URL = LOGIN_REDIRECT_URL_FAILURE = "/unauthorized"
|
|
||||||
|
|
||||||
OIDC_ADDITIONAL_SCOPES = env("OIDC_ADDITIONAL_SCOPES", default="")
|
|
||||||
|
|
||||||
OIDC_OP_AUTHORIZATION_ENDPOINT = env("OIDC_OP_AUTHORIZATION_ENDPOINT")
|
|
||||||
OIDC_OP_JWKS_ENDPOINT = env("OIDC_OP_JWKS_ENDPOINT", default=None)
|
|
||||||
OIDC_OP_TOKEN_ENDPOINT = env("OIDC_OP_TOKEN_ENDPOINT")
|
|
||||||
OIDC_OP_USER_ENDPOINT = env("OIDC_OP_USER_ENDPOINT")
|
|
||||||
|
|
||||||
OIDC_ROLES_CLAIM = env("OIDC_ROLES_CLAIM")
|
|
||||||
|
|
||||||
OIDC_RP_CLIENT_ID = env("OIDC_RP_CLIENT_ID")
|
|
||||||
OIDC_RP_CLIENT_SECRET = env("OIDC_RP_CLIENT_SECRET")
|
|
||||||
OIDC_RP_SCOPES = f"openid profile email{' ' if OIDC_ADDITIONAL_SCOPES else ''}{OIDC_ADDITIONAL_SCOPES}"
|
|
||||||
OIDC_RP_SIGN_ALGO = env("OIDC_RP_SIGN_ALGO", default="HS256")
|
|
||||||
|
|
||||||
OIDC_SUPERUSER_ROLE = env("OIDC_SUPERUSER_ROLE")
|
|
||||||
|
|
|
@ -16,10 +16,8 @@ Including another URLconf
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", include("lelcsc.core.urls")),
|
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("oidc/", include("mozilla_django_oidc.urls")),
|
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue