From e377a3938c1aa45f51f42af776604d4234f08336 Mon Sep 17 00:00:00 2001 From: Nino Date: Tue, 3 Dec 2024 11:27:57 +0100 Subject: [PATCH 1/8] first version entra --- app/apps/cases/views/case.py | 3 +-- app/apps/users/auth.py | 2 +- app/apps/users/permissions.py | 32 +++++++++++++++++++++++++++++- app/apps/users/tests/tests_auth.py | 2 +- app/apps/users/views.py | 2 +- app/apps/workflow/views.py | 3 +-- app/config/settings.py | 30 ++++++++++------------------ app/requirements.txt | 1 - 8 files changed, 47 insertions(+), 28 deletions(-) diff --git a/app/apps/cases/views/case.py b/app/apps/cases/views/case.py index 4deb18768..397c2bc88 100644 --- a/app/apps/cases/views/case.py +++ b/app/apps/cases/views/case.py @@ -42,7 +42,7 @@ ) from apps.schedules.models import DaySegment, Priority, Schedule, WeekSegment from apps.users.auth_apps import TopKeyAuth -from apps.users.permissions import CanAccessSensitiveCases +from apps.users.permissions import CanAccessSensitiveCases, IsInAuthorizedRealm from apps.workflow.models import CaseUserTask, CaseWorkflow, WorkflowOption from apps.workflow.serializers import ( CaseWorkflowSerializer, @@ -56,7 +56,6 @@ from django_filters import rest_framework as filters from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, extend_schema -from keycloak_oidc.drf.permissions import IsInAuthorizedRealm from rest_framework import mixins, serializers, status, viewsets from rest_framework.decorators import action, parser_classes from rest_framework.pagination import LimitOffsetPagination diff --git a/app/apps/users/auth.py b/app/apps/users/auth.py index ee7632d73..e0f006dbd 100644 --- a/app/apps/users/auth.py +++ b/app/apps/users/auth.py @@ -2,7 +2,7 @@ from django.conf import settings from drf_spectacular.contrib.rest_framework_simplejwt import SimpleJWTScheme -from keycloak_oidc.auth import OIDCAuthenticationBackend +from mozilla_django_oidc.auth import OIDCAuthenticationBackend from mozilla_django_oidc.contrib.drf import OIDCAuthentication from rest_framework_simplejwt.authentication import JWTAuthentication diff --git a/app/apps/users/permissions.py b/app/apps/users/permissions.py index 02790636f..c84ac54b3 100644 --- a/app/apps/users/permissions.py +++ b/app/apps/users/permissions.py @@ -1,8 +1,38 @@ from apps.cases.models import Case from apps.users.auth_apps import TonKeyAuth, TopKeyAuth -from keycloak_oidc.drf.permissions import IsInAuthorizedRealm +from django.conf import settings from rest_framework.permissions import BasePermission, IsAuthenticated + +class InAuthGroup(BasePermission): + allowed_group_names = None + + def __init__(self): + if self.allowed_group_names is None: + raise Exception( + "Allowed group names must be set when using the AuthGroupPermission class" + ) + + super().__init__() + + def has_permission(self, request, view): + return bool( + request.user + and request.user.is_authenticated + and request.user.groups.filter(name__in=self.allowed_group_names).exists() + ) + + +class IsInAuthorizedRealm(InAuthGroup): + """ + A permission to allow access if and only if a user is logged in, + and is a member of one of the OIDC_AUTHORIZED_GROUPS groups in Keycloak + """ + + assert settings.OIDC_AUTHORIZED_GROUPS, "OIDC_AUTHORIZED_GROUPS must be set" + allowed_group_names = settings.OIDC_AUTHORIZED_GROUPS + + custom_permissions = [ # Permissions for cases/tasks ("create_case", "Create a new Case"), diff --git a/app/apps/users/tests/tests_auth.py b/app/apps/users/tests/tests_auth.py index db503e6e4..f0a1a5856 100644 --- a/app/apps/users/tests/tests_auth.py +++ b/app/apps/users/tests/tests_auth.py @@ -10,7 +10,7 @@ from django.core.exceptions import SuspiciousOperation from django.test import TestCase -from keycloak_oidc.auth import OIDCAuthenticationBackend +from mozilla_django_oidc.contrib.drf import OIDCAuthenticationBackend from app.utils.unittest_helpers import get_test_user diff --git a/app/apps/users/views.py b/app/apps/users/views.py index a81c918d7..542991dba 100644 --- a/app/apps/users/views.py +++ b/app/apps/users/views.py @@ -1,9 +1,9 @@ import logging +from apps.users.permissions import IsInAuthorizedRealm from django.contrib.auth.models import Permission from django.http import HttpResponseBadRequest from drf_spectacular.utils import extend_schema -from keycloak_oidc.drf.permissions import IsInAuthorizedRealm from rest_framework import generics, serializers, status from rest_framework.decorators import action from rest_framework.response import Response diff --git a/app/apps/workflow/views.py b/app/apps/workflow/views.py index 901e70ac8..8c3c51015 100644 --- a/app/apps/workflow/views.py +++ b/app/apps/workflow/views.py @@ -12,7 +12,7 @@ from apps.main.pagination import EmptyPagination from apps.summons.serializers import SummonTypeSerializer from apps.users.auth_apps import TopKeyAuth -from apps.users.permissions import CanAccessSensitiveCases +from apps.users.permissions import CanAccessSensitiveCases, IsInAuthorizedRealm from apps.workflow.serializers import ( CaseUserTaskSerializer, CaseUserTaskTaskNameSerializer, @@ -24,7 +24,6 @@ from django_filters import rest_framework as filters from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import OpenApiParameter, extend_schema -from keycloak_oidc.drf.permissions import IsInAuthorizedRealm from rest_framework import mixins, serializers, status, viewsets from rest_framework.decorators import action from rest_framework.pagination import LimitOffsetPagination diff --git a/app/config/settings.py b/app/config/settings.py index 6fbf16846..de9f69880 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -5,7 +5,6 @@ from celery.schedules import crontab from dotenv import load_dotenv -from keycloak_oidc.default_settings import * # noqa from opencensus.ext.azure.trace_exporter import AzureExporter from .azure_settings import Azure @@ -14,7 +13,6 @@ load_dotenv() -# config_integration.trace_integrations(["requests", "logging"]) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY") @@ -48,7 +46,6 @@ "django.contrib.postgres", "corsheaders", # Third party apps - "keycloak_oidc", "rest_framework", "rest_framework.authtoken", "drf_spectacular", @@ -162,9 +159,7 @@ "rest_framework.renderers.JSONRenderer", "rest_framework.renderers.BrowsableAPIRenderer", ), - "DEFAULT_PERMISSION_CLASSES": ( - "keycloak_oidc.drf.permissions.IsInAuthorizedRealm", - ), + "DEFAULT_PERMISSION_CLASSES": ("apps.users.permissions.IsInAuthorizedRealm",), "DEFAULT_AUTHENTICATION_CLASSES": ( "apps.users.auth.AuthenticationClass", "rest_framework.authentication.TokenAuthentication", @@ -183,7 +178,7 @@ TAG_NAME = os.getenv("TAG_NAME", "default-release") -LOGGING_LEVEL = os.getenv("LOGGING_LEVEL", "DEBUG") +LOGGING_LEVEL = "INFO" LOGGING = { "version": 1, @@ -219,7 +214,7 @@ "level": LOGGING_LEVEL, "propagate": True, }, - "mozilla_django_oidc": {"handlers": ["console"], "level": "INFO"}, + "mozilla_django_oidc": {"handlers": ["console"], "level": "DEBUG"}, }, } @@ -274,7 +269,7 @@ def filter_traces(envelope): OIDC_AUTHORIZED_GROUPS OIDC_OP_USER_ENDPOINT """ -OIDC_RP_CLIENT_ID = os.environ.get("OIDC_RP_CLIENT_ID", None) +# OIDC_RP_CLIENT_ID = os.environ.get("OIDC_RP_CLIENT_ID", None) OIDC_RP_CLIENT_SECRET = os.environ.get("OIDC_RP_CLIENT_SECRET", None) OIDC_USE_NONCE = False OIDC_AUTHORIZED_GROUPS = ( @@ -283,26 +278,23 @@ def filter_traces(envelope): "enable_persistent_token", ) OIDC_AUTHENTICATION_CALLBACK_URL = "oidc-authenticate" - +OIDC_RP_CLIENT_ID = os.environ.get( + "OIDC_RP_CLIENT_ID", "14c4257b-bcd1-4850-889e-7156c9efe2ec" +) OIDC_OP_AUTHORIZATION_ENDPOINT = os.getenv( "OIDC_OP_AUTHORIZATION_ENDPOINT", - "https://acc.iam.amsterdam.nl/auth/realms/datapunt-ad-acc/protocol/openid-connect/auth", + "https://login.microsoftonline.com/72fca1b1-2c2e-4376-a445-294d80196804/oauth2/v2.0/authorize", ) OIDC_OP_TOKEN_ENDPOINT = os.getenv( "OIDC_OP_TOKEN_ENDPOINT", - "https://acc.iam.amsterdam.nl/auth/realms/datapunt-ad-acc/protocol/openid-connect/token", + "https://login.microsoftonline.com/72fca1b1-2c2e-4376-a445-294d80196804/oauth2/v2.0/token", ) OIDC_OP_USER_ENDPOINT = os.getenv( - "OIDC_OP_USER_ENDPOINT", - "https://acc.iam.amsterdam.nl/auth/realms/datapunt-ad-acc/protocol/openid-connect/userinfo", + "OIDC_OP_USER_ENDPOINT", "https://graph.microsoft.com/oidc/userinfo" ) OIDC_OP_JWKS_ENDPOINT = os.getenv( "OIDC_OP_JWKS_ENDPOINT", - "https://acc.iam.amsterdam.nl/auth/realms/datapunt-ad-acc/protocol/openid-connect/certs", -) -OIDC_OP_LOGOUT_ENDPOINT = os.getenv( - "OIDC_OP_LOGOUT_ENDPOINT", - "https://acc.iam.amsterdam.nl/auth/realms/datapunt-ad-acc/protocol/openid-connect/logout", + "https://login.microsoftonline.com/72fca1b1-2c2e-4376-a445-294d80196804/discovery/v2.0/keys", ) LOCAL_DEVELOPMENT_AUTHENTICATION = ( diff --git a/app/requirements.txt b/app/requirements.txt index eba0c7cbf..e1982645c 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -14,7 +14,6 @@ click-didyoumean==0.3.1 click-plugins==1.1.1 click-repl==0.2.0 cryptography==43.0.3 -datapunt-keycloak-oidc @ git+https://github.com/remyvdwereld/keycloak_oidc_top.git@main debugpy==1.4.1 Django==4.2.16 django-axes==6.5.0 From 7c4ff61b82674697134b63bac76f49484840e1bd Mon Sep 17 00:00:00 2001 From: Nino Date: Tue, 3 Dec 2024 11:30:08 +0100 Subject: [PATCH 2/8] cleanup --- app/config/settings.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/config/settings.py b/app/config/settings.py index de9f69880..97f6e79eb 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -178,7 +178,7 @@ TAG_NAME = os.getenv("TAG_NAME", "default-release") -LOGGING_LEVEL = "INFO" +LOGGING_LEVEL = os.getenv("LOGGING_LEVEL", "DEBUG") LOGGING = { "version": 1, @@ -214,7 +214,7 @@ "level": LOGGING_LEVEL, "propagate": True, }, - "mozilla_django_oidc": {"handlers": ["console"], "level": "DEBUG"}, + "mozilla_django_oidc": {"handlers": ["console"], "level": LOGGING_LEVEL}, }, } @@ -269,7 +269,6 @@ def filter_traces(envelope): OIDC_AUTHORIZED_GROUPS OIDC_OP_USER_ENDPOINT """ -# OIDC_RP_CLIENT_ID = os.environ.get("OIDC_RP_CLIENT_ID", None) OIDC_RP_CLIENT_SECRET = os.environ.get("OIDC_RP_CLIENT_SECRET", None) OIDC_USE_NONCE = False OIDC_AUTHORIZED_GROUPS = ( From ddbc8892c8ebb92aba4175fbb4e5574113e6cd7d Mon Sep 17 00:00:00 2001 From: Nino Date: Wed, 4 Dec 2024 16:56:05 +0100 Subject: [PATCH 3/8] use id token --- app/apps/users/auth.py | 53 ++++++++++++++++++++++++++++++++++++++++++ app/config/settings.py | 5 ++++ 2 files changed, 58 insertions(+) diff --git a/app/apps/users/auth.py b/app/apps/users/auth.py index e0f006dbd..28cc1eecb 100644 --- a/app/apps/users/auth.py +++ b/app/apps/users/auth.py @@ -1,4 +1,5 @@ import logging +import time from django.conf import settings from drf_spectacular.contrib.rest_framework_simplejwt import SimpleJWTScheme @@ -8,6 +9,58 @@ from .auth_dev import DevelopmentAuthenticationBackend + +class OIDCAuthenticationBackend(OIDCAuthenticationBackend): + def validate_issuer(self, payload): + issuer = self.get_settings("OIDC_OP_ISSUER") + if not issuer == payload["iss"]: + raise Exception( + '"iss": %r does not match configured value for OIDC_OP_ISSUER: %r' + % (payload["iss"], issuer) + ) + + def validate_audience(self, payload): + client_id = self.get_settings("OIDC_RP_CLIENT_ID") + trusted_audiences = self.get_settings("OIDC_TRUSTED_AUDIENCES", []) + trusted_audiences = set(trusted_audiences) + trusted_audiences.add(client_id) + + audience = payload["aud"] + if not isinstance(audience, list): + audience = [audience] + audience = set(audience) + distrusted_audiences = audience.difference(trusted_audiences) + if distrusted_audiences: + raise Exception( + '"aud" contains distrusted audiences: %r' % distrusted_audiences + ) + + def validate_expiry(self, payload): + expire_time = payload["exp"] + now = time.time() + if now > expire_time: + raise Exception("Id-token is expired %r > %r" % (now, expire_time)) + + def validate_id_token(self, payload): + """Validate the content of the id token as required by OpenID Connect 1.0 + + This aims to fulfill point 2. 3. and 9. under section 3.1.3.7. ID Token + Validation + """ + self.validate_issuer(payload) + self.validate_audience(payload) + self.validate_expiry(payload) + return payload + + def get_userinfo(self, access_token, id_token=None, payload=None): + """ + Get user info from the OIDC provider. + """ + userinfo = self.verify_token(access_token) + self.validate_id_token(userinfo) + return userinfo + + LOGGER = logging.getLogger(__name__) if settings.LOCAL_DEVELOPMENT_AUTHENTICATION: diff --git a/app/config/settings.py b/app/config/settings.py index 97f6e79eb..a2212a1f0 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -295,6 +295,11 @@ def filter_traces(envelope): "OIDC_OP_JWKS_ENDPOINT", "https://login.microsoftonline.com/72fca1b1-2c2e-4376-a445-294d80196804/discovery/v2.0/keys", ) +OIDC_RP_SIGN_ALGO = "RS256" +OIDC_OP_ISSUER = os.getenv( + "OIDC_OP_ISSUER", + "https://login.microsoftonline.com/72fca1b1-2c2e-4376-a445-294d80196804/v2.0", +) LOCAL_DEVELOPMENT_AUTHENTICATION = ( os.getenv("LOCAL_DEVELOPMENT_AUTHENTICATION", False) == "True" From cdff44458dbba2e1ae7eae9cb43dfe353a249d84 Mon Sep 17 00:00:00 2001 From: Nino Date: Mon, 16 Dec 2024 12:31:07 +0100 Subject: [PATCH 4/8] changes --- app/apps/addresses/views.py | 3 ++- app/apps/users/auth.py | 18 +++++++++++------- app/config/settings.py | 7 +++++-- app/utils/api_queries_brp.py | 8 ++++---- app/utils/exceptions.py | 6 ++++++ 5 files changed, 28 insertions(+), 14 deletions(-) diff --git a/app/apps/addresses/views.py b/app/apps/addresses/views.py index 072fb287b..5a68d8114 100644 --- a/app/apps/addresses/views.py +++ b/app/apps/addresses/views.py @@ -81,8 +81,9 @@ def residents_by_bag_id(self, request, bag_id): # nummeraanduiding_id should have been retrieved, so get BRP data if address.nummeraanduiding_id: try: + brp_access_token = request.GET.get("brp_access_token", None) brp_data, status_code = get_brp_by_nummeraanduiding_id( - request, address.nummeraanduiding_id + request, address.nummeraanduiding_id, brp_access_token ) serialized_residents = ResidentsSerializer(data=brp_data) serialized_residents.is_valid(raise_exception=True) diff --git a/app/apps/users/auth.py b/app/apps/users/auth.py index 28cc1eecb..d49a9c545 100644 --- a/app/apps/users/auth.py +++ b/app/apps/users/auth.py @@ -10,28 +10,30 @@ from .auth_dev import DevelopmentAuthenticationBackend +class InvalidTokenError(Exception): + pass + + class OIDCAuthenticationBackend(OIDCAuthenticationBackend): def validate_issuer(self, payload): issuer = self.get_settings("OIDC_OP_ISSUER") if not issuer == payload["iss"]: - raise Exception( + raise InvalidTokenError( '"iss": %r does not match configured value for OIDC_OP_ISSUER: %r' % (payload["iss"], issuer) ) def validate_audience(self, payload): - client_id = self.get_settings("OIDC_RP_CLIENT_ID") + # client_id = self.get_settings("OIDC_RP_CLIENT_ID") trusted_audiences = self.get_settings("OIDC_TRUSTED_AUDIENCES", []) trusted_audiences = set(trusted_audiences) - trusted_audiences.add(client_id) + # trusted_audiences.add(client_id) audience = payload["aud"] - if not isinstance(audience, list): - audience = [audience] audience = set(audience) distrusted_audiences = audience.difference(trusted_audiences) if distrusted_audiences: - raise Exception( + raise InvalidTokenError( '"aud" contains distrusted audiences: %r' % distrusted_audiences ) @@ -39,7 +41,9 @@ def validate_expiry(self, payload): expire_time = payload["exp"] now = time.time() if now > expire_time: - raise Exception("Id-token is expired %r > %r" % (now, expire_time)) + raise InvalidTokenError( + "Access-token is expired %r > %r" % (now, expire_time) + ) def validate_id_token(self, payload): """Validate the content of the id token as required by OpenID Connect 1.0 diff --git a/app/config/settings.py b/app/config/settings.py index a2212a1f0..c3b961604 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -298,12 +298,15 @@ def filter_traces(envelope): OIDC_RP_SIGN_ALGO = "RS256" OIDC_OP_ISSUER = os.getenv( "OIDC_OP_ISSUER", - "https://login.microsoftonline.com/72fca1b1-2c2e-4376-a445-294d80196804/v2.0", + "https://sts.windows.net/72fca1b1-2c2e-4376-a445-294d80196804/", ) +OIDC_TRUSTED_AUDIENCES = f"api://{OIDC_RP_CLIENT_ID}" + LOCAL_DEVELOPMENT_AUTHENTICATION = ( os.getenv("LOCAL_DEVELOPMENT_AUTHENTICATION", False) == "True" ) + DATA_UPLOAD_MAX_MEMORY_SIZE = 5242880 DATA_UPLOAD_MAX_NUMBER_FIELDS = 6000 @@ -357,7 +360,7 @@ def filter_traces(envelope): BRP_API_URL = "/".join( [ - os.getenv("BRP_API_URL", "https://acc.bp.data.amsterdam.nl/brp"), + os.getenv("BRP_API_URL", "https://acc.bp.data.amsterdam.nl/entra/brp"), "ingeschrevenpersonen", ] ) diff --git a/app/utils/api_queries_brp.py b/app/utils/api_queries_brp.py index 93932cd09..e19879d2d 100644 --- a/app/utils/api_queries_brp.py +++ b/app/utils/api_queries_brp.py @@ -8,7 +8,7 @@ logger = logging.getLogger(__name__) -def get_brp_by_nummeraanduiding_id(request, nummeraanduiding_id): +def get_brp_by_nummeraanduiding_id(request, nummeraanduiding_id, brp_access_token): """Returns BRP data by bag_""" queryParams = { @@ -16,7 +16,7 @@ def get_brp_by_nummeraanduiding_id(request, nummeraanduiding_id): "inclusiefoverledenpersonen": "true", "expand": "partners,ouders,kinderen", } - return get_brp(request, queryParams) + return get_brp(request, queryParams, brp_access_token) def get_brp_by_address(request, postal_code, number, suffix, suffix_letter): @@ -44,7 +44,7 @@ def get_brp_by_address(request, postal_code, number, suffix, suffix_letter): @retry(stop=stop_after_attempt(3), after=after_log(logger, logging.ERROR)) -def get_brp(request, queryParams): +def get_brp(request, queryParams, brp_access_token): """Returns BRP data""" url = f"{settings.BRP_API_URL}" @@ -54,7 +54,7 @@ def get_brp(request, queryParams): params=queryParams, timeout=30, headers={ - "Authorization": request.headers.get("Authorization"), + "Authorization": f"Bearer {brp_access_token}", }, ) if response.status_code == 403: diff --git a/app/utils/exceptions.py b/app/utils/exceptions.py index 3e4450d5e..8889d5cb8 100644 --- a/app/utils/exceptions.py +++ b/app/utils/exceptions.py @@ -1,3 +1,4 @@ +from apps.users.auth import InvalidTokenError from redis.exceptions import TimeoutError as RedisTimeoutError from rest_framework import status from rest_framework.response import Response @@ -40,6 +41,11 @@ def custom_exception_handler(exc, context): status=status.HTTP_403_FORBIDDEN, ) + if isinstance(exc, InvalidTokenError): + return Response( + {"message": "Unauthorized"}, + status=status.HTTP_403_FORBIDDEN, + ) if isinstance(exc, DistrictNotFoundError): return Response( {"message": "Het stadsdeel voor dit adres is niet gevonden"}, From 1a014a3ce107be5fc04d8800a9d92c6893c0f615 Mon Sep 17 00:00:00 2001 From: Nino Date: Tue, 17 Dec 2024 14:08:15 +0100 Subject: [PATCH 5/8] changes for brp obo --- .env | 2 ++ .gitignore | 1 + .local.env.example | 3 +++ app/apps/addresses/views.py | 8 ++++---- app/config/settings.py | 2 ++ app/utils/api_queries_brp.py | 30 +++++++++++++++++++++++------- docker-compose.local.yml | 1 + 7 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 .local.env.example diff --git a/.env b/.env index 3a453ab8f..58887a98f 100644 --- a/.env +++ b/.env @@ -53,3 +53,5 @@ HOST=http://172.17.0.1:8080 USE_DECOS_MOCK_DATA=False SESSION_COOKIE_AGE=25200 AXES_ENABLED=False +BRP_CLIENT_ID = client_id +BRP_CLIENT_SECRET = client_secret diff --git a/.gitignore b/.gitignore index a6d52bd8c..1e6e26ae0 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ __pycache__/ app/.env app/.coverage private_media/ +.local.env diff --git a/.local.env.example b/.local.env.example new file mode 100644 index 000000000..8272b128d --- /dev/null +++ b/.local.env.example @@ -0,0 +1,3 @@ +# Set these in your .local.env file +BRP_CLIENT_ID= +BRP_CLIENT_SECRET= diff --git a/app/apps/addresses/views.py b/app/apps/addresses/views.py index 5a68d8114..6e4581e49 100644 --- a/app/apps/addresses/views.py +++ b/app/apps/addresses/views.py @@ -41,7 +41,7 @@ class AddressViewSet( serializer_class = AddressSerializer queryset = Address.objects.all() lookup_field = "bag_id" - http_method_names = ["get", "patch"] + http_method_names = ["get", "patch", "post"] def update(self, request, bag_id, *args, **kwargs): address_instance = Address.objects.get(bag_id=bag_id) @@ -56,7 +56,7 @@ def update(self, request, bag_id, *args, **kwargs): @action( detail=True, - methods=["get"], + methods=["post"], serializer_class=ResidentsSerializer, url_path="residents", permission_classes=[permissions.CanAccessBRP], @@ -81,9 +81,9 @@ def residents_by_bag_id(self, request, bag_id): # nummeraanduiding_id should have been retrieved, so get BRP data if address.nummeraanduiding_id: try: - brp_access_token = request.GET.get("brp_access_token", None) + obo_acces_stoken = request.data.get("obo_acces_stoken") brp_data, status_code = get_brp_by_nummeraanduiding_id( - request, address.nummeraanduiding_id, brp_access_token + request, address.nummeraanduiding_id, obo_acces_stoken ) serialized_residents = ResidentsSerializer(data=brp_data) serialized_residents.is_valid(raise_exception=True) diff --git a/app/config/settings.py b/app/config/settings.py index c3b961604..4a172ca6b 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -365,6 +365,8 @@ def filter_traces(envelope): ] ) +BRP_CLIENT_ID = os.getenv("BRP_CLIENT_ID", "BRP_CLIENT_ID") +BRP_CLIENT_SECRET = os.getenv("BRP_CLIENT_SECRET", "BRP_CLIENT_SECRET") # Secret keys which can be used to access certain parts of the API SECRET_KEY_TOP_ZAKEN = os.getenv("SECRET_KEY_TOP_ZAKEN", None) SECRET_KEY_TON_ZAKEN = os.getenv("SECRET_KEY_TON_ZAKEN", None) diff --git a/app/utils/api_queries_brp.py b/app/utils/api_queries_brp.py index e19879d2d..9de7661a2 100644 --- a/app/utils/api_queries_brp.py +++ b/app/utils/api_queries_brp.py @@ -2,13 +2,13 @@ import requests from django.conf import settings -from tenacity import after_log, retry, stop_after_attempt +from tenacity import retry, stop_after_attempt from utils.exceptions import MKSPermissionsError logger = logging.getLogger(__name__) -def get_brp_by_nummeraanduiding_id(request, nummeraanduiding_id, brp_access_token): +def get_brp_by_nummeraanduiding_id(request, nummeraanduiding_id, obo_acces_stoken): """Returns BRP data by bag_""" queryParams = { @@ -16,7 +16,7 @@ def get_brp_by_nummeraanduiding_id(request, nummeraanduiding_id, brp_access_toke "inclusiefoverledenpersonen": "true", "expand": "partners,ouders,kinderen", } - return get_brp(request, queryParams, brp_access_token) + return get_brp(queryParams, obo_acces_stoken) def get_brp_by_address(request, postal_code, number, suffix, suffix_letter): @@ -43,12 +43,11 @@ def get_brp_by_address(request, postal_code, number, suffix, suffix_letter): return get_brp(request, queryParams) -@retry(stop=stop_after_attempt(3), after=after_log(logger, logging.ERROR)) -def get_brp(request, queryParams, brp_access_token): +@retry(stop=stop_after_attempt(3)) +def get_brp(queryParams, obo_acces_stoken): """Returns BRP data""" - url = f"{settings.BRP_API_URL}" - + brp_access_token = get_brp_access_token(obo_acces_stoken) response = requests.get( url, params=queryParams, @@ -57,12 +56,29 @@ def get_brp(request, queryParams, brp_access_token): "Authorization": f"Bearer {brp_access_token}", }, ) + print(response.text) if response.status_code == 403: raise MKSPermissionsError() return response.json(), response.status_code +def get_brp_access_token(obo_acces_stoken): + url = settings.OIDC_OP_TOKEN_ENDPOINT + payload = { + "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", + "client_id": settings.BRP_CLIENT_ID, + "client_secret": settings.BRP_CLIENT_SECRET, + "assertion": obo_acces_stoken, + "scope": f"{settings.BRP_CLIENT_ID}/.default", + "requested_token_use": "on_behalf_of", + } + + response = requests.request("POST", url, data=payload) + print(response.text) + return response.json().get("access_token") + + def get_mock_brp(): return { "message": "mocked data", diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 5b3df1aed..7446beeee 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -36,6 +36,7 @@ services: - zaak-redis env_file: - .env + - .local.env entrypoint: /app/deploy/docker-entrypoint.development.sh command: python -m debugpy --listen 0.0.0.0:5678 ./manage.py runserver 0.0.0.0:8000 volumes: From 4e87c3068af1231a1c9f9ef2ebec9ad6c4b82d14 Mon Sep 17 00:00:00 2001 From: Remy van der Wereld Date: Tue, 17 Dec 2024 14:23:09 +0100 Subject: [PATCH 6/8] fix: rename obo_acces_stoken to obo_access_token for consistency --- app/apps/addresses/views.py | 4 ++-- app/utils/api_queries_brp.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/apps/addresses/views.py b/app/apps/addresses/views.py index 6e4581e49..e5ff6e043 100644 --- a/app/apps/addresses/views.py +++ b/app/apps/addresses/views.py @@ -81,9 +81,9 @@ def residents_by_bag_id(self, request, bag_id): # nummeraanduiding_id should have been retrieved, so get BRP data if address.nummeraanduiding_id: try: - obo_acces_stoken = request.data.get("obo_acces_stoken") + obo_access_token = request.data.get("obo_access_token") brp_data, status_code = get_brp_by_nummeraanduiding_id( - request, address.nummeraanduiding_id, obo_acces_stoken + request, address.nummeraanduiding_id, obo_access_token ) serialized_residents = ResidentsSerializer(data=brp_data) serialized_residents.is_valid(raise_exception=True) diff --git a/app/utils/api_queries_brp.py b/app/utils/api_queries_brp.py index 9de7661a2..8e43d644b 100644 --- a/app/utils/api_queries_brp.py +++ b/app/utils/api_queries_brp.py @@ -8,7 +8,7 @@ logger = logging.getLogger(__name__) -def get_brp_by_nummeraanduiding_id(request, nummeraanduiding_id, obo_acces_stoken): +def get_brp_by_nummeraanduiding_id(request, nummeraanduiding_id, obo_access_token): """Returns BRP data by bag_""" queryParams = { @@ -16,7 +16,7 @@ def get_brp_by_nummeraanduiding_id(request, nummeraanduiding_id, obo_acces_stoke "inclusiefoverledenpersonen": "true", "expand": "partners,ouders,kinderen", } - return get_brp(queryParams, obo_acces_stoken) + return get_brp(queryParams, obo_access_token) def get_brp_by_address(request, postal_code, number, suffix, suffix_letter): @@ -44,10 +44,10 @@ def get_brp_by_address(request, postal_code, number, suffix, suffix_letter): @retry(stop=stop_after_attempt(3)) -def get_brp(queryParams, obo_acces_stoken): +def get_brp(queryParams, obo_access_token): """Returns BRP data""" url = f"{settings.BRP_API_URL}" - brp_access_token = get_brp_access_token(obo_acces_stoken) + brp_access_token = get_brp_access_token(obo_access_token) response = requests.get( url, params=queryParams, @@ -63,13 +63,13 @@ def get_brp(queryParams, obo_acces_stoken): return response.json(), response.status_code -def get_brp_access_token(obo_acces_stoken): +def get_brp_access_token(obo_access_token): url = settings.OIDC_OP_TOKEN_ENDPOINT payload = { "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer", "client_id": settings.BRP_CLIENT_ID, "client_secret": settings.BRP_CLIENT_SECRET, - "assertion": obo_acces_stoken, + "assertion": obo_access_token, "scope": f"{settings.BRP_CLIENT_ID}/.default", "requested_token_use": "on_behalf_of", } From 33dc73982bcae7b912b79bd65a5613639de0cb0f Mon Sep 17 00:00:00 2001 From: Nino Date: Tue, 17 Dec 2024 14:24:22 +0100 Subject: [PATCH 7/8] make it non required --- app/apps/addresses/serializers.py | 4 ++++ app/apps/addresses/views.py | 2 ++ docker-compose.local.yml | 5 +++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/apps/addresses/serializers.py b/app/apps/addresses/serializers.py index 36f81e02e..9848a4d15 100644 --- a/app/apps/addresses/serializers.py +++ b/app/apps/addresses/serializers.py @@ -110,6 +110,10 @@ class ResidentsSerializer(serializers.Serializer): _embedded = serializers.DictField() +class GetResidentsSerializer(serializers.Serializer): + obo_access_token = serializers.DictField() + + class MeldingenSerializer(serializers.Serializer): pageNumber = serializers.IntegerField() pageSize = serializers.IntegerField() diff --git a/app/apps/addresses/views.py b/app/apps/addresses/views.py index e5ff6e043..47d108492 100644 --- a/app/apps/addresses/views.py +++ b/app/apps/addresses/views.py @@ -4,6 +4,7 @@ from apps.addresses.serializers import ( AddressSerializer, DistrictSerializer, + GetResidentsSerializer, HousingCorporationSerializer, MeldingenSerializer, ResidentsSerializer, @@ -61,6 +62,7 @@ def update(self, request, bag_id, *args, **kwargs): url_path="residents", permission_classes=[permissions.CanAccessBRP], ) + @extend_schema(request={GetResidentsSerializer}) def residents_by_bag_id(self, request, bag_id): # Get address try: diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 7446beeee..44e7c1a38 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -35,8 +35,9 @@ services: - database - zaak-redis env_file: - - .env - - .local.env + - path: .env + - path: .local.env + required: false entrypoint: /app/deploy/docker-entrypoint.development.sh command: python -m debugpy --listen 0.0.0.0:5678 ./manage.py runserver 0.0.0.0:8000 volumes: From 21318bc22424b1645ecee6b08cf14a7d30dd02ce Mon Sep 17 00:00:00 2001 From: Remy van der Wereld Date: Wed, 18 Dec 2024 10:40:17 +0100 Subject: [PATCH 8/8] Removed prints --- app/utils/api_queries_brp.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/utils/api_queries_brp.py b/app/utils/api_queries_brp.py index 8e43d644b..f87104b48 100644 --- a/app/utils/api_queries_brp.py +++ b/app/utils/api_queries_brp.py @@ -56,7 +56,6 @@ def get_brp(queryParams, obo_access_token): "Authorization": f"Bearer {brp_access_token}", }, ) - print(response.text) if response.status_code == 403: raise MKSPermissionsError() @@ -75,7 +74,7 @@ def get_brp_access_token(obo_access_token): } response = requests.request("POST", url, data=payload) - print(response.text) + return response.json().get("access_token")