138 lines
4.7 KiB
Python
138 lines
4.7 KiB
Python
|
|
"""Authentication Views for the People core app."""
|
||
|
|
|
||
|
|
from urllib.parse import urlencode
|
||
|
|
|
||
|
|
from django.contrib import auth
|
||
|
|
from django.core.exceptions import SuspiciousOperation
|
||
|
|
from django.http import HttpResponseRedirect
|
||
|
|
from django.urls import reverse
|
||
|
|
from django.utils import crypto
|
||
|
|
|
||
|
|
from mozilla_django_oidc.utils import (
|
||
|
|
absolutify,
|
||
|
|
)
|
||
|
|
from mozilla_django_oidc.views import (
|
||
|
|
OIDCLogoutView as MozillaOIDCOIDCLogoutView,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class OIDCLogoutView(MozillaOIDCOIDCLogoutView):
|
||
|
|
"""Custom logout view for handling OpenID Connect (OIDC) logout flow.
|
||
|
|
|
||
|
|
Adds support for handling logout callbacks from the identity provider (OP)
|
||
|
|
by initiating the logout flow if the user has an active session.
|
||
|
|
|
||
|
|
The Django session is retained during the logout process to persist the 'state' OIDC parameter.
|
||
|
|
This parameter is crucial for maintaining the integrity of the logout flow between this call
|
||
|
|
and the subsequent callback.
|
||
|
|
"""
|
||
|
|
|
||
|
|
@staticmethod
|
||
|
|
def persist_state(request, state):
|
||
|
|
"""Persist the given 'state' parameter in the session's 'oidc_states' dictionary
|
||
|
|
|
||
|
|
This method is used to store the OIDC state parameter in the session, according to the
|
||
|
|
structure expected by Mozilla Django OIDC's 'add_state_and_verifier_and_nonce_to_session'
|
||
|
|
utility function.
|
||
|
|
"""
|
||
|
|
|
||
|
|
if "oidc_states" not in request.session or not isinstance(
|
||
|
|
request.session["oidc_states"], dict
|
||
|
|
):
|
||
|
|
request.session["oidc_states"] = {}
|
||
|
|
|
||
|
|
request.session["oidc_states"][state] = {}
|
||
|
|
request.session.save()
|
||
|
|
|
||
|
|
def construct_oidc_logout_url(self, request):
|
||
|
|
"""Create the redirect URL for interfacing with the OIDC provider.
|
||
|
|
|
||
|
|
Retrieves the necessary parameters from the session and constructs the URL
|
||
|
|
required to initiate logout with the OpenID Connect provider.
|
||
|
|
|
||
|
|
If no ID token is found in the session, the logout flow will not be initiated,
|
||
|
|
and the method will return the default redirect URL.
|
||
|
|
|
||
|
|
The 'state' parameter is generated randomly and persisted in the session to ensure
|
||
|
|
its integrity during the subsequent callback.
|
||
|
|
"""
|
||
|
|
|
||
|
|
oidc_logout_endpoint = self.get_settings("OIDC_OP_LOGOUT_ENDPOINT")
|
||
|
|
|
||
|
|
if not oidc_logout_endpoint:
|
||
|
|
return self.redirect_url
|
||
|
|
|
||
|
|
reverse_url = reverse("oidc_logout_callback")
|
||
|
|
id_token = request.session.get("oidc_id_token", None)
|
||
|
|
|
||
|
|
if not id_token:
|
||
|
|
return self.redirect_url
|
||
|
|
|
||
|
|
query = {
|
||
|
|
"id_token_hint": id_token,
|
||
|
|
"state": crypto.get_random_string(self.get_settings("OIDC_STATE_SIZE", 32)),
|
||
|
|
"post_logout_redirect_uri": absolutify(request, reverse_url),
|
||
|
|
}
|
||
|
|
|
||
|
|
self.persist_state(request, query["state"])
|
||
|
|
|
||
|
|
return f"{oidc_logout_endpoint}?{urlencode(query)}"
|
||
|
|
|
||
|
|
def post(self, request):
|
||
|
|
"""Handle user logout.
|
||
|
|
|
||
|
|
If the user is not authenticated, redirects to the default logout URL.
|
||
|
|
Otherwise, constructs the OIDC logout URL and redirects the user to start
|
||
|
|
the logout process.
|
||
|
|
|
||
|
|
If the user is redirected to the default logout URL, ensure her Django session
|
||
|
|
is terminated.
|
||
|
|
"""
|
||
|
|
|
||
|
|
logout_url = self.redirect_url
|
||
|
|
|
||
|
|
if request.user.is_authenticated:
|
||
|
|
logout_url = self.construct_oidc_logout_url(request)
|
||
|
|
|
||
|
|
# If the user is not redirected to the OIDC provider, ensure logout
|
||
|
|
if logout_url == self.redirect_url:
|
||
|
|
auth.logout(request)
|
||
|
|
|
||
|
|
return HttpResponseRedirect(logout_url)
|
||
|
|
|
||
|
|
|
||
|
|
class OIDCLogoutCallbackView(MozillaOIDCOIDCLogoutView):
|
||
|
|
"""Custom view for handling the logout callback from the OpenID Connect (OIDC) provider.
|
||
|
|
|
||
|
|
Handles the callback after logout from the identity provider (OP).
|
||
|
|
Verifies the state parameter and performs necessary logout actions.
|
||
|
|
|
||
|
|
The Django session is maintained during the logout process to ensure the integrity
|
||
|
|
of the logout flow initiated in the previous step.
|
||
|
|
"""
|
||
|
|
|
||
|
|
http_method_names = ["get"]
|
||
|
|
|
||
|
|
def get(self, request):
|
||
|
|
"""Handle the logout callback.
|
||
|
|
|
||
|
|
If the user is not authenticated, redirects to the default logout URL.
|
||
|
|
Otherwise, verifies the state parameter and performs necessary logout actions.
|
||
|
|
"""
|
||
|
|
|
||
|
|
if not request.user.is_authenticated:
|
||
|
|
return HttpResponseRedirect(self.redirect_url)
|
||
|
|
|
||
|
|
state = request.GET.get("state")
|
||
|
|
|
||
|
|
if state not in request.session.get("oidc_states", {}):
|
||
|
|
msg = "OIDC callback state not found in session `oidc_states`!"
|
||
|
|
raise SuspiciousOperation(msg)
|
||
|
|
|
||
|
|
del request.session["oidc_states"][state]
|
||
|
|
request.session.save()
|
||
|
|
|
||
|
|
auth.logout(request)
|
||
|
|
|
||
|
|
return HttpResponseRedirect(self.redirect_url)
|