From fa523bb59c7acfe69a9f82900b79637a8089a924 Mon Sep 17 00:00:00 2001 From: Steven Bal Date: Fri, 18 Oct 2024 14:33:43 +0200 Subject: [PATCH 1/3] :construction: [maykinmedia/django-setup-configuration#16] Setup config fixes ensure that notificaties API can access itself, to complete the configuration --- .../configuration/opennotifs_config_cli.rst | 1 + src/nrc/config/authorization.py | 36 +++++++++++++++++++ .../commands/test_setup_configuration.py | 33 ++++++++++++++++- 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/docs/installation/configuration/opennotifs_config_cli.rst b/docs/installation/configuration/opennotifs_config_cli.rst index 5ef75a99..d164bac3 100644 --- a/docs/installation/configuration/opennotifs_config_cli.rst +++ b/docs/installation/configuration/opennotifs_config_cli.rst @@ -46,6 +46,7 @@ Open Notificaties uses Open Zaak Authorisaties API to check authorizations of its consumers, therefore Open Notificaties should be able to request Open Zaak. Make sure that the correct permissions are configured in Open Zaak Autorisaties API. +* ``OPENNOTIFICATIES_DOMAIN``: a ``[host]:[port]`` or ``[host]`` value. Required. * ``AUTHORIZATION_CONFIG_ENABLE``: enable Authorization configuration. Defaults to ``False``. * ``AUTORISATIES_API_ROOT``: full URL to the Authorisaties API root, for example diff --git a/src/nrc/config/authorization.py b/src/nrc/config/authorization.py index e15f6c58..363d5bbd 100644 --- a/src/nrc/config/authorization.py +++ b/src/nrc/config/authorization.py @@ -6,9 +6,13 @@ import requests from django_setup_configuration.configuration import BaseConfigurationStep from django_setup_configuration.exceptions import SelfTestFailed +from furl import furl +from notifications_api_common.models import NotificationsConfig from vng_api_common.authorizations.models import AuthorizationsConfig, ComponentTypes from vng_api_common.models import APICredential, JWTSecret from zds_client import ClientAuth +from zgw_consumers.constants import APITypes, AuthTypes +from zgw_consumers.models import Service from nrc.utils import build_absolute_url @@ -19,6 +23,8 @@ class AuthorizationStep(BaseConfigurationStep): 1. Set up authorization to point to the API 2. Add credentials for Open Notifications to request Open Zaak + 3. Configure Open Notificaties such that it can access itself (required because + Open Notificaties must be subscribed to changes in the `autorisaties` channel) Normal mode doesn't change the credentials after its initial creation. If the client_id or secret is changed, run this command with 'overwrite' flag @@ -26,6 +32,7 @@ class AuthorizationStep(BaseConfigurationStep): verbose_name = "Authorization Configuration" required_settings = [ + "OPENNOTIFICATIES_DOMAIN", "AUTORISATIES_API_ROOT", "NOTIF_OPENZAAK_CLIENT_ID", "NOTIF_OPENZAAK_SECRET", @@ -65,6 +72,35 @@ def configure(self) -> None: }, ) + # TODO remove hardcoded version? + # Step 3 (step 8/9 in Open Zaak configuration documentation) + api_version = settings.API_VERSION.split(".")[0] + notifs_api_root = ( + furl(settings.OPENNOTIFICATIES_DOMAIN) + / reverse("api-root", kwargs={"version": api_version}) + ).url + notifs_oas_url = ( + furl(settings.OPENNOTIFICATIES_DOMAIN) + / reverse("schema", kwargs={"version": api_version}) + ).url + scheme = "http" if settings.DEBUG else "https" + notification_service, _ = Service.objects.update_or_create( + api_root=f"{scheme}://{notifs_api_root}", + oas=f"{scheme}://{notifs_oas_url}", + defaults={ + "label": "Open Notificaties", + "api_type": APITypes.nrc, + "client_id": settings.NOTIF_OPENZAAK_CLIENT_ID, + "secret": settings.NOTIF_OPENZAAK_SECRET, + "auth_type": AuthTypes.zgw, + "user_id": settings.NOTIF_OPENZAAK_CLIENT_ID, + "user_representation": f"Open Notificaties {organization}", + }, + ) + config = NotificationsConfig.get_solo() + config.notifications_api_service = notification_service + config.save() + def test_configuration(self) -> None: """ This check depends on the configuration of permissions in Open Zaak diff --git a/src/nrc/tests/commands/test_setup_configuration.py b/src/nrc/tests/commands/test_setup_configuration.py index 4c43b9e5..9cfa9933 100644 --- a/src/nrc/tests/commands/test_setup_configuration.py +++ b/src/nrc/tests/commands/test_setup_configuration.py @@ -9,10 +9,12 @@ import requests import requests_mock from jwt import decode +from notifications_api_common.models import NotificationsConfig from rest_framework import status from vng_api_common.authorizations.models import AuthorizationsConfig from zds_client.auth import ClientAuth -from zgw_consumers.constants import APITypes +from zgw_consumers.constants import APITypes, AuthTypes +from zgw_consumers.models import Service from zgw_consumers.test import mock_service_oas_get from nrc.config.authorization import AuthorizationStep, OpenZaakAuthStep @@ -118,6 +120,35 @@ def test_setup_configuration(self, m): self.assertEqual(response.status_code, status.HTTP_200_OK) + with self.subTest("Open Notificaties can access itself"): + notifications_service = Service.objects.get() + + self.assertEqual( + notifications_service.api_root, + "https://open-notificaties.example.com/api/v1/", + ) + self.assertEqual( + notifications_service.oas, + "https://open-notificaties.example.com/api/v1/schema/openapi.yaml", + ) + self.assertEqual(notifications_service.label, "Open Notificaties") + self.assertEqual(notifications_service.api_type, APITypes.nrc) + self.assertEqual(notifications_service.client_id, "notif-client-id") + self.assertEqual(notifications_service.secret, "notif-secret") + self.assertEqual(notifications_service.auth_type, AuthTypes.zgw) + self.assertEqual(notifications_service.user_id, "notif-client-id") + self.assertEqual( + notifications_service.user_representation, "Open Notificaties ACME" + ) + + config = NotificationsConfig.get_solo() + + self.assertEqual(config.notifications_api_service, notifications_service) + # resp = self.client.get("/view-config/") + + # import pdb; pdb.set_trace() + # TODO add test for service creation and check view config? + @requests_mock.Mocker() def test_setup_configuration_selftest_fails(self, m): m.get("http://open-notificaties.example.com/", exc=requests.ConnectionError) From a3a2a5ce1212e30709bfb1afbfa13982b0936d4b Mon Sep 17 00:00:00 2001 From: Steven Bal Date: Fri, 8 Nov 2024 09:34:57 +0100 Subject: [PATCH 2/3] :pushpin: Pin commonground-api-common to branch and remove gemma-zds-client --- requirements/base.in | 2 +- requirements/base.txt | 30 ++++++++++++------------------ requirements/ci.txt | 33 +++++++-------------------------- requirements/dev.txt | 32 +++++++------------------------- 4 files changed, 27 insertions(+), 70 deletions(-) diff --git a/requirements/base.in b/requirements/base.in index 624d7683..979c05c6 100644 --- a/requirements/base.in +++ b/requirements/base.in @@ -6,4 +6,4 @@ self-certifi furl # API libraries -gemma-zds-client +git+https://github.com/maykinmedia/commonground-api-common@feature/update-notifs-client diff --git a/requirements/base.txt b/requirements/base.txt index e7a8d9b4..a7cce01e 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # # ./bin/compile_dependencies.sh @@ -7,7 +7,10 @@ amqp==5.2.0 # via kombu ape-pie==0.2.0 - # via zgw-consumers + # via + # commonground-api-common + # notifications-api-common + # zgw-consumers asgiref==3.8.1 # via # django @@ -15,8 +18,6 @@ asgiref==3.8.1 # django-cors-headers asn1crypto==1.5.1 # via webauthn -async-timeout==4.0.3 - # via redis attrs==24.2.0 # via # glom @@ -59,8 +60,10 @@ click-plugins==1.1.1 # via celery click-repl==0.3.0 # via celery -commonground-api-common==1.13.2 - # via open-api-framework +commonground-api-common @ git+https://github.com/maykinmedia/commonground-api-common@feature/update-notifs-client + # via + # -r requirements/base.in + # open-api-framework coreapi==2.3.3 # via commonground-api-common coreschema==0.0.4 @@ -201,11 +204,6 @@ furl==2.1.3 # via # -r requirements/base.in # ape-pie -gemma-zds-client==1.0.1 - # via - # -r requirements/base.in - # commonground-api-common - # notifications-api-common glom==23.5.0 # via mozilla-django-oidc-db humanize==4.10.0 @@ -242,7 +240,7 @@ mozilla-django-oidc==4.0.1 # via mozilla-django-oidc-db mozilla-django-oidc-db==0.19.0 # via open-api-framework -notifications-api-common==0.2.2 +notifications-api-common==0.3.0 # via commonground-api-common open-api-framework==0.8.1 # via -r requirements/base.in @@ -265,7 +263,6 @@ pycparser==2.22 pyjwt==2.9.0 # via # commonground-api-common - # gemma-zds-client # zgw-consumers pyopenssl==24.2.1 # via @@ -289,7 +286,6 @@ pyyaml==6.0.2 # via # drf-spectacular # drf-yasg - # gemma-zds-client # oyaml qrcode==7.4.2 # via django-two-factor-auth @@ -305,7 +301,6 @@ requests==2.32.3 # commonground-api-common # coreapi # django-log-outgoing-requests - # gemma-zds-client # mozilla-django-oidc # open-api-framework # zgw-consumers @@ -330,8 +325,6 @@ tornado==6.4.1 # via flower typing-extensions==4.12.2 # via - # asgiref - # django-solo # mozilla-django-oidc-db # qrcode # zgw-consumers @@ -362,7 +355,8 @@ webencodings==0.5.1 # via bleach wrapt==1.16.0 # via elastic-apm -zgw-consumers==0.34.0 +zgw-consumers==0.35.1 # via + # commonground-api-common # notifications-api-common # open-api-framework diff --git a/requirements/ci.txt b/requirements/ci.txt index 27cb8488..8aa6ac24 100644 --- a/requirements/ci.txt +++ b/requirements/ci.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # # ./bin/compile_dependencies.sh @@ -13,6 +13,8 @@ amqp==5.2.0 ape-pie==0.2.0 # via # -r requirements/base.txt + # commonground-api-common + # notifications-api-common # zgw-consumers asgiref==3.8.1 # via @@ -24,10 +26,6 @@ asn1crypto==1.5.1 # via # -r requirements/base.txt # webauthn -async-timeout==4.0.3 - # via - # -r requirements/base.txt - # redis attrs==24.2.0 # via # -r requirements/base.txt @@ -100,7 +98,7 @@ click-repl==0.3.0 # celery codecov==2.1.13 # via -r requirements/ci.in -commonground-api-common==1.13.2 +commonground-api-common @ git+https://github.com/maykinmedia/commonground-api-common@feature/update-notifs-client # via # -r requirements/base.txt # open-api-framework @@ -315,8 +313,6 @@ elastic-apm==6.23.0 # via # -r requirements/base.txt # open-api-framework -exceptiongroup==1.2.2 - # via pytest face==20.1.1 # via # -r requirements/base.txt @@ -337,11 +333,6 @@ furl==2.1.3 # via # -r requirements/base.txt # ape-pie -gemma-zds-client==1.0.1 - # via - # -r requirements/base.txt - # commonground-api-common - # notifications-api-common glom==23.5.0 # via # -r requirements/base.txt @@ -425,7 +416,7 @@ multidict==6.0.5 # via yarl mypy-extensions==0.4.3 # via black -notifications-api-common==0.2.2 +notifications-api-common==0.3.0 # via # -r requirements/base.txt # commonground-api-common @@ -484,7 +475,6 @@ pyjwt==2.9.0 # via # -r requirements/base.txt # commonground-api-common - # gemma-zds-client # zgw-consumers pyopenssl==24.2.1 # via @@ -524,7 +514,6 @@ pyyaml==6.0.2 # -r requirements/base.txt # drf-spectacular # drf-yasg - # gemma-zds-client # oyaml # vcrpy qrcode==7.4.2 @@ -550,7 +539,6 @@ requests==2.32.3 # commonground-api-common # coreapi # django-log-outgoing-requests - # gemma-zds-client # mozilla-django-oidc # open-api-framework # requests-mock @@ -612,11 +600,6 @@ sqlparse==0.5.1 # django tblib==1.7.0 # via -r requirements/test-tools.in -tomli==2.0.1 - # via - # black - # pytest - # sphinx tornado==6.4.1 # via # -r requirements/base.txt @@ -624,9 +607,6 @@ tornado==6.4.1 typing-extensions==4.12.2 # via # -r requirements/base.txt - # asgiref - # black - # django-solo # mozilla-django-oidc-db # qrcode # zgw-consumers @@ -683,8 +663,9 @@ wrapt==1.16.0 # vcrpy yarl==1.9.4 # via vcrpy -zgw-consumers==0.34.0 +zgw-consumers==0.35.1 # via # -r requirements/base.txt + # commonground-api-common # notifications-api-common # open-api-framework diff --git a/requirements/dev.txt b/requirements/dev.txt index 91971b6f..b35547bc 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.10 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # # ./bin/compile_dependencies.sh @@ -13,6 +13,8 @@ amqp==5.2.0 ape-pie==0.2.0 # via # -r requirements/base.txt + # commonground-api-common + # notifications-api-common # zgw-consumers asgiref==3.8.1 # via @@ -24,10 +26,6 @@ asn1crypto==1.5.1 # via # -r requirements/base.txt # webauthn -async-timeout==4.0.3 - # via - # -r requirements/base.txt - # redis attrs==24.2.0 # via # -r requirements/base.txt @@ -103,7 +101,7 @@ click-repl==0.3.0 # via # -r requirements/base.txt # celery -commonground-api-common==1.13.2 +commonground-api-common @ git+https://github.com/maykinmedia/commonground-api-common@feature/update-notifs-client # via # -r requirements/base.txt # open-api-framework @@ -342,11 +340,6 @@ furl==2.1.3 # via # -r requirements/base.txt # ape-pie -gemma-zds-client==1.0.1 - # via - # -r requirements/base.txt - # commonground-api-common - # notifications-api-common gitdb==4.0.9 # via gitpython gitpython==3.1.41 @@ -432,7 +425,7 @@ multidict==6.0.5 # via yarl mypy-extensions==0.4.3 # via black -notifications-api-common==0.2.2 +notifications-api-common==0.3.0 # via # -r requirements/base.txt # commonground-api-common @@ -491,7 +484,6 @@ pyjwt==2.9.0 # via # -r requirements/base.txt # commonground-api-common - # gemma-zds-client # zgw-consumers pyopenssl==24.2.1 # via @@ -531,7 +523,6 @@ pyyaml==6.0.2 # -r requirements/base.txt # drf-spectacular # drf-yasg - # gemma-zds-client # oyaml # vcrpy qrcode==7.4.2 @@ -556,7 +547,6 @@ requests==2.32.3 # commonground-api-common # coreapi # django-log-outgoing-requests - # gemma-zds-client # mozilla-django-oidc # open-api-framework # requests-mock @@ -618,12 +608,6 @@ sqlparse==0.5.1 # django-debug-toolbar tblib==1.7.0 # via -r requirements/test-tools.in -tomli==2.0.1 - # via - # black - # build - # pip-tools - # pyproject-hooks tornado==6.4.1 # via # -r requirements/base.txt @@ -631,9 +615,6 @@ tornado==6.4.1 typing-extensions==4.12.2 # via # -r requirements/base.txt - # asgiref - # black - # django-solo # mozilla-django-oidc-db # qrcode # zgw-consumers @@ -692,9 +673,10 @@ wrapt==1.16.0 # vcrpy yarl==1.9.4 # via vcrpy -zgw-consumers==0.34.0 +zgw-consumers==0.35.1 # via # -r requirements/base.txt + # commonground-api-common # notifications-api-common # open-api-framework From 68da993018d22bbbaf52da1de3f6a28a8b014141 Mon Sep 17 00:00:00 2001 From: Steven Bal Date: Fri, 8 Nov 2024 09:35:28 +0100 Subject: [PATCH 3/3] :alien: Remove reliance on gemma-zds-client --- src/nrc/api/admin.py | 8 +++++--- src/nrc/config/authorization.py | 12 +++++++----- .../tests/commands/test_setup_configuration.py | 8 +++++--- src/nrc/utils/auth.py | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 src/nrc/utils/auth.py diff --git a/src/nrc/api/admin.py b/src/nrc/api/admin.py index c1214fba..78aef966 100644 --- a/src/nrc/api/admin.py +++ b/src/nrc/api/admin.py @@ -3,7 +3,8 @@ from django.utils.translation import gettext_lazy as _ from vng_api_common.models import JWTSecret -from zds_client import ClientAuth + +from nrc.utils.auth import generate_jwt admin.site.unregister(JWTSecret) @@ -18,8 +19,9 @@ class JWTSecretAdmin(admin.ModelAdmin): @admin.display(description="jwt") def get_jwt(self, obj): if obj.identifier and obj.secret: - auth = ClientAuth(obj.identifier, obj.secret) - jwt = auth.credentials()["Authorization"] + jwt = generate_jwt( + obj.identifier, obj.secret, obj.identifier, obj.identifier + ) return format_html( '{val}

{hint}

', val=jwt, diff --git a/src/nrc/config/authorization.py b/src/nrc/config/authorization.py index 363d5bbd..f5c2ae88 100644 --- a/src/nrc/config/authorization.py +++ b/src/nrc/config/authorization.py @@ -10,11 +10,11 @@ from notifications_api_common.models import NotificationsConfig from vng_api_common.authorizations.models import AuthorizationsConfig, ComponentTypes from vng_api_common.models import APICredential, JWTSecret -from zds_client import ClientAuth from zgw_consumers.constants import APITypes, AuthTypes from zgw_consumers.models import Service from nrc.utils import build_absolute_url +from nrc.utils.auth import generate_jwt class AuthorizationStep(BaseConfigurationStep): @@ -151,14 +151,16 @@ def test_configuration(self): """ endpoint = reverse("kanaal-list", kwargs={"version": "1"}) full_url = build_absolute_url(endpoint, request=None) - auth = ClientAuth( - client_id=settings.OPENZAAK_NOTIF_CLIENT_ID, - secret=settings.OPENZAAK_NOTIF_SECRET, + token = generate_jwt( + settings.OPENZAAK_NOTIF_CLIENT_ID, + settings.OPENZAAK_NOTIF_SECRET, + settings.OPENZAAK_NOTIF_CLIENT_ID, + settings.OPENZAAK_NOTIF_CLIENT_ID, ) try: response = requests.get( - full_url, headers={**auth.credentials(), "Accept": "application/json"} + full_url, headers={"Authorization": token, "Accept": "application/json"} ) response.raise_for_status() except requests.RequestException as exc: diff --git a/src/nrc/tests/commands/test_setup_configuration.py b/src/nrc/tests/commands/test_setup_configuration.py index 9cfa9933..94df67d6 100644 --- a/src/nrc/tests/commands/test_setup_configuration.py +++ b/src/nrc/tests/commands/test_setup_configuration.py @@ -12,7 +12,6 @@ from notifications_api_common.models import NotificationsConfig from rest_framework import status from vng_api_common.authorizations.models import AuthorizationsConfig -from zds_client.auth import ClientAuth from zgw_consumers.constants import APITypes, AuthTypes from zgw_consumers.models import Service from zgw_consumers.test import mock_service_oas_get @@ -20,6 +19,7 @@ from nrc.config.authorization import AuthorizationStep, OpenZaakAuthStep from nrc.config.notification_retry import NotificationRetryConfigurationStep from nrc.config.site import SiteConfigurationStep +from nrc.utils.auth import generate_jwt @override_settings( @@ -111,11 +111,13 @@ def test_setup_configuration(self, m): self.assertEqual(decoded_jwt["client_id"], "notif-client-id") with self.subTest("Open Zaak can query Notification API"): - auth = ClientAuth("oz-client-id", "oz-secret") + token = generate_jwt( + "oz-client-id", "oz-secret", "oz-client-id", "oz-client-id" + ) response = self.client.get( reverse("kanaal-list", kwargs={"version": 1}), - HTTP_AUTHORIZATION=auth.credentials()["Authorization"], + HTTP_AUTHORIZATION=token, ) self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/src/nrc/utils/auth.py b/src/nrc/utils/auth.py new file mode 100644 index 00000000..e8c58ac2 --- /dev/null +++ b/src/nrc/utils/auth.py @@ -0,0 +1,18 @@ +def generate_jwt(client_id, secret, user_id, user_representation): + # TODO fix this workaround + from zgw_consumers.client import ZGWAuth + + class FakeService: + def __init__(self, **kwargs): + for key, value in kwargs.items(): + setattr(self, key, value) + + auth = ZGWAuth( + service=FakeService( # type: ignore + client_id=client_id, + secret=secret, + user_id=user_id, + user_representation=user_representation, + ) + ) + return f"Bearer {auth._token}"