e3d5ee82ee
* integrates authlib into project * adds authlib package to requirements.txt * refactors internal konova/utils/generators/generate_token method to use authlib's generate_token method and act as wrapper for backwards compatibility * refactors OAuth views to be based on authlib's oauth client implementation * refactors oauth model to work with authlib's oauth2 tokens
112 lines
3.3 KiB
Python
112 lines
3.3 KiB
Python
"""
|
|
Author: Michel Peltriaux
|
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
Contact: ksp-servicestelle@sgdnord.rlp.de
|
|
Created on: 26.04.24
|
|
|
|
"""
|
|
import base64
|
|
import hashlib
|
|
|
|
from authlib.common.security import generate_token
|
|
from authlib.integrations.base_client import OAuthError
|
|
from authlib.integrations.django_client import OAuth
|
|
|
|
from django.contrib.auth import login
|
|
from django.http import HttpRequest, HttpResponse
|
|
from django.shortcuts import redirect
|
|
from django.urls import reverse
|
|
from django.views import View
|
|
|
|
from api.models import OAuthToken
|
|
from konova.sub_settings.django_settings import BASE_URL
|
|
from konova.sub_settings.sso_settings import SSO_SERVER_BASE, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET
|
|
|
|
|
|
|
|
_OAUTH = OAuth()
|
|
_OAUTH.register(
|
|
name="sso",
|
|
client_id=OAUTH_CLIENT_ID,
|
|
client_secret=OAUTH_CLIENT_SECRET,
|
|
authorize_url=f"{SSO_SERVER_BASE}o/authorize/",
|
|
access_token_url=f"{SSO_SERVER_BASE}o/token/",
|
|
code_challenge_method="S256",
|
|
client_kwargs={},
|
|
)
|
|
|
|
class OAuthLoginView(View):
|
|
""" View for OAuth2 login step
|
|
|
|
"""
|
|
def get(self, request: HttpRequest, *args, **kwargs):
|
|
redirect_uri = f'{BASE_URL}{reverse("oauth-callback")}'
|
|
|
|
# create new PKCE-Verifier
|
|
code_verifier, code_challenge = self.__create_code_challenge()
|
|
|
|
# keep verifier in session, so it can be read out for callback
|
|
request.session["oauth_code_verifier"] = code_verifier
|
|
|
|
return _OAUTH.sso.authorize_redirect(
|
|
request,
|
|
redirect_uri,
|
|
code_verifier=code_verifier,
|
|
)
|
|
|
|
def __create_code_challenge(self):
|
|
"""
|
|
Creates a code verifier and code challenge for extra security.
|
|
See https://django-oauth-toolkit.readthedocs.io/en/latest/getting_started.html#authorization-code for further
|
|
information
|
|
|
|
Returns:
|
|
|
|
"""
|
|
code_verifier = generate_token(128)
|
|
|
|
code_challenge = hashlib.sha256(code_verifier.encode('utf-8')).digest()
|
|
code_challenge = base64.urlsafe_b64encode(code_challenge).decode('utf-8').replace('=', '')
|
|
return code_verifier, code_challenge
|
|
|
|
class OAuthCallbackView(View):
|
|
""" View for OAuth2 callback step
|
|
|
|
"""
|
|
def get(self, request: HttpRequest, *args, **kwargs):
|
|
# Get code verifier (created on oatuh-login step) from session
|
|
code_verifier = request.session.pop(
|
|
"oauth_code_verifier",
|
|
None,
|
|
)
|
|
if not code_verifier:
|
|
return HttpResponse(
|
|
"PKCE code_verifier missing or is invalid.",
|
|
status=400,
|
|
)
|
|
|
|
try:
|
|
token = _OAUTH.sso.authorize_access_token(
|
|
request
|
|
)
|
|
except OAuthError as e:
|
|
print(e)
|
|
return HttpResponse(
|
|
"OAuth-Login failed while authorizing with OAuth.",
|
|
status=400,
|
|
)
|
|
try:
|
|
oauth_token = OAuthToken.create_from_oauth2_token(token)
|
|
except AssertionError:
|
|
return HttpResponse(
|
|
"OAuth-Login failed due to unexpected token content.",
|
|
status=400,
|
|
)
|
|
|
|
oauth_token.save()
|
|
user = oauth_token.update_and_get_user()
|
|
user.oauth_replace_token(oauth_token)
|
|
|
|
login(request, user)
|
|
return redirect("home")
|