vergabe-teilnahme/vergabe_teilnahme/apps/accounts/views.py
tegwick 8be281025b
Some checks failed
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Application acceptance / application-tests (push) Has been cancelled
Build and Publish Container Image / build-and-push (push) Successful in 40s
Add company welcome and verified NetKingdom sign-in
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a092fe-13b1-7f12-ac74-7d258af4d79c
2026-09-12 02:43:31 +02:00

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")