diff --git a/src/openforms/authentication/contrib/digid_eherkenning_oidc/backends.py b/src/openforms/authentication/contrib/digid_eherkenning_oidc/backends.py index 30ec03b87f..6bf48452ee 100644 --- a/src/openforms/authentication/contrib/digid_eherkenning_oidc/backends.py +++ b/src/openforms/authentication/contrib/digid_eherkenning_oidc/backends.py @@ -1,12 +1,20 @@ import logging +from copy import deepcopy + +from glom import PathAccessError, glom from digid_eherkenning_oidc_generics.backends import OIDCAuthenticationBackend from digid_eherkenning_oidc_generics.mixins import ( SoloConfigDigiDMixin, SoloConfigEHerkenningMixin, ) +from digid_eherkenning_oidc_generics.utils import obfuscate_claim -from .constants import DIGID_OIDC_AUTH_SESSION_KEY, EHERKENNING_OIDC_AUTH_SESSION_KEY +from .constants import ( + DIGID_MACHTIGEN_OIDC_AUTH_SESSION_KEY, + DIGID_OIDC_AUTH_SESSION_KEY, + EHERKENNING_OIDC_AUTH_SESSION_KEY, +) logger = logging.getLogger(__name__) @@ -27,3 +35,55 @@ class OIDCAuthenticationEHerkenningBackend( """ session_key = EHERKENNING_OIDC_AUTH_SESSION_KEY + + +class OIDCAuthenticationDigiDMachtigenBackend( + SoloConfigDigiDMixin, OIDCAuthenticationBackend +): + session_key = DIGID_MACHTIGEN_OIDC_AUTH_SESSION_KEY + + def extract_claims(self, payload: dict) -> None: + claim_names = [ + self.config.vertegenwoordigde_claim_name, + self.config.gemachtigde_claim_name, + ] + + self.request.session[self.session_key] = {} + for claim_name in claim_names: + self.request.session[self.session_key][claim_name] = glom( + payload, claim_name + ) + + def log_received_claims(self, claims: dict): + copied_claims = deepcopy(claims) + + def _obfuscate_claims_values(claims_to_obfuscate: dict) -> dict: + for key, value in claims_to_obfuscate.items(): + if isinstance(value, dict): + _obfuscate_claims_values(value) + else: + claims_to_obfuscate[key] = obfuscate_claim(value) + return claims_to_obfuscate + + obfuscated_claims = _obfuscate_claims_values(copied_claims) + logger.debug("OIDC claims received: %s", obfuscated_claims) + + def verify_claims(self, claims: dict) -> bool: + expected_claim_names = [ + self.config.vertegenwoordigde_claim_name, + self.config.gemachtigde_claim_name, + ] + + self.log_received_claims(claims) + + for expected_claim in expected_claim_names: + try: + glom(claims, expected_claim) + except PathAccessError: + logger.error( + "`%s` not in OIDC claims, cannot proceed with DigiD Machtigen authentication", + expected_claim, + ) + return False + + return True diff --git a/src/openforms/authentication/contrib/digid_eherkenning_oidc/constants.py b/src/openforms/authentication/contrib/digid_eherkenning_oidc/constants.py index a2f917db49..af3cb390cb 100644 --- a/src/openforms/authentication/contrib/digid_eherkenning_oidc/constants.py +++ b/src/openforms/authentication/contrib/digid_eherkenning_oidc/constants.py @@ -1,2 +1,3 @@ DIGID_OIDC_AUTH_SESSION_KEY = "digid_oidc:bsn" EHERKENNING_OIDC_AUTH_SESSION_KEY = "eherkenning_oidc:kvk" +DIGID_MACHTIGEN_OIDC_AUTH_SESSION_KEY = "digid_machtigen_oidc:machtigen" diff --git a/src/openforms/authentication/contrib/digid_eherkenning_oidc/digid_machtigen_urls.py b/src/openforms/authentication/contrib/digid_eherkenning_oidc/digid_machtigen_urls.py new file mode 100644 index 0000000000..66fb122364 --- /dev/null +++ b/src/openforms/authentication/contrib/digid_eherkenning_oidc/digid_machtigen_urls.py @@ -0,0 +1,24 @@ +from django.urls import path + +from mozilla_django_oidc.urls import urlpatterns + +from .views import ( + DigiDMachtigenOIDCAuthenticationCallbackView, + DigiDMachtigenOIDCAuthenticationRequestView, +) + +app_name = "digid_machtigen_oidc" + + +urlpatterns = [ + path( + "callback/", + DigiDMachtigenOIDCAuthenticationCallbackView.as_view(), + name="callback", + ), + path( + "authenticate/", + DigiDMachtigenOIDCAuthenticationRequestView.as_view(), + name="init", + ), +] + urlpatterns diff --git a/src/openforms/authentication/contrib/digid_eherkenning_oidc/plugin.py b/src/openforms/authentication/contrib/digid_eherkenning_oidc/plugin.py index 1313b16357..ca296a7a58 100644 --- a/src/openforms/authentication/contrib/digid_eherkenning_oidc/plugin.py +++ b/src/openforms/authentication/contrib/digid_eherkenning_oidc/plugin.py @@ -21,7 +21,11 @@ from ...constants import CO_SIGN_PARAMETER, FORM_AUTH_SESSION_KEY, AuthAttribute from ...exceptions import InvalidCoSignData from ...registry import register -from .constants import DIGID_OIDC_AUTH_SESSION_KEY, EHERKENNING_OIDC_AUTH_SESSION_KEY +from .constants import ( + DIGID_MACHTIGEN_OIDC_AUTH_SESSION_KEY, + DIGID_OIDC_AUTH_SESSION_KEY, + EHERKENNING_OIDC_AUTH_SESSION_KEY, +) class OIDCAuthentication(BasePlugin): @@ -59,6 +63,15 @@ def handle_co_sign( "fields": {}, } + def add_claims_to_sessions_if_not_cosigning(self, claim, request): + # set the session auth key only if we're not co-signing + if claim and CO_SIGN_PARAMETER not in request.GET: + request.session[FORM_AUTH_SESSION_KEY] = { + "plugin": self.identifier, + "attribute": self.provides_auth, + "value": claim, + } + def handle_return(self, request, form): """ Redirect to form URL. @@ -69,13 +82,7 @@ def handle_return(self, request, form): claim = request.session.get(self.session_key) - # set the session auth key only if we're not co-signing - if claim and CO_SIGN_PARAMETER not in request.GET: - request.session[FORM_AUTH_SESSION_KEY] = { - "plugin": self.identifier, - "attribute": self.provides_auth, - "value": claim, - } + self.add_claims_to_sessions_if_not_cosigning(claim, request) return HttpResponseRedirect(form_url) @@ -129,3 +136,23 @@ def get_label(self) -> str: def get_logo(self, request) -> Optional[LoginLogo]: return LoginLogo(title=self.get_label(), **get_eherkenning_logo(request)) + + +@register("digid_machtigen_oidc") +class DigiDMachtigenOIDCAuthentication(OIDCAuthentication): + verbose_name = _("DigiD Machtigen via OpenID Connect") + provides_auth = AuthAttribute.bsn + init_url = "digid_machtigen_oidc:init" + session_key = DIGID_MACHTIGEN_OIDC_AUTH_SESSION_KEY + config_class = OpenIDConnectPublicConfig + + def add_claims_to_sessions_if_not_cosigning(self, claim, request): + # set the session auth key only if we're not co-signing + if claim and CO_SIGN_PARAMETER not in request.GET: + config = OpenIDConnectPublicConfig.get_solo() + request.session[FORM_AUTH_SESSION_KEY] = { + "plugin": self.identifier, + "attribute": self.provides_auth, + "value": claim[config.vertegenwoordigde_claim_name], + "machtigen": request.session[DIGID_MACHTIGEN_OIDC_AUTH_SESSION_KEY], + } diff --git a/src/openforms/authentication/contrib/digid_eherkenning_oidc/views.py b/src/openforms/authentication/contrib/digid_eherkenning_oidc/views.py index 63e5e4d0ce..39786926a2 100644 --- a/src/openforms/authentication/contrib/digid_eherkenning_oidc/views.py +++ b/src/openforms/authentication/contrib/digid_eherkenning_oidc/views.py @@ -19,6 +19,7 @@ from ...views import BACKEND_OUTAGE_RESPONSE_PARAMETER from .backends import ( OIDCAuthenticationDigiDBackend, + OIDCAuthenticationDigiDMachtigenBackend, OIDCAuthenticationEHerkenningBackend, ) @@ -92,3 +93,16 @@ class eHerkenningOIDCAuthenticationCallbackView( ): plugin_identifier = "eherkenning_oidc" auth_backend_class = OIDCAuthenticationEHerkenningBackend + + +class DigiDMachtigenOIDCAuthenticationRequestView( + SoloConfigDigiDMixin, OIDCAuthenticationRequestView +): + plugin_identifier = "digid_machtigen_oidc" + + +class DigiDMachtigenOIDCAuthenticationCallbackView( + SoloConfigDigiDMixin, OIDCAuthenticationCallbackView +): + plugin_identifier = "digid_machtigen_oidc" + auth_backend_class = OIDCAuthenticationDigiDMachtigenBackend diff --git a/src/openforms/authentication/utils.py b/src/openforms/authentication/utils.py index 6189d34d64..3da8e10dc1 100644 --- a/src/openforms/authentication/utils.py +++ b/src/openforms/authentication/utils.py @@ -1,4 +1,4 @@ -from typing import Literal, TypedDict +from typing import Literal, Optional, TypedDict from openforms.submissions.models import Submission @@ -13,6 +13,7 @@ class FormAuth(TypedDict): AuthAttribute.pseudo, ] value: str + machtigen: Optional[dict] def store_auth_details(submission: Submission, form_auth: FormAuth) -> None: diff --git a/src/openforms/urls.py b/src/openforms/urls.py index aefb8b9de1..8a11ecbdc6 100644 --- a/src/openforms/urls.py +++ b/src/openforms/urls.py @@ -85,6 +85,12 @@ "openforms.authentication.contrib.digid_eherkenning_oidc.eherkenning_urls", ), ), + path( + "digid-machtigen-oidc/", + include( + "openforms.authentication.contrib.digid_eherkenning_oidc.digid_machtigen_urls", + ), + ), path("payment/", include("openforms.payments.urls", namespace="payments")), # NOTE: we dont use the User creation feature so don't enable all the mock views path("digid/", include("openforms.authentication.contrib.digid.urls")),