From ec2514e49fcbdd146250224463350de19dcac0a5 Mon Sep 17 00:00:00 2001 From: Anton Alekseev Date: Tue, 23 Dec 2025 11:11:04 +0200 Subject: [PATCH 1/2] SV-246: Add on_failure hook for side-effects --- esteid/mixins.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/esteid/mixins.py b/esteid/mixins.py index eafd62f..5bab3f9 100644 --- a/esteid/mixins.py +++ b/esteid/mixins.py @@ -1,7 +1,7 @@ import json import logging from http import HTTPStatus -from typing import Callable, TYPE_CHECKING +from typing import Callable, Literal, TYPE_CHECKING from django.core.exceptions import ValidationError as DjangoValidationError from django.http import Http404, HttpRequest, JsonResponse, QueryDict @@ -30,6 +30,9 @@ class RequestType(HttpRequest): logger = logging.getLogger(__name__) +SessionStageType = Literal["start", "finish"] + + class SessionViewMixin: """ Provides POST and PATCH method handlers for auth/signing session management. @@ -52,7 +55,16 @@ def report_error(self, e: EsteidError): def handle_user_cancel(self): pass - def handle_errors(self, e: Exception, stage="start"): + def on_failure(self, e: Exception, stage: SessionStageType, request: HttpRequest): + """ + Hook called when exception occurs during authentication/signing. + It runs before "handle_errors" and allows to perform logging, auditing, or other side effects. + """ + pass + + def handle_errors(self, e: Exception, stage: SessionStageType = "start", request: HttpRequest = None): + self.on_failure(e, stage, request) + if isinstance(e, EsteidError): if isinstance(e, CanceledByUser): self.handle_user_cancel() @@ -84,7 +96,7 @@ def post(self, request, *args, **kwargs): try: return self.start_session(request, *args, **kwargs) except Exception as e: - return self.handle_errors(e, stage="start") + return self.handle_errors(e, stage="start", request=request) def patch(self, request, *args, **kwargs): """ @@ -93,7 +105,7 @@ def patch(self, request, *args, **kwargs): try: return self.finish_session(request, *args, **kwargs) except Exception as e: - return self.handle_errors(e, stage="finish") + return self.handle_errors(e, stage="finish", request=request) class DjangoRestCompatibilityMixin(SessionViewMixin): From 0167ac695efaf5ae63a37718a7aeab94ba54a1f3 Mon Sep 17 00:00:00 2001 From: Anton Alekseev Date: Tue, 23 Dec 2025 15:45:58 +0200 Subject: [PATCH 2/2] fix: code-review fixes, rolling back a bit of implementation --- esteid/mixins.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/esteid/mixins.py b/esteid/mixins.py index 5bab3f9..39f5243 100644 --- a/esteid/mixins.py +++ b/esteid/mixins.py @@ -1,7 +1,8 @@ import json import logging +from contextvars import ContextVar from http import HTTPStatus -from typing import Callable, Literal, TYPE_CHECKING +from typing import Callable, Optional, TYPE_CHECKING from django.core.exceptions import ValidationError as DjangoValidationError from django.http import Http404, HttpRequest, JsonResponse, QueryDict @@ -30,7 +31,12 @@ class RequestType(HttpRequest): logger = logging.getLogger(__name__) -SessionStageType = Literal["start", "finish"] +_current_request: ContextVar[Optional[HttpRequest]] = ContextVar("_current_request", default=None) + + +def get_current_request() -> Optional[HttpRequest]: + """Public getter for the current request stored in context variable.""" + return _current_request.get() class SessionViewMixin: @@ -55,16 +61,7 @@ def report_error(self, e: EsteidError): def handle_user_cancel(self): pass - def on_failure(self, e: Exception, stage: SessionStageType, request: HttpRequest): - """ - Hook called when exception occurs during authentication/signing. - It runs before "handle_errors" and allows to perform logging, auditing, or other side effects. - """ - pass - - def handle_errors(self, e: Exception, stage: SessionStageType = "start", request: HttpRequest = None): - self.on_failure(e, stage, request) - + def handle_errors(self, e: Exception, stage="start"): if isinstance(e, EsteidError): if isinstance(e, CanceledByUser): self.handle_user_cancel() @@ -93,19 +90,25 @@ def post(self, request, *args, **kwargs): """ Handles session start requests """ + token = _current_request.set(request) try: return self.start_session(request, *args, **kwargs) except Exception as e: - return self.handle_errors(e, stage="start", request=request) + return self.handle_errors(e, stage="start") + finally: + _current_request.reset(token) def patch(self, request, *args, **kwargs): """ Handles session finish requests """ + token = _current_request.set(request) try: return self.finish_session(request, *args, **kwargs) except Exception as e: - return self.handle_errors(e, stage="finish", request=request) + return self.handle_errors(e, stage="finish") + finally: + _current_request.reset(token) class DjangoRestCompatibilityMixin(SessionViewMixin):