commonground-api-common 1.13.2__py3-none-any.whl → 1.13.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: commonground-api-common
3
- Version: 1.13.2
3
+ Version: 1.13.4
4
4
  Summary: Commonground API tooling
5
5
  Home-page: https://github.com/maykinmedia/commonground-api-common
6
6
  Author: Maykin Media, VNG-Realisatie
@@ -17,50 +17,48 @@ Classifier: Operating System :: MacOS
17
17
  Classifier: Operating System :: Microsoft :: Windows
18
18
  Classifier: Programming Language :: Python :: 3
19
19
  Classifier: Programming Language :: Python :: 3 :: Only
20
- Classifier: Programming Language :: Python :: 3.8
21
- Classifier: Programming Language :: Python :: 3.9
22
20
  Classifier: Programming Language :: Python :: 3.10
23
21
  Classifier: Programming Language :: Python :: 3.11
24
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
25
- Requires-Dist: django >=3.2.0
26
- Requires-Dist: django-filter >=2.0
23
+ Requires-Dist: django>=3.2.0
24
+ Requires-Dist: django-filter>=2.0
27
25
  Requires-Dist: django-solo
28
- Requires-Dist: djangorestframework >=3.11.0
29
- Requires-Dist: djangorestframework-camel-case >=1.2.0
26
+ Requires-Dist: djangorestframework>=3.11.0
27
+ Requires-Dist: djangorestframework-camel-case>=1.2.0
30
28
  Requires-Dist: django-rest-framework-condition
31
- Requires-Dist: drf-yasg >=1.20.0
32
- Requires-Dist: drf-nested-routers >=0.93.3
33
- Requires-Dist: gemma-zds-client >=0.14.0
29
+ Requires-Dist: drf-yasg>=1.20.0
30
+ Requires-Dist: drf-nested-routers>=0.93.3
31
+ Requires-Dist: gemma-zds-client>=0.14.0
34
32
  Requires-Dist: iso-639
35
33
  Requires-Dist: isodate
36
- Requires-Dist: notifications-api-common >=0.2.2
34
+ Requires-Dist: notifications-api-common>=0.2.2
37
35
  Requires-Dist: oyaml
38
- Requires-Dist: PyJWT >=2.0.0
36
+ Requires-Dist: PyJWT>=2.0.0
39
37
  Requires-Dist: requests
40
38
  Requires-Dist: coreapi
41
39
  Provides-Extra: coverage
42
- Requires-Dist: pytest-cov ; extra == 'coverage'
40
+ Requires-Dist: pytest-cov; extra == "coverage"
43
41
  Provides-Extra: docs
44
- Requires-Dist: psycopg2 ; extra == 'docs'
45
- Requires-Dist: sphinx ; extra == 'docs'
46
- Requires-Dist: sphinx-rtd-theme ; extra == 'docs'
42
+ Requires-Dist: psycopg2; extra == "docs"
43
+ Requires-Dist: sphinx; extra == "docs"
44
+ Requires-Dist: sphinx-rtd-theme; extra == "docs"
47
45
  Provides-Extra: markdown_docs
48
- Requires-Dist: django-markup <=1.3 ; extra == 'markdown_docs'
49
- Requires-Dist: markdown ; extra == 'markdown_docs'
46
+ Requires-Dist: django-markup<=1.3; extra == "markdown-docs"
47
+ Requires-Dist: markdown; extra == "markdown-docs"
50
48
  Provides-Extra: pep8
51
- Requires-Dist: flake8 ; extra == 'pep8'
49
+ Requires-Dist: flake8; extra == "pep8"
52
50
  Provides-Extra: release
53
- Requires-Dist: bump2version ; extra == 'release'
51
+ Requires-Dist: bump2version; extra == "release"
54
52
  Provides-Extra: tests
55
- Requires-Dist: psycopg2 ; extra == 'tests'
56
- Requires-Dist: pytest ; extra == 'tests'
57
- Requires-Dist: pytest-django ; extra == 'tests'
58
- Requires-Dist: pytest-factoryboy ; extra == 'tests'
59
- Requires-Dist: tox ; extra == 'tests'
60
- Requires-Dist: isort ; extra == 'tests'
61
- Requires-Dist: black ; extra == 'tests'
62
- Requires-Dist: requests-mock ; extra == 'tests'
63
- Requires-Dist: freezegun ; extra == 'tests'
53
+ Requires-Dist: psycopg2; extra == "tests"
54
+ Requires-Dist: pytest; extra == "tests"
55
+ Requires-Dist: pytest-django; extra == "tests"
56
+ Requires-Dist: pytest-factoryboy; extra == "tests"
57
+ Requires-Dist: tox; extra == "tests"
58
+ Requires-Dist: isort; extra == "tests"
59
+ Requires-Dist: black; extra == "tests"
60
+ Requires-Dist: requests-mock; extra == "tests"
61
+ Requires-Dist: freezegun; extra == "tests"
64
62
 
65
63
  ===================================================
66
64
  Commonground-API-common - Tooling voor RESTful APIs
@@ -1,7 +1,7 @@
1
- commonground_api_common-1.13.2.data/scripts/generate_schema,sha256=UKhznmbHX1zUjPx8G3XtxUPQiqnyd3TOIXs27Tnjl7U,952
2
- commonground_api_common-1.13.2.data/scripts/patch_content_types,sha256=dpGpYrZOZe8O5CHWd0F0QnP6Wk_7lK6DyuVZpBPr4mY,319
3
- commonground_api_common-1.13.2.data/scripts/use_external_components,sha256=xvvbngO2aDUagVXv4xRUqPaVtH_eOaVMWLQ8lyAPhEA,369
4
- vng_api_common/__init__.py,sha256=e1i4EXbzZ1xnh8stGo52300k0WTGVZuco7l8x2OgNso,23
1
+ commonground_api_common-1.13.4.data/scripts/generate_schema,sha256=UKhznmbHX1zUjPx8G3XtxUPQiqnyd3TOIXs27Tnjl7U,952
2
+ commonground_api_common-1.13.4.data/scripts/patch_content_types,sha256=dpGpYrZOZe8O5CHWd0F0QnP6Wk_7lK6DyuVZpBPr4mY,319
3
+ commonground_api_common-1.13.4.data/scripts/use_external_components,sha256=xvvbngO2aDUagVXv4xRUqPaVtH_eOaVMWLQ8lyAPhEA,369
4
+ vng_api_common/__init__.py,sha256=Gra4o8i--fyNCOTGnifCcG2dIC3_llV16Cw1aadiFd8,23
5
5
  vng_api_common/admin.py,sha256=oWKpUl5yjeYOIsh60ruCmBDyPURGq4ALle4zGhlUQsE,902
6
6
  vng_api_common/apps.py,sha256=wOQuxUImMpH39R0JrDdCZp5ADaUM8jU8vFPFW9froSs,3458
7
7
  vng_api_common/checks.py,sha256=tOyfV7MMLGh4anrd_W30LvJCxiyQ4sFs1mGd9mtrEc0,1175
@@ -19,10 +19,11 @@ vng_api_common/filters.py,sha256=77m9s3V6woMqE7uOQoqzit7w1AQpWcGqZozzQFdKAYs,568
19
19
  vng_api_common/filtersets.py,sha256=Ioarp5t_cz6Hf3iIcP2Rc0D7tGYhcMQPVdIJJ2dvtv8,867
20
20
  vng_api_common/generators.py,sha256=tD7ZyyFgCQ3KiktpBFtn5YiVdl3jioGxzyvkEZFYdQQ,4911
21
21
  vng_api_common/geo.py,sha256=AZbrw0rwGYOmaSUk8JJSkx-4_tVrfT_cgggh9omRwhU,1862
22
- vng_api_common/middleware.py,sha256=DhdGA_9_bk7bYr-keHEt4F_VS6IkYJmzzAhkjqnzeYw,8857
22
+ vng_api_common/middleware.py,sha256=2DRw0hPpvUMqwoH1Ze8S7tDB16lmj4Bnd6vN5ijkGM8,875
23
23
  vng_api_common/mocks.py,sha256=JjpJyVmHReRUXbwDi2cnmuq4NYI1hLRr_W962TIhN6Y,6118
24
24
  vng_api_common/models.py,sha256=JmUfkM1PuiqW3wuTA-chMtcDGvohy4j0JsB95s7VK4Q,5041
25
25
  vng_api_common/oas.py,sha256=FiqBZ3n36q-fPRZEgpmLDxjnqmoAKmaTKZ-FlFcuRp0,2712
26
+ vng_api_common/pagination.py,sha256=sI6VJ5AHDBhtNRQ8OFrD2heQK-NxaL_6n73ijiqWDLc,255
26
27
  vng_api_common/permissions.py,sha256=ayDxk9Wt8j0yO57FhZ8XaRkPET4lAqd8SSw3m4o3EGs,7562
27
28
  vng_api_common/polymorphism.py,sha256=N-x39pG2unD4N0ZbxFvuhE8ibPaJ0eKeqfvAJW3NnEU,6623
28
29
  vng_api_common/routers.py,sha256=Tq_V-ewibFfcMl-0TS91_jvel6SxQGF-649GSKqlexE,1906
@@ -75,6 +76,7 @@ vng_api_common/audittrails/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
75
76
  vng_api_common/audittrails/migrations/_operations.py,sha256=UOMv0zAK8CIQ73cSu6wwQG_hkW46Isdy7JCnljn8GII,3280
76
77
  vng_api_common/authorizations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
77
78
  vng_api_common/authorizations/admin.py,sha256=BmJQgNH7KlDB3flAXrSMm_w0NowpJrDYWDY61RZcKCI,795
79
+ vng_api_common/authorizations/middleware.py,sha256=XcHXBvzGdVXSxDgiVaz6IAZQPS-pFsrJnCzepug1Ddw,8100
78
80
  vng_api_common/authorizations/models.py,sha256=ahwEMG5R6ILTlrnKo5TggYp52Z7dAJMk2QSZyZW8Qa0,4236
79
81
  vng_api_common/authorizations/serializers.py,sha256=3HeKWEqhI3UWwI8SttC4rEID-Epbk7SWsC-bEjolbaw,5151
80
82
  vng_api_common/authorizations/validators.py,sha256=u7fKm0QgGy8fiAeYmIEB9Gy-yIE9C-tC2ZpnNQBXPso,2816
@@ -187,7 +189,7 @@ vng_api_common/tests/auth.py,sha256=IKDWTEFv4Bign4F70-ibsFcnJqRxEJaXvqaPQJWa1xY,
187
189
  vng_api_common/tests/caching.py,sha256=zfIw5cRRvO9cekHZZKfRqZc8cx5IfJUYNmcH6cuIMg4,624
188
190
  vng_api_common/tests/schema.py,sha256=9LFgEXzcYGkHZtlpCiHNCWywR5lF7Pl0S43cwlwKDgQ,1954
189
191
  vng_api_common/tests/urls.py,sha256=PFrYzQbBC0TFPMEn3uPhcBG0IQs9JsEPqckicJT1UA4,2159
190
- commonground_api_common-1.13.2.dist-info/METADATA,sha256=suvblBi_2_joSevlo67TVW8tzQIEfvkiaPl2qC0Q1gM,6656
191
- commonground_api_common-1.13.2.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
192
- commonground_api_common-1.13.2.dist-info/top_level.txt,sha256=vPismc83zPzWXTmlNCCwfDlFV9iygJYxNJW5iDjKTgw,15
193
- commonground_api_common-1.13.2.dist-info/RECORD,,
192
+ commonground_api_common-1.13.4.dist-info/METADATA,sha256=Rvx7_yFH4rnQfECFINzFJxtkysviCExbotrYUUJ3yCc,6529
193
+ commonground_api_common-1.13.4.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
194
+ commonground_api_common-1.13.4.dist-info/top_level.txt,sha256=vPismc83zPzWXTmlNCCwfDlFV9iygJYxNJW5iDjKTgw,15
195
+ commonground_api_common-1.13.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.2.0)
2
+ Generator: setuptools (75.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1 +1 @@
1
- __version__ = "1.13.2"
1
+ __version__ = "1.13.4"
@@ -0,0 +1,235 @@
1
+ import logging
2
+ from typing import Any, Dict, Iterable, List, Optional
3
+
4
+ from django.conf import settings
5
+ from django.db import models, transaction
6
+ from django.db.models import QuerySet
7
+ from django.utils.translation import gettext as _
8
+
9
+ import jwt
10
+ from djangorestframework_camel_case.util import underscoreize
11
+ from rest_framework.exceptions import PermissionDenied
12
+ from zds_client.client import ClientError
13
+
14
+ from vng_api_common.constants import VertrouwelijkheidsAanduiding
15
+
16
+ from ..models import JWTSecret
17
+ from ..utils import get_uuid_from_path
18
+ from .models import Applicatie, AuthorizationsConfig, Autorisatie
19
+ from .serializers import ApplicatieUuidSerializer
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class JWTAuth:
25
+ def __init__(self, encoded: Optional[str] = None):
26
+ self.encoded = encoded
27
+
28
+ @property
29
+ def applicaties(self) -> Iterable[Applicatie]:
30
+ if self.client_id is None:
31
+ return []
32
+
33
+ applicaties = self._get_auth()
34
+
35
+ if not applicaties:
36
+ auth_data = self._request_auth()
37
+ applicaties = self._save_auth(auth_data)
38
+
39
+ return applicaties
40
+
41
+ @property
42
+ def autorisaties(self) -> models.QuerySet:
43
+ """
44
+ Retrieve all authorizations relevant to this component.
45
+ """
46
+ app_ids = [app.id for app in self.applicaties]
47
+ config = AuthorizationsConfig.get_solo()
48
+ return Autorisatie.objects.filter(
49
+ applicatie_id__in=app_ids, component=config.component
50
+ )
51
+
52
+ def _request_auth(self) -> list:
53
+ client = AuthorizationsConfig.get_client()
54
+ try:
55
+ response = client.list(
56
+ "applicatie", query_params={"clientIds": self.client_id}
57
+ )
58
+ except ClientError as exc:
59
+ response = exc.args[0]
60
+ # friendly debug - hint at where the problem is located
61
+ if response["status"] == 403 and response["code"] == "not_authenticated":
62
+ detail = _(
63
+ "Component could not authenticate against the AC - "
64
+ "authorizations could not be retrieved"
65
+ )
66
+ raise PermissionDenied(detail=detail, code="not_authenticated_for_ac")
67
+ logger.warn("Authorization component can't be accessed")
68
+ return []
69
+
70
+ return underscoreize(response["results"])
71
+
72
+ def _get_auth(self):
73
+ return Applicatie.objects.filter(client_ids__contains=[self.client_id])
74
+
75
+ @transaction.atomic
76
+ def _save_auth(self, auth_data):
77
+ applicaties = []
78
+
79
+ for applicatie_data in auth_data:
80
+ applicatie_serializer = ApplicatieUuidSerializer(data=applicatie_data)
81
+ uuid = get_uuid_from_path(applicatie_data["url"])
82
+ applicatie_data["uuid"] = uuid
83
+ applicatie_serializer.is_valid()
84
+ applicaties.append(applicatie_serializer.save())
85
+
86
+ return applicaties
87
+
88
+ @property
89
+ def payload(self) -> Optional[Dict[str, Any]]:
90
+ if self.encoded is None:
91
+ return None
92
+
93
+ if not hasattr(self, "_payload"):
94
+ # decode the JWT and validate it
95
+
96
+ # jwt check
97
+ try:
98
+ payload = jwt.decode(
99
+ self.encoded,
100
+ algorithms=["HS256"],
101
+ options={"verify_signature": False},
102
+ leeway=settings.JWT_LEEWAY,
103
+ )
104
+ except jwt.DecodeError:
105
+ logger.info("Invalid JWT encountered")
106
+ raise PermissionDenied(
107
+ _(
108
+ "JWT could not be decoded. Possibly you made a copy-paste mistake."
109
+ ),
110
+ code="jwt-decode-error",
111
+ )
112
+
113
+ # get client_id
114
+ try:
115
+ client_id = payload["client_id"]
116
+ except KeyError:
117
+ raise PermissionDenied(
118
+ "Client identifier is niet aanwezig in JWT",
119
+ code="missing-client-identifier",
120
+ )
121
+
122
+ # find client_id in DB and retrieve its secret
123
+ try:
124
+ jwt_secret = JWTSecret.objects.exclude(secret="").get(
125
+ identifier=client_id
126
+ )
127
+ except JWTSecret.DoesNotExist:
128
+ raise PermissionDenied(
129
+ "Client identifier bestaat niet", code="invalid-client-identifier"
130
+ )
131
+ else:
132
+ key = jwt_secret.secret
133
+
134
+ # check signature of the token
135
+ try:
136
+ payload = jwt.decode(
137
+ self.encoded,
138
+ key,
139
+ algorithms=["HS256"],
140
+ leeway=settings.JWT_LEEWAY,
141
+ )
142
+ except jwt.InvalidSignatureError:
143
+ logger.exception("Invalid signature - possible payload tampering?")
144
+ raise PermissionDenied(
145
+ "Client credentials zijn niet geldig", code="invalid-jwt-signature"
146
+ )
147
+
148
+ self._payload = payload
149
+
150
+ return self._payload
151
+
152
+ @property
153
+ def client_id(self) -> Optional[str]:
154
+ if not self.payload:
155
+ return None
156
+ return self.payload["client_id"]
157
+
158
+ def filter_vertrouwelijkheidaanduiding(self, base: QuerySet, value) -> QuerySet:
159
+ if value is None:
160
+ return base
161
+
162
+ order_provided = VertrouwelijkheidsAanduiding.get_choice_order(value)
163
+ order_case = VertrouwelijkheidsAanduiding.get_order_expression(
164
+ "max_vertrouwelijkheidaanduiding"
165
+ )
166
+
167
+ # In this case we are filtering Autorisatie model to look for auth which meets our needs.
168
+ # Therefore we're only considering authorizations here that have a max_vertrouwelijkheidaanduiding
169
+ # bigger or equal than what we're checking for the object.
170
+ # In cases when we are filtering data objects (Zaak, InformatieObject etc) it's the other way around
171
+
172
+ return base.annotate(max_vertr=order_case).filter(max_vertr__gte=order_provided)
173
+
174
+ def filter_default(self, base: QuerySet, name, value) -> QuerySet:
175
+ if value is None:
176
+ return base
177
+
178
+ return base.filter(**{name: value})
179
+
180
+ def has_auth(
181
+ self, scopes: List[str], component: Optional[str] = None, **fields
182
+ ) -> bool:
183
+ if scopes is None:
184
+ return False
185
+
186
+ scopes_provided = set()
187
+ config = AuthorizationsConfig.get_solo()
188
+ if component is None:
189
+ component = config.component
190
+
191
+ for applicatie in self.applicaties:
192
+ # allow everything
193
+ if applicatie.heeft_alle_autorisaties is True:
194
+ return True
195
+
196
+ autorisaties = applicatie.autorisaties.filter(component=component)
197
+
198
+ # filter on all additional components
199
+ for field_name, field_value in fields.items():
200
+ if hasattr(self, f"filter_{field_name}"):
201
+ autorisaties = getattr(self, f"filter_{field_name}")(
202
+ autorisaties, field_value
203
+ )
204
+ else:
205
+ autorisaties = self.filter_default(
206
+ autorisaties, field_name, field_value
207
+ )
208
+
209
+ for autorisatie in autorisaties:
210
+ scopes_provided.update(autorisatie.scopes)
211
+
212
+ return scopes.is_contained_in(list(scopes_provided))
213
+
214
+
215
+ class AuthMiddleware:
216
+ header = "HTTP_AUTHORIZATION"
217
+ auth_type = "Bearer"
218
+
219
+ def __init__(self, get_response=None):
220
+ self.get_response = get_response
221
+
222
+ def __call__(self, request):
223
+ self.extract_jwt_payload(request)
224
+ return self.get_response(request) if self.get_response else None
225
+
226
+ def extract_jwt_payload(self, request):
227
+ authorization = request.META.get(self.header, "")
228
+ prefix = f"{self.auth_type} "
229
+ if authorization.startswith(prefix):
230
+ # grab the actual token
231
+ encoded = authorization[len(prefix) :]
232
+ else:
233
+ encoded = None
234
+
235
+ request.jwt_auth = JWTAuth(encoded)
@@ -1,242 +1,16 @@
1
1
  # https://pyjwt.readthedocs.io/en/latest/usage.html#reading-headers-without-validation
2
2
  # -> we can put the organization/service in the headers itself
3
3
  import logging
4
- from typing import Any, Dict, Iterable, List, Optional
5
4
 
6
5
  from django.conf import settings
7
- from django.db import models, transaction
8
- from django.db.models import QuerySet
9
- from django.utils.translation import gettext as _
10
6
 
11
- import jwt
12
- from djangorestframework_camel_case.util import underscoreize
13
- from rest_framework.exceptions import PermissionDenied
14
7
  from rest_framework.response import Response
15
- from zds_client.client import ClientError
16
8
 
17
- from .authorizations.models import Applicatie, AuthorizationsConfig, Autorisatie
18
- from .authorizations.serializers import ApplicatieUuidSerializer
19
- from .constants import VERSION_HEADER, VertrouwelijkheidsAanduiding
20
- from .models import JWTSecret
21
- from .utils import get_uuid_from_path
9
+ from .constants import VERSION_HEADER
22
10
 
23
11
  logger = logging.getLogger(__name__)
24
12
 
25
13
 
26
- class JWTAuth:
27
- def __init__(self, encoded: str = None):
28
- self.encoded = encoded
29
-
30
- @property
31
- def applicaties(self) -> Iterable[Applicatie]:
32
- if self.client_id is None:
33
- return []
34
-
35
- applicaties = self._get_auth()
36
-
37
- if not applicaties:
38
- auth_data = self._request_auth()
39
- applicaties = self._save_auth(auth_data)
40
-
41
- return applicaties
42
-
43
- @property
44
- def autorisaties(self) -> models.QuerySet:
45
- """
46
- Retrieve all authorizations relevant to this component.
47
- """
48
- app_ids = [app.id for app in self.applicaties]
49
- config = AuthorizationsConfig.get_solo()
50
- return Autorisatie.objects.filter(
51
- applicatie_id__in=app_ids, component=config.component
52
- )
53
-
54
- def _request_auth(self) -> list:
55
- client = AuthorizationsConfig.get_client()
56
- try:
57
- response = client.list(
58
- "applicatie", query_params={"clientIds": self.client_id}
59
- )
60
- except ClientError as exc:
61
- response = exc.args[0]
62
- # friendly debug - hint at where the problem is located
63
- if response["status"] == 403 and response["code"] == "not_authenticated":
64
- detail = _(
65
- "Component could not authenticate against the AC - "
66
- "authorizations could not be retrieved"
67
- )
68
- raise PermissionDenied(detail=detail, code="not_authenticated_for_ac")
69
- logger.warn("Authorization component can't be accessed")
70
- return []
71
-
72
- return underscoreize(response["results"])
73
-
74
- def _get_auth(self):
75
- return Applicatie.objects.filter(client_ids__contains=[self.client_id])
76
-
77
- @transaction.atomic
78
- def _save_auth(self, auth_data):
79
- applicaties = []
80
-
81
- for applicatie_data in auth_data:
82
- applicatie_serializer = ApplicatieUuidSerializer(data=applicatie_data)
83
- uuid = get_uuid_from_path(applicatie_data["url"])
84
- applicatie_data["uuid"] = uuid
85
- applicatie_serializer.is_valid()
86
- applicaties.append(applicatie_serializer.save())
87
-
88
- return applicaties
89
-
90
- @property
91
- def payload(self) -> Optional[Dict[str, Any]]:
92
- if self.encoded is None:
93
- return None
94
-
95
- if not hasattr(self, "_payload"):
96
- # decode the JWT and validate it
97
-
98
- # jwt check
99
- try:
100
- payload = jwt.decode(
101
- self.encoded,
102
- algorithms=["HS256"],
103
- options={"verify_signature": False},
104
- leeway=settings.JWT_LEEWAY,
105
- )
106
- except jwt.DecodeError:
107
- logger.info("Invalid JWT encountered")
108
- raise PermissionDenied(
109
- _(
110
- "JWT could not be decoded. Possibly you made a copy-paste mistake."
111
- ),
112
- code="jwt-decode-error",
113
- )
114
-
115
- # get client_id
116
- try:
117
- client_id = payload["client_id"]
118
- except KeyError:
119
- raise PermissionDenied(
120
- "Client identifier is niet aanwezig in JWT",
121
- code="missing-client-identifier",
122
- )
123
-
124
- # find client_id in DB and retrieve its secret
125
- try:
126
- jwt_secret = JWTSecret.objects.exclude(secret="").get(
127
- identifier=client_id
128
- )
129
- except JWTSecret.DoesNotExist:
130
- raise PermissionDenied(
131
- "Client identifier bestaat niet", code="invalid-client-identifier"
132
- )
133
- else:
134
- key = jwt_secret.secret
135
-
136
- # check signature of the token
137
- try:
138
- payload = jwt.decode(
139
- self.encoded,
140
- key,
141
- algorithms=["HS256"],
142
- leeway=settings.JWT_LEEWAY,
143
- )
144
- except jwt.InvalidSignatureError:
145
- logger.exception("Invalid signature - possible payload tampering?")
146
- raise PermissionDenied(
147
- "Client credentials zijn niet geldig", code="invalid-jwt-signature"
148
- )
149
-
150
- self._payload = payload
151
-
152
- return self._payload
153
-
154
- @property
155
- def client_id(self) -> str:
156
- if not self.payload:
157
- return None
158
- return self.payload["client_id"]
159
-
160
- def filter_vertrouwelijkheidaanduiding(self, base: QuerySet, value) -> QuerySet:
161
- if value is None:
162
- return base
163
-
164
- order_provided = VertrouwelijkheidsAanduiding.get_choice_order(value)
165
- order_case = VertrouwelijkheidsAanduiding.get_order_expression(
166
- "max_vertrouwelijkheidaanduiding"
167
- )
168
-
169
- # In this case we are filtering Autorisatie model to look for auth which meets our needs.
170
- # Therefore we're only considering authorizations here that have a max_vertrouwelijkheidaanduiding
171
- # bigger or equal than what we're checking for the object.
172
- # In cases when we are filtering data objects (Zaak, InformatieObject etc) it's the other way around
173
-
174
- return base.annotate(max_vertr=order_case).filter(max_vertr__gte=order_provided)
175
-
176
- def filter_default(self, base: QuerySet, name, value) -> QuerySet:
177
- if value is None:
178
- return base
179
-
180
- return base.filter(**{name: value})
181
-
182
- def has_auth(
183
- self, scopes: List[str], component: Optional[str] = None, **fields
184
- ) -> bool:
185
- if scopes is None:
186
- return False
187
-
188
- scopes_provided = set()
189
- config = AuthorizationsConfig.get_solo()
190
- if component is None:
191
- component = config.component
192
-
193
- for applicatie in self.applicaties:
194
- # allow everything
195
- if applicatie.heeft_alle_autorisaties is True:
196
- return True
197
-
198
- autorisaties = applicatie.autorisaties.filter(component=component)
199
-
200
- # filter on all additional components
201
- for field_name, field_value in fields.items():
202
- if hasattr(self, f"filter_{field_name}"):
203
- autorisaties = getattr(self, f"filter_{field_name}")(
204
- autorisaties, field_value
205
- )
206
- else:
207
- autorisaties = self.filter_default(
208
- autorisaties, field_name, field_value
209
- )
210
-
211
- for autorisatie in autorisaties:
212
- scopes_provided.update(autorisatie.scopes)
213
-
214
- return scopes.is_contained_in(list(scopes_provided))
215
-
216
-
217
- class AuthMiddleware:
218
- header = "HTTP_AUTHORIZATION"
219
- auth_type = "Bearer"
220
-
221
- def __init__(self, get_response=None):
222
- self.get_response = get_response
223
-
224
- def __call__(self, request):
225
- self.extract_jwt_payload(request)
226
- return self.get_response(request) if self.get_response else None
227
-
228
- def extract_jwt_payload(self, request):
229
- authorization = request.META.get(self.header, "")
230
- prefix = f"{self.auth_type} "
231
- if authorization.startswith(prefix):
232
- # grab the actual token
233
- encoded = authorization[len(prefix) :]
234
- else:
235
- encoded = None
236
-
237
- request.jwt_auth = JWTAuth(encoded)
238
-
239
-
240
14
  class APIVersionHeaderMiddleware:
241
15
  """
242
16
  Include a header specifying the API-version
@@ -0,0 +1,10 @@
1
+ from rest_framework.pagination import PageNumberPagination
2
+
3
+
4
+ class DynamicPageSizeMixin:
5
+ page_size = 100
6
+ page_size_query_param = "pageSize"
7
+ max_page_size = 500
8
+
9
+
10
+ class DynamicPageSizePagination(DynamicPageSizeMixin, PageNumberPagination): ...