Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
137 lines
5.2 KiB
Python
137 lines
5.2 KiB
Python
import hashlib
|
|
import time
|
|
from urllib.error import URLError
|
|
|
|
import jwt
|
|
from django.conf import settings
|
|
from django.contrib.auth import login, logout
|
|
from django.contrib.auth.decorators import login_not_required
|
|
from django.contrib.auth.views import LoginView
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
from django.db import IntegrityError, transaction
|
|
from django.http import Http404
|
|
from django.shortcuts import redirect, render
|
|
from django.views.decorators.debug import sensitive_variables
|
|
from django.views.decorators.http import require_GET, require_POST
|
|
|
|
from . import oidc
|
|
from .forms import AuthenticationForm
|
|
from .models import Mitarbeiter, OIDCIdentity
|
|
|
|
|
|
def context(**extra):
|
|
return {"company_name": settings.COMPANY_DISPLAY_NAME, **extra}
|
|
|
|
|
|
@login_not_required
|
|
def company_login(request):
|
|
if settings.NETKINGDOM_ENABLED:
|
|
return welcome(request)
|
|
return LoginView.as_view(
|
|
template_name="accounts/login.html", authentication_form=AuthenticationForm
|
|
)(request)
|
|
|
|
|
|
@login_not_required
|
|
@require_GET
|
|
def welcome(request):
|
|
if not settings.NETKINGDOM_ENABLED:
|
|
return redirect("accounts:login")
|
|
# A setup return never establishes or changes an application session.
|
|
return render(request, "accounts/welcome.html", context())
|
|
|
|
|
|
@login_not_required
|
|
@require_POST
|
|
@sensitive_variables()
|
|
def oidc_start(request):
|
|
if not settings.NETKINGDOM_ENABLED:
|
|
raise Http404
|
|
# Explicit CSRF-protected action clears only this product's previous login.
|
|
logout(request)
|
|
try:
|
|
pending, target = oidc.begin()
|
|
except (ValueError, URLError, TimeoutError, OSError, ImproperlyConfigured):
|
|
return render(request, "accounts/welcome.html", context(login_error=True), status=503)
|
|
request.session["oidc_pending"] = pending
|
|
request.session.set_expiry(600)
|
|
return redirect(target)
|
|
|
|
|
|
@login_not_required
|
|
@require_GET
|
|
@sensitive_variables()
|
|
def oidc_callback(request):
|
|
if not settings.NETKINGDOM_ENABLED:
|
|
raise Http404
|
|
pending = request.session.pop("oidc_pending", None)
|
|
request.session.pop("oidc_confirm", None)
|
|
try:
|
|
if request.GET.get("error") or len(request.GET.getlist("state")) != 1:
|
|
raise oidc.LoginRejectedError("Authorization was not completed")
|
|
verified = oidc.complete(pending, request.GET.get("state", ""), request.GET.get("code", ""))
|
|
except (ValueError, jwt.PyJWTError, URLError, TimeoutError, OSError, ImproperlyConfigured):
|
|
return render(request, "accounts/welcome.html", context(login_error=True), status=403)
|
|
request.session["oidc_confirm"] = verified
|
|
# Remove the authorization code from the address bar before displaying identity.
|
|
return redirect("accounts:oidc_confirm")
|
|
|
|
|
|
@login_not_required
|
|
@sensitive_variables()
|
|
def oidc_confirm(request):
|
|
if not settings.NETKINGDOM_ENABLED:
|
|
raise Http404
|
|
verified = request.session.get("oidc_confirm")
|
|
if (
|
|
not verified
|
|
or verified["expires"] <= time.time()
|
|
or verified.get("tenant") != settings.NETKINGDOM_TENANT
|
|
or verified.get("issuer") != settings.NETKINGDOM_ISSUER
|
|
or verified.get("client") != settings.NETKINGDOM_CLIENT_ID
|
|
):
|
|
request.session.pop("oidc_confirm", None)
|
|
return render(request, "accounts/welcome.html", context(login_error=True), status=403)
|
|
if request.method == "GET":
|
|
return render(request, "accounts/confirm.html", context(identity_label=verified["label"]))
|
|
if request.method != "POST":
|
|
from django.http import HttpResponseNotAllowed
|
|
|
|
return HttpResponseNotAllowed(["GET", "POST"])
|
|
request.session.pop("oidc_confirm", None)
|
|
try:
|
|
with transaction.atomic():
|
|
identity = (
|
|
OIDCIdentity.objects.select_related("user")
|
|
.filter(
|
|
issuer=verified["issuer"],
|
|
subject=verified["subject"],
|
|
)
|
|
.first()
|
|
)
|
|
if identity is None:
|
|
# Never merge by email, display name or directory username.
|
|
username = (
|
|
"nk_"
|
|
+ hashlib.sha256(
|
|
(verified["issuer"] + "\0" + verified["subject"]).encode(),
|
|
).hexdigest()
|
|
)
|
|
user = Mitarbeiter(username=username, first_name=verified["label"][:150])
|
|
user.set_unusable_password()
|
|
user.save()
|
|
identity = OIDCIdentity.objects.create(
|
|
issuer=verified["issuer"],
|
|
subject=verified["subject"],
|
|
user=user,
|
|
)
|
|
user = identity.user
|
|
if not user.is_active or user.is_staff or user.is_superuser:
|
|
raise oidc.LoginRejectedError("Product account is not admitted")
|
|
except (IntegrityError, oidc.LoginRejectedError):
|
|
return render(request, "accounts/welcome.html", context(login_error=True), status=403)
|
|
login(request, user, backend="django.contrib.auth.backends.ModelBackend")
|
|
request.session["oidc_expires"] = verified["expires"]
|
|
request.session["oidc_binding"] = [verified["issuer"], verified["tenant"], verified["client"]]
|
|
request.session.set_expiry(max(1, int(verified["expires"] - time.time())))
|
|
return redirect("home")
|