Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First version entra #1288

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ __pycache__/
app/.env
app/.coverage
private_media/
.local.env
3 changes: 3 additions & 0 deletions .local.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Set these in your .local.env file
BRP_CLIENT_ID=
BRP_CLIENT_SECRET=
4 changes: 4 additions & 0 deletions app/apps/addresses/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
9 changes: 6 additions & 3 deletions app/apps/addresses/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from apps.addresses.serializers import (
AddressSerializer,
DistrictSerializer,
GetResidentsSerializer,
HousingCorporationSerializer,
MeldingenSerializer,
ResidentsSerializer,
Expand Down Expand Up @@ -41,7 +42,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)
Expand All @@ -56,11 +57,12 @@ 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],
)
@extend_schema(request={GetResidentsSerializer})
def residents_by_bag_id(self, request, bag_id):
# Get address
try:
Expand All @@ -81,8 +83,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_access_token = request.data.get("obo_access_token")
brp_data, status_code = get_brp_by_nummeraanduiding_id(
request, address.nummeraanduiding_id
request, address.nummeraanduiding_id, obo_access_token
)
serialized_residents = ResidentsSerializer(data=brp_data)
serialized_residents.is_valid(raise_exception=True)
Expand Down
3 changes: 1 addition & 2 deletions app/apps/cases/views/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
59 changes: 58 additions & 1 deletion app/apps/users/auth.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,70 @@
import logging
import time

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

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 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")
trusted_audiences = self.get_settings("OIDC_TRUSTED_AUDIENCES", [])
trusted_audiences = set(trusted_audiences)
# trusted_audiences.add(client_id)

audience = payload["aud"]
audience = set(audience)
distrusted_audiences = audience.difference(trusted_audiences)
if distrusted_audiences:
raise InvalidTokenError(
'"aud" contains distrusted audiences: %r' % distrusted_audiences
)

def validate_expiry(self, payload):
expire_time = payload["exp"]
now = time.time()
if 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

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:
Expand Down
32 changes: 31 additions & 1 deletion app/apps/users/permissions.py
Original file line number Diff line number Diff line change
@@ -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"),
Expand Down
2 changes: 1 addition & 1 deletion app/apps/users/tests/tests_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion app/apps/users/views.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 1 addition & 2 deletions app/apps/workflow/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand Down
37 changes: 19 additions & 18 deletions app/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -48,7 +46,6 @@
"django.contrib.postgres",
"corsheaders",
# Third party apps
"keycloak_oidc",
"rest_framework",
"rest_framework.authtoken",
"drf_spectacular",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -219,7 +214,7 @@
"level": LOGGING_LEVEL,
"propagate": True,
},
"mozilla_django_oidc": {"handlers": ["console"], "level": "INFO"},
"mozilla_django_oidc": {"handlers": ["console"], "level": LOGGING_LEVEL},
},
}

Expand Down Expand Up @@ -274,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 = (
Expand All @@ -283,31 +277,36 @@ 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",
"https://login.microsoftonline.com/72fca1b1-2c2e-4376-a445-294d80196804/discovery/v2.0/keys",
)
OIDC_OP_LOGOUT_ENDPOINT = os.getenv(
"OIDC_OP_LOGOUT_ENDPOINT",
"https://acc.iam.amsterdam.nl/auth/realms/datapunt-ad-acc/protocol/openid-connect/logout",
OIDC_RP_SIGN_ALGO = "RS256"
OIDC_OP_ISSUER = os.getenv(
"OIDC_OP_ISSUER",
"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

Expand Down Expand Up @@ -361,11 +360,13 @@ 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",
]
)

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)
Expand Down
1 change: 0 additions & 1 deletion app/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading