From 1c24cbea2666b64b8681357e780efcae18413699 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Mon, 23 Dec 2024 09:26:14 +0100 Subject: [PATCH 1/4] # OAuth Token revocation * adds revocation of user tokens on logout --- api/models/token.py | 19 +++++++++++++++++++ konova/views/logout.py | 5 +++++ 2 files changed, 24 insertions(+) diff --git a/api/models/token.py b/api/models/token.py index dac0b4a7..698202ed 100644 --- a/api/models/token.py +++ b/api/models/token.py @@ -155,3 +155,22 @@ class OAuthToken(UuidModel): return user + def revoke(self) -> (int, int): + """ Revokes the tokens of the user + + Returns: + revocation_status_codes (tuple): HTTP status code for revocation of access_token and refresh_token + """ + revoke_url = f"{SSO_SERVER_BASE}o/revoke_token/" + token = self.refresh_token + revocation_status_codes = requests.post( + revoke_url, + data={ + 'token': token, + 'token_type_hint': "refresh_token", + }, + auth=(OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET), + ).status_code + + return revocation_status_codes + diff --git a/konova/views/logout.py b/konova/views/logout.py index fe4d0db4..57a34fd6 100644 --- a/konova/views/logout.py +++ b/konova/views/logout.py @@ -24,5 +24,10 @@ class LogoutView(View): Returns: A redirect """ + user = request.user + oauth_token = user.oauth_token + if oauth_token: + oauth_token.revoke() + logout(request) return redirect(SSO_SERVER_BASE) -- 2.45.2 From 9149e4cbd378ec73c82a28312c2e0641e9e4c680 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Mon, 23 Dec 2024 10:45:08 +0100 Subject: [PATCH 2/4] # Propagation improvement * fixes documentation and variable names on oauth token revocation * introduces private key for propagation * changes key usage in decryption of propagated user data from oauth_client_id to private propagation key --- .env.sample | 1 + api/models/token.py | 13 ++++++++----- konova/sub_settings/sso_settings.py | 2 ++ konova/views/oauth.py | 8 ++++---- user/views/propagate.py | 4 ++-- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.env.sample b/.env.sample index 4960a541..3e4c29ed 100644 --- a/.env.sample +++ b/.env.sample @@ -37,6 +37,7 @@ SSO_SERVER_BASE_URL=https://login.naturschutz.rlp.de OAUTH_CODE_VERIFIER=CHANGE_ME OAUTH_CLIENT_ID=CHANGE_ME OAUTH_CLIENT_SECRET=CHANGE_ME +PROPAGATION_SECRET=CHANGE_ME # RabbitMQ ## For connections to EGON diff --git a/api/models/token.py b/api/models/token.py index 698202ed..84c56496 100644 --- a/api/models/token.py +++ b/api/models/token.py @@ -155,15 +155,18 @@ class OAuthToken(UuidModel): return user - def revoke(self) -> (int, int): - """ Revokes the tokens of the user + def revoke(self) -> int: + """ Revokes the OAuth2 token of the user + + (/o/revoke_token/ indeed removes the corresponding access token on provider side and invalidates the + submitted refresh token in one step) Returns: - revocation_status_codes (tuple): HTTP status code for revocation of access_token and refresh_token + revocation_status_code (int): HTTP status code for revocation of refresh_token """ revoke_url = f"{SSO_SERVER_BASE}o/revoke_token/" token = self.refresh_token - revocation_status_codes = requests.post( + revocation_status_code = requests.post( revoke_url, data={ 'token': token, @@ -172,5 +175,5 @@ class OAuthToken(UuidModel): auth=(OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET), ).status_code - return revocation_status_codes + return revocation_status_code diff --git a/konova/sub_settings/sso_settings.py b/konova/sub_settings/sso_settings.py index 7ff66777..2a50e60c 100644 --- a/konova/sub_settings/sso_settings.py +++ b/konova/sub_settings/sso_settings.py @@ -16,3 +16,5 @@ OAUTH_CODE_VERIFIER = env("OAUTH_CODE_VERIFIER") OAUTH_CLIENT_ID = env("OAUTH_CLIENT_ID") OAUTH_CLIENT_SECRET = env("OAUTH_CLIENT_SECRET") + +PROPAGATION_SECRET = env("PROPAGATION_SECRET") diff --git a/konova/views/oauth.py b/konova/views/oauth.py index 5748d482..8649e164 100644 --- a/konova/views/oauth.py +++ b/konova/views/oauth.py @@ -115,10 +115,10 @@ class OAuthCallbackView(View): if status_code_invalid: raise RuntimeError(f"OAuth access token could not be fetched: {access_code_response.text}") - oauth_access_token = OAuthToken.from_access_token_response(access_code_response_body, received_on) - oauth_access_token.save() - user = oauth_access_token.update_and_get_user() - user.oauth_replace_token(oauth_access_token) + oauth_token = OAuthToken.from_access_token_response(access_code_response_body, received_on) + oauth_token.save() + user = oauth_token.update_and_get_user() + user.oauth_replace_token(oauth_token) login(request, user) return redirect("home") diff --git a/user/views/propagate.py b/user/views/propagate.py index e6a0e5c9..3afb6fcf 100644 --- a/user/views/propagate.py +++ b/user/views/propagate.py @@ -16,7 +16,7 @@ from django.utils.decorators import method_decorator from django.views import View from django.views.decorators.csrf import csrf_exempt -from konova.sub_settings.sso_settings import OAUTH_CLIENT_ID +from konova.sub_settings.sso_settings import PROPAGATION_SECRET from user.models import User @@ -36,7 +36,7 @@ class PropagateUserView(View): # Decrypt encrypted_body = request.body _hash = hashlib.md5() - _hash.update(OAUTH_CLIENT_ID.encode("utf-8")) + _hash.update(PROPAGATION_SECRET.encode("utf-8")) key = base64.urlsafe_b64encode(_hash.hexdigest().encode("utf-8")) fernet = Fernet(key) body = fernet.decrypt(encrypted_body).decode("utf-8") -- 2.45.2 From d677ac6b5ae084077ab7a0e2900eb6aa3a6864df Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Mon, 23 Dec 2024 11:08:41 +0100 Subject: [PATCH 3/4] # Map proxy enhancement * adds whitelisting for map proxy hosts --- .env.sample | 1 + konova/sub_settings/lanis_settings.py | 4 ++++ konova/views/map_proxy.py | 14 ++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/.env.sample b/.env.sample index 3e4c29ed..31c0ac71 100644 --- a/.env.sample +++ b/.env.sample @@ -24,6 +24,7 @@ DEFAULT_FROM_EMAIL=service@ksp.de # Proxy PROXY=CHANGE_ME +MAP_PROXY_HOST_WHITELIST=CHANGE_ME_1,CHANGE_ME_2 GEOPORTAL_RLP_USER=CHANGE_ME GEOPORTAL_RLP_PASSWORD=CHANGE_ME diff --git a/konova/sub_settings/lanis_settings.py b/konova/sub_settings/lanis_settings.py index 72082931..25ed4d25 100644 --- a/konova/sub_settings/lanis_settings.py +++ b/konova/sub_settings/lanis_settings.py @@ -5,6 +5,7 @@ Contact: michel.peltriaux@sgdnord.rlp.de Created on: 31.01.22 """ +from konova.sub_settings.django_settings import env # MAPS DEFAULT_LAT = 50.00 @@ -28,3 +29,6 @@ LANIS_ZOOM_LUT = { 1000: 30, 500: 31, } + +MAP_PROXY_HOST_WHITELIST = env.list("MAP_PROXY_HOST_WHITELIST") +i = 0 \ No newline at end of file diff --git a/konova/views/map_proxy.py b/konova/views/map_proxy.py index 790ab8dc..c84622c6 100644 --- a/konova/views/map_proxy.py +++ b/konova/views/map_proxy.py @@ -9,6 +9,7 @@ import json from json import JSONDecodeError import requests +import urllib3.util from django.contrib.auth.decorators import login_required from django.http import JsonResponse, HttpRequest from django.utils.decorators import method_decorator @@ -18,6 +19,7 @@ from django.utils.translation import gettext_lazy as _ from requests.auth import HTTPDigestAuth +from konova.sub_settings.lanis_settings import MAP_PROXY_HOST_WHITELIST from konova.sub_settings.proxy_settings import PROXIES, GEOPORTAL_RLP_USER, GEOPORTAL_RLP_PASSWORD @@ -32,6 +34,13 @@ class BaseClientProxyView(View): def dispatch(self, request, *args, **kwargs): return super().dispatch(request, *args, **kwargs) + def _check_with_whitelist(self, url): + parsed_url = urllib3.util.parse_url(url) + parsed_url_host = parsed_url.host + whitelist = set(MAP_PROXY_HOST_WHITELIST) + is_allowed = parsed_url_host in whitelist + return is_allowed + def perform_url_call(self, url, headers={}, auth=None): """ Generic proxied call @@ -59,6 +68,11 @@ class ClientProxyParcelSearch(BaseClientProxyView): def get(self, request: HttpRequest): url = request.META.get("QUERY_STRING") + + is_url_allowed = self._check_with_whitelist(url) + if not is_url_allowed: + raise PermissionError(f"Proxied url '{url}' is not allowed!") + content, response_code = self.perform_url_call(url) try: body = json.loads(content) -- 2.45.2 From 72a5075f3b4728aa15aaae96f585be6bec499be0 Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Mon, 23 Dec 2024 12:03:15 +0100 Subject: [PATCH 4/4] # Update dependencies * updates requirements.txt --- requirements.txt | 66 ++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/requirements.txt b/requirements.txt index e66fb477..0e0c559b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,65 +1,65 @@ -amqp==5.2.0 +amqp==5.3.1 asgiref==3.8.1 -async-timeout==4.0.3 +async-timeout==5.0.1 beautifulsoup4==4.13.0b2 -billiard==4.2.0 -cached-property==1.5.2 +billiard==4.2.1 +cached-property==2.0.1 celery==5.4.0 -certifi==2024.7.4 -cffi==1.17.0 +certifi==2024.12.14 +cffi==1.17.1 chardet==5.2.0 -charset-normalizer==3.3.2 -click==8.1.7 +charset-normalizer==3.4.0 +click==8.1.8 click-didyoumean==0.3.1 click-plugins==1.1.1 click-repl==0.3.0 -coverage==7.5.4 -cryptography==43.0.0 -Deprecated==1.2.14 -Django==5.0.8 +coverage==7.6.9 +cryptography==44.0.0 +Deprecated==1.2.15 +Django==5.1.4 django-autocomplete-light==3.11.0 -django-bootstrap-modal-forms==3.0.4 -django-bootstrap4==24.3 +django-bootstrap-modal-forms==3.0.5 +django-bootstrap4==24.4 django-environ==0.11.2 django-filter==24.3 django-fontawesome-5==1.0.18 -django-oauth-toolkit==2.4.0 +django-oauth-toolkit==3.0.1 django-simple-sso==1.2.0 -django-tables2==2.7.0 -et-xmlfile==1.1.0 -gunicorn==22.0.0 -idna==3.7 -importlib_metadata==8.2.0 +django-tables2==2.7.1 +et_xmlfile==2.0.0 +gunicorn==23.0.0 +idna==3.10 +importlib_metadata==8.5.0 itsdangerous==0.24 jwcrypto==1.5.6 kombu==5.4.0rc1 oauthlib==3.2.2 openpyxl==3.2.0b1 -packaging==24.1 +packaging==24.2 pika==1.3.2 -pillow==10.4.0 -prompt_toolkit==3.0.47 -psycopg==3.2.1 -psycopg-binary==3.2.1 +pillow==11.0.0 +prompt_toolkit==3.0.48 +psycopg==3.2.3 +psycopg-binary==3.2.3 pycparser==2.22 -pyparsing==3.1.2 +pyparsing==3.2.0 pypng==0.20220715.0 -pyproj==3.6.1 +pyproj==3.7.0 python-dateutil==2.9.0.post0 -pytz==2024.1 +pytz==2024.2 PyYAML==6.0.2 qrcode==7.3.1 redis==5.1.0b6 -requests<2.32.0 # kombu 5.4.0rc1 depends on requests<2.32.0 +requests==2.32.3 six==1.16.0 soupsieve==2.5 sqlparse==0.5.1 typing_extensions==4.12.2 -tzdata==2024.1 -urllib3==2.2.2 +tzdata==2024.2 +urllib3==2.3.0 vine==5.1.0 wcwidth==0.2.13 webservices==0.7 wrapt==1.16.0 -xmltodict==0.13.0 -zipp==3.19.2 +xmltodict==0.14.2 +zipp==3.21.0 -- 2.45.2