From 58d2d3aabaf20a3afc9bc545c1fc79d95bfc3319 Mon Sep 17 00:00:00 2001 From: SilviaAmAm Date: Thu, 31 Mar 2022 15:46:52 +0200 Subject: [PATCH] :construction: [#1471] Splitting DigiD/DigiD machtigen config models --- src/digid_eherkenning_oidc_generics/admin.py | 59 ++++++- .../digid_machtigen_settings.py | 2 + src/digid_eherkenning_oidc_generics/forms.py | 14 +- .../migrations/0002_auto_20220331_1221.py | 33 ---- .../0002_openidconnectdigidmachtigenconfig.py | 167 ++++++++++++++++++ src/digid_eherkenning_oidc_generics/mixins.py | 32 ++-- src/digid_eherkenning_oidc_generics/models.py | 25 ++- .../digid_eherkenning_oidc/backends.py | 17 +- .../contrib/digid_eherkenning_oidc/plugin.py | 12 +- .../contrib/digid_eherkenning_oidc/views.py | 5 +- 10 files changed, 312 insertions(+), 54 deletions(-) create mode 100644 src/digid_eherkenning_oidc_generics/digid_machtigen_settings.py delete mode 100644 src/digid_eherkenning_oidc_generics/migrations/0002_auto_20220331_1221.py create mode 100644 src/digid_eherkenning_oidc_generics/migrations/0002_openidconnectdigidmachtigenconfig.py diff --git a/src/digid_eherkenning_oidc_generics/admin.py b/src/digid_eherkenning_oidc_generics/admin.py index 9c79342e22..85e5bcdb64 100644 --- a/src/digid_eherkenning_oidc_generics/admin.py +++ b/src/digid_eherkenning_oidc_generics/admin.py @@ -4,8 +4,16 @@ from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin from solo.admin import SingletonModelAdmin -from .forms import OpenIDConnectEHerkenningConfigForm, OpenIDConnectPublicConfigForm -from .models import OpenIDConnectEHerkenningConfig, OpenIDConnectPublicConfig +from .forms import ( + OpenIDConnectDigiDMachtigenConfigForm, + OpenIDConnectEHerkenningConfigForm, + OpenIDConnectPublicConfigForm, +) +from .models import ( + OpenIDConnectDigiDMachtigenConfig, + OpenIDConnectEHerkenningConfig, + OpenIDConnectPublicConfig, +) class OpenIDConnectConfigBaseAdmin(DynamicArrayMixin, SingletonModelAdmin): @@ -52,3 +60,50 @@ class OpenIDConnectConfigDigiDAdmin(OpenIDConnectConfigBaseAdmin): @admin.register(OpenIDConnectEHerkenningConfig) class OpenIDConnectConfigEHerkenningAdmin(OpenIDConnectConfigBaseAdmin): form = OpenIDConnectEHerkenningConfigForm + + +@admin.register(OpenIDConnectDigiDMachtigenConfig) +class OpenIDConnectConfigDigiDMachtigenAdmin(DynamicArrayMixin, SingletonModelAdmin): + form = OpenIDConnectDigiDMachtigenConfigForm + + fieldsets = ( + ( + _("Activation"), + {"fields": ("enabled",)}, + ), + ( + _("Common settings"), + { + "fields": ( + "oidc_rp_client_id", + "oidc_rp_client_secret", + "oidc_rp_scopes_list", + "oidc_rp_sign_algo", + "oidc_rp_idp_sign_key", + ) + }, + ), + ( + _("Attributes to extract from claim"), + { + "fields": ( + "vertegenwoordigde_claim_name", + "gemachtigde_claim_name", + ) + }, + ), + ( + _("Endpoints"), + { + "fields": ( + "oidc_op_discovery_endpoint", + "oidc_op_jwks_endpoint", + "oidc_op_authorization_endpoint", + "oidc_op_token_endpoint", + "oidc_op_user_endpoint", + "oidc_op_logout_endpoint", + ) + }, + ), + (_("Keycloak specific settings"), {"fields": ("oidc_keycloak_idp_hint",)}), + ) diff --git a/src/digid_eherkenning_oidc_generics/digid_machtigen_settings.py b/src/digid_eherkenning_oidc_generics/digid_machtigen_settings.py new file mode 100644 index 0000000000..b998079bf5 --- /dev/null +++ b/src/digid_eherkenning_oidc_generics/digid_machtigen_settings.py @@ -0,0 +1,2 @@ +DIGID_MACHTIGEN_CUSTOM_OIDC_DB_PREFIX = "digid_machtigen_oidc" +OIDC_AUTHENTICATION_CALLBACK_URL = "digid_machtigen_oidc:callback" diff --git a/src/digid_eherkenning_oidc_generics/forms.py b/src/digid_eherkenning_oidc_generics/forms.py index 972b32f18d..d9b67ac521 100644 --- a/src/digid_eherkenning_oidc_generics/forms.py +++ b/src/digid_eherkenning_oidc_generics/forms.py @@ -8,7 +8,11 @@ from openforms.forms.models import Form -from .models import OpenIDConnectEHerkenningConfig, OpenIDConnectPublicConfig +from .models import ( + OpenIDConnectDigiDMachtigenConfig, + OpenIDConnectEHerkenningConfig, + OpenIDConnectPublicConfig, +) OIDC_MAPPING = deepcopy(_OIDC_MAPPING) @@ -58,3 +62,11 @@ class OpenIDConnectEHerkenningConfigForm(OpenIDConnectBaseConfigForm): class Meta: model = OpenIDConnectEHerkenningConfig fields = "__all__" + + +class OpenIDConnectDigiDMachtigenConfigForm(OpenIDConnectBaseConfigForm): + plugin_identifier = "digid_machtigen_oidc" + + class Meta: + model = OpenIDConnectDigiDMachtigenConfig + fields = "__all__" diff --git a/src/digid_eherkenning_oidc_generics/migrations/0002_auto_20220331_1221.py b/src/digid_eherkenning_oidc_generics/migrations/0002_auto_20220331_1221.py deleted file mode 100644 index 3f1e37ce82..0000000000 --- a/src/digid_eherkenning_oidc_generics/migrations/0002_auto_20220331_1221.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 3.2.12 on 2022-03-31 10:21 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("digid_eherkenning_oidc_generics", "0001_initial"), - ] - - operations = [ - migrations.AddField( - model_name="openidconnectpublicconfig", - name="gemachtigde_claim_name", - field=models.CharField( - default="gemachtigde.bsn", - help_text="Name of the claim in which the BSN of the person representing someone else is stored", - max_length=50, - verbose_name="gemachtigde claim name", - ), - ), - migrations.AddField( - model_name="openidconnectpublicconfig", - name="vertegenwoordigde_claim_name", - field=models.CharField( - default="aanvrager.bsn", - help_text="Name of the claim in which the BSN of the person being represented is stored", - max_length=50, - verbose_name="vertegenwoordigde claim name", - ), - ), - ] diff --git a/src/digid_eherkenning_oidc_generics/migrations/0002_openidconnectdigidmachtigenconfig.py b/src/digid_eherkenning_oidc_generics/migrations/0002_openidconnectdigidmachtigenconfig.py new file mode 100644 index 0000000000..76916dc3fd --- /dev/null +++ b/src/digid_eherkenning_oidc_generics/migrations/0002_openidconnectdigidmachtigenconfig.py @@ -0,0 +1,167 @@ +# Generated by Django 3.2.12 on 2022-03-31 12:45 + +import digid_eherkenning_oidc_generics.models +from django.db import migrations, models +import django_better_admin_arrayfield.models.fields +import mozilla_django_oidc_db.models + + +class Migration(migrations.Migration): + + dependencies = [ + ("digid_eherkenning_oidc_generics", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="OpenIDConnectDigiDMachtigenConfig", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "enabled", + models.BooleanField( + default=False, + help_text="Indicates whether OpenID Connect for authentication/authorization is enabled", + verbose_name="enable", + ), + ), + ( + "oidc_rp_client_id", + models.CharField( + help_text="OpenID Connect client ID provided by the OIDC Provider", + max_length=1000, + verbose_name="OpenID Connect client ID", + ), + ), + ( + "oidc_rp_client_secret", + models.CharField( + help_text="OpenID Connect secret provided by the OIDC Provider", + max_length=1000, + verbose_name="OpenID Connect secret", + ), + ), + ( + "oidc_rp_sign_algo", + models.CharField( + default="HS256", + help_text="Algorithm the Identity Provider uses to sign ID tokens", + max_length=50, + verbose_name="OpenID sign algorithm", + ), + ), + ( + "oidc_op_discovery_endpoint", + models.URLField( + blank=True, + help_text="URL of your OpenID Connect provider discovery endpoint ending with a slash (`.well-known/...` will be added automatically). If this is provided, the remaining endpoints can be omitted, as they will be derived from this endpoint.", + max_length=1000, + verbose_name="Discovery endpoint", + ), + ), + ( + "oidc_op_jwks_endpoint", + models.URLField( + blank=True, + help_text="URL of your OpenID Connect provider JSON Web Key Set endpoint. Required if `RS256` is used as signing algorithm", + max_length=1000, + verbose_name="JSON Web Key Set endpoint", + ), + ), + ( + "oidc_op_authorization_endpoint", + models.URLField( + help_text="URL of your OpenID Connect provider authorization endpoint", + max_length=1000, + verbose_name="Authorization endpoint", + ), + ), + ( + "oidc_op_token_endpoint", + models.URLField( + help_text="URL of your OpenID Connect provider token endpoint", + max_length=1000, + verbose_name="Token endpoint", + ), + ), + ( + "oidc_op_user_endpoint", + models.URLField( + help_text="URL of your OpenID Connect provider userinfo endpoint", + max_length=1000, + verbose_name="User endpoint", + ), + ), + ( + "oidc_rp_idp_sign_key", + models.CharField( + blank=True, + help_text="Key the Identity Provider uses to sign ID tokens in the case of an RSA sign algorithm. Should be the signing key in PEM or DER format", + max_length=1000, + verbose_name="Sign key", + ), + ), + ( + "oidc_op_logout_endpoint", + models.URLField( + blank=True, + help_text="URL of your OpenID Connect provider logout endpoint", + max_length=1000, + verbose_name="Logout endpoint", + ), + ), + ( + "oidc_keycloak_idp_hint", + models.CharField( + blank=True, + help_text="Specific for Keycloak: parameter that indicates which identity provider should be used (therefore skipping the Keycloak login screen).", + max_length=1000, + verbose_name="Keycloak Identity Provider hint", + ), + ), + ( + "vertegenwoordigde_claim_name", + models.CharField( + default="aanvrager.bsn", + help_text="Name of the claim in which the BSN of the person being represented is stored", + max_length=50, + verbose_name="vertegenwoordigde claim name", + ), + ), + ( + "gemachtigde_claim_name", + models.CharField( + default="gemachtigde.bsn", + help_text="Name of the claim in which the BSN of the person representing someone else is stored", + max_length=50, + verbose_name="gemachtigde claim name", + ), + ), + ( + "oidc_rp_scopes_list", + django_better_admin_arrayfield.models.fields.ArrayField( + base_field=models.CharField( + max_length=50, verbose_name="OpenID Connect scope" + ), + blank=True, + default=digid_eherkenning_oidc_generics.models.get_default_scopes_bsn, + help_text="OpenID Connect scopes that are requested during login. These scopes are hardcoded and must be supported by the identity provider", + size=None, + verbose_name="OpenID Connect scopes", + ), + ), + ], + options={ + "verbose_name": "OpenID Connect configuration for DigiD Machtigen", + }, + bases=(mozilla_django_oidc_db.models.CachingMixin, models.Model), + ), + ] diff --git a/src/digid_eherkenning_oidc_generics/mixins.py b/src/digid_eherkenning_oidc_generics/mixins.py index f395c13e3f..9cb0de09af 100644 --- a/src/digid_eherkenning_oidc_generics/mixins.py +++ b/src/digid_eherkenning_oidc_generics/mixins.py @@ -1,24 +1,36 @@ -from mozilla_django_oidc_db.mixins import SoloConfigMixin +from mozilla_django_oidc_db.mixins import SoloConfigMixin as _SoloConfigMixin +import digid_eherkenning_oidc_generics.digid_machtigen_settings as digid_machtigen_settings import digid_eherkenning_oidc_generics.digid_settings as digid_settings import digid_eherkenning_oidc_generics.eherkenning_settings as eherkenning_settings -from .models import OpenIDConnectEHerkenningConfig, OpenIDConnectPublicConfig +from .models import ( + OpenIDConnectDigiDMachtigenConfig, + OpenIDConnectEHerkenningConfig, + OpenIDConnectPublicConfig, +) -class SoloConfigDigiDMixin(SoloConfigMixin): - config_class = OpenIDConnectPublicConfig +class SoloConfigMixin(_SoloConfigMixin): + config_class = "" + settings_attribute = None def get_settings(self, attr, *args): - if hasattr(digid_settings, attr): - return getattr(digid_settings, attr) + if hasattr(self.settings_attribute, attr): + return getattr(self.settings_attribute, attr) return super().get_settings(attr, *args) +class SoloConfigDigiDMixin(SoloConfigMixin): + config_class = OpenIDConnectPublicConfig + settings_attribute = digid_settings + + class SoloConfigEHerkenningMixin(SoloConfigMixin): config_class = OpenIDConnectEHerkenningConfig + settings_attribute = eherkenning_settings - def get_settings(self, attr, *args): - if hasattr(eherkenning_settings, attr): - return getattr(eherkenning_settings, attr) - return super().get_settings(attr, *args) + +class SoloConfigDigiDMachtigenMixin(SoloConfigMixin): + config_class = OpenIDConnectDigiDMachtigenConfig + settings_attribute = digid_machtigen_settings diff --git a/src/digid_eherkenning_oidc_generics/models.py b/src/digid_eherkenning_oidc_generics/models.py index b1587717ca..eeadf25054 100644 --- a/src/digid_eherkenning_oidc_generics/models.py +++ b/src/digid_eherkenning_oidc_generics/models.py @@ -7,6 +7,7 @@ from openforms.authentication.constants import AuthAttribute +from .digid_machtigen_settings import DIGID_MACHTIGEN_CUSTOM_OIDC_DB_PREFIX from .digid_settings import DIGID_CUSTOM_OIDC_DB_PREFIX from .eherkenning_settings import EHERKENNING_CUSTOM_OIDC_DB_PREFIX @@ -74,6 +75,16 @@ class OpenIDConnectPublicConfig(OpenIDConnectBaseConfig): "These scopes are hardcoded and must be supported by the identity provider" ), ) + + @classproperty + def custom_oidc_db_prefix(cls): + return DIGID_CUSTOM_OIDC_DB_PREFIX + + class Meta: + verbose_name = _("OpenID Connect configuration for DigiD") + + +class OpenIDConnectDigiDMachtigenConfig(OpenIDConnectBaseConfig): vertegenwoordigde_claim_name = models.CharField( verbose_name=_("vertegenwoordigde claim name"), default="aanvrager.bsn", @@ -90,13 +101,23 @@ class OpenIDConnectPublicConfig(OpenIDConnectBaseConfig): "Name of the claim in which the BSN of the person representing someone else is stored" ), ) + oidc_rp_scopes_list = ArrayField( + verbose_name=_("OpenID Connect scopes"), + base_field=models.CharField(_("OpenID Connect scope"), max_length=50), + default=get_default_scopes_bsn, + blank=True, + help_text=_( + "OpenID Connect scopes that are requested during login. " + "These scopes are hardcoded and must be supported by the identity provider" + ), + ) @classproperty def custom_oidc_db_prefix(cls): - return DIGID_CUSTOM_OIDC_DB_PREFIX + return DIGID_MACHTIGEN_CUSTOM_OIDC_DB_PREFIX class Meta: - verbose_name = _("OpenID Connect configuration for DigiD") + verbose_name = _("OpenID Connect configuration for DigiD Machtigen") class OpenIDConnectEHerkenningConfig(OpenIDConnectBaseConfig): diff --git a/src/openforms/authentication/contrib/digid_eherkenning_oidc/backends.py b/src/openforms/authentication/contrib/digid_eherkenning_oidc/backends.py index 6bf48452ee..e49d89d5c3 100644 --- a/src/openforms/authentication/contrib/digid_eherkenning_oidc/backends.py +++ b/src/openforms/authentication/contrib/digid_eherkenning_oidc/backends.py @@ -2,9 +2,12 @@ from copy import deepcopy from glom import PathAccessError, glom +from django.contrib.auth.models import AnonymousUser +from django.core.exceptions import SuspiciousOperation from digid_eherkenning_oidc_generics.backends import OIDCAuthenticationBackend from digid_eherkenning_oidc_generics.mixins import ( + SoloConfigDigiDMachtigenMixin, SoloConfigDigiDMixin, SoloConfigEHerkenningMixin, ) @@ -38,10 +41,22 @@ class OIDCAuthenticationEHerkenningBackend( class OIDCAuthenticationDigiDMachtigenBackend( - SoloConfigDigiDMixin, OIDCAuthenticationBackend + SoloConfigDigiDMachtigenMixin, OIDCAuthenticationBackend ): session_key = DIGID_MACHTIGEN_OIDC_AUTH_SESSION_KEY + def get_or_create_user(self, access_token, id_token, payload): + claims_verified = self.verify_claims(payload) + if not claims_verified: + msg = "Claims verification failed" + raise SuspiciousOperation(msg) + + self.extract_claims(payload) + + user = AnonymousUser() + user.is_active = True + return user + def extract_claims(self, payload: dict) -> None: claim_names = [ self.config.vertegenwoordigde_claim_name, diff --git a/src/openforms/authentication/contrib/digid_eherkenning_oidc/plugin.py b/src/openforms/authentication/contrib/digid_eherkenning_oidc/plugin.py index ca296a7a58..1f45fc5c50 100644 --- a/src/openforms/authentication/contrib/digid_eherkenning_oidc/plugin.py +++ b/src/openforms/authentication/contrib/digid_eherkenning_oidc/plugin.py @@ -9,7 +9,7 @@ from digid_eherkenning_oidc_generics.models import ( OpenIDConnectEHerkenningConfig, - OpenIDConnectPublicConfig, + OpenIDConnectPublicConfig, OpenIDConnectDigiDMachtigenConfig, ) from openforms.contrib.digid_eherkenning.utils import ( get_digid_logo, @@ -144,15 +144,21 @@ class DigiDMachtigenOIDCAuthentication(OIDCAuthentication): provides_auth = AuthAttribute.bsn init_url = "digid_machtigen_oidc:init" session_key = DIGID_MACHTIGEN_OIDC_AUTH_SESSION_KEY - config_class = OpenIDConnectPublicConfig + config_class = OpenIDConnectDigiDMachtigenConfig 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() + config = OpenIDConnectDigiDMachtigenConfig.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], } + + def get_label(self) -> str: + return "DigiD Machtigen" + + def get_logo(self, request) -> Optional[LoginLogo]: + return LoginLogo(title=self.get_label(), **get_digid_logo(request)) diff --git a/src/openforms/authentication/contrib/digid_eherkenning_oidc/views.py b/src/openforms/authentication/contrib/digid_eherkenning_oidc/views.py index 39786926a2..f4068f25a5 100644 --- a/src/openforms/authentication/contrib/digid_eherkenning_oidc/views.py +++ b/src/openforms/authentication/contrib/digid_eherkenning_oidc/views.py @@ -8,6 +8,7 @@ from mozilla_django_oidc.views import get_next_url from digid_eherkenning_oidc_generics.mixins import ( + SoloConfigDigiDMachtigenMixin, SoloConfigDigiDMixin, SoloConfigEHerkenningMixin, ) @@ -96,13 +97,13 @@ class eHerkenningOIDCAuthenticationCallbackView( class DigiDMachtigenOIDCAuthenticationRequestView( - SoloConfigDigiDMixin, OIDCAuthenticationRequestView + SoloConfigDigiDMachtigenMixin, OIDCAuthenticationRequestView ): plugin_identifier = "digid_machtigen_oidc" class DigiDMachtigenOIDCAuthenticationCallbackView( - SoloConfigDigiDMixin, OIDCAuthenticationCallbackView + SoloConfigDigiDMachtigenMixin, OIDCAuthenticationCallbackView ): plugin_identifier = "digid_machtigen_oidc" auth_backend_class = OIDCAuthenticationDigiDMachtigenBackend