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