commonground-api-common 2.6.6__py3-none-any.whl → 2.6.7__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.4
2
2
  Name: commonground-api-common
3
- Version: 2.6.6
3
+ Version: 2.6.7
4
4
  Summary: Commonground API tooling
5
5
  Home-page: https://github.com/maykinmedia/commonground-api-common
6
6
  Author: Maykin Media, VNG-Realisatie
@@ -32,7 +32,7 @@ Requires-Dist: iso639-lang
32
32
  Requires-Dist: notifications-api-common>=0.3.1
33
33
  Requires-Dist: zgw-consumers>=0.35.1
34
34
  Requires-Dist: oyaml
35
- Requires-Dist: PyJWT>=2.1.1
35
+ Requires-Dist: PyJWT>=2.10.1
36
36
  Requires-Dist: requests
37
37
  Requires-Dist: coreapi
38
38
  Requires-Dist: ape-pie
@@ -45,10 +45,10 @@ Provides-Extra: drf-extra-fields
45
45
  Requires-Dist: drf-extra-fields>=3.7.0; extra == "drf-extra-fields"
46
46
  Provides-Extra: tests
47
47
  Requires-Dist: psycopg2; extra == "tests"
48
- Requires-Dist: pytest==8.3.5; extra == "tests"
48
+ Requires-Dist: pytest; extra == "tests"
49
49
  Requires-Dist: pytest-django; extra == "tests"
50
50
  Requires-Dist: pytest-dotenv; extra == "tests"
51
- Requires-Dist: pytest-factoryboy; extra == "tests"
51
+ Requires-Dist: pytest-factoryboy>=2.8.0; extra == "tests"
52
52
  Requires-Dist: tox; extra == "tests"
53
53
  Requires-Dist: ruff; extra == "tests"
54
54
  Requires-Dist: requests-mock; extra == "tests"
@@ -76,7 +76,7 @@ Commonground-API-common - Tooling voor RESTful APIs
76
76
  ===================================================
77
77
 
78
78
 
79
- :Version: 2.6.6
79
+ :Version: 2.6.7
80
80
  :Source: https://github.com/maykinmedia/commonground-api-common
81
81
  :PythonVersion: 3.12
82
82
 
@@ -1,5 +1,5 @@
1
- commonground_api_common-2.6.6.data/scripts/generate_schema,sha256=OpKgzlFc_uzA3TVW_vHSYXAD_feLaCdTEnkWjIcxVzA,280
2
- vng_api_common/__init__.py,sha256=MJHGx-Qo0nycI7WHSavnK8Mok6HS_De_qLfGWXih6Og,22
1
+ commonground_api_common-2.6.7.data/scripts/generate_schema,sha256=OpKgzlFc_uzA3TVW_vHSYXAD_feLaCdTEnkWjIcxVzA,280
2
+ vng_api_common/__init__.py,sha256=Kc2cyTvfIdfaMxraNhj61Hw9Mr3C-eTCdQPLK5KH1AQ,22
3
3
  vng_api_common/admin.py,sha256=WiD6afzO9sJh_Nz7TKsxCiGm49vS0qQH1PxyXnGpEt8,204
4
4
  vng_api_common/apps.py,sha256=QQiJXRmjX9Q91oh0P9fvVnHe3NSYd1cEcUUBw0HLBCA,3690
5
5
  vng_api_common/checks.py,sha256=tOyfV7MMLGh4anrd_W30LvJCxiyQ4sFs1mGd9mtrEc0,1175
@@ -76,7 +76,7 @@ vng_api_common/audittrails/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCe
76
76
  vng_api_common/audittrails/migrations/_operations.py,sha256=UOMv0zAK8CIQ73cSu6wwQG_hkW46Isdy7JCnljn8GII,3280
77
77
  vng_api_common/authorizations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
78
78
  vng_api_common/authorizations/admin.py,sha256=Tk0yYKbb005E0XZaYYWbucMf_K5M8Hhz62wSBDi8rhM,813
79
- vng_api_common/authorizations/middleware.py,sha256=cR7reyRIZmCtH2qONopva8PZKCkwBl3DU-VWJqnL-Z0,8418
79
+ vng_api_common/authorizations/middleware.py,sha256=hbjGeg2UZ20QPSgZ8yMonDOS2dfrvFKPaxTcpoyH7gs,10446
80
80
  vng_api_common/authorizations/models.py,sha256=-PwbFceloUz6pbfFp2DB3YfioC2xq63YDm-EVc3U3HE,5160
81
81
  vng_api_common/authorizations/serializers.py,sha256=3HeKWEqhI3UWwI8SttC4rEID-Epbk7SWsC-bEjolbaw,5151
82
82
  vng_api_common/authorizations/utils.py,sha256=GSeHVXia-GHGCN_E4u671r4pGssvcyp9osKFf8Fc8v4,590
@@ -107,7 +107,7 @@ vng_api_common/caching/models.py,sha256=RroS9HFiKNXDV59Odh0x8BO8Az8E81v4gwuprF1A
107
107
  vng_api_common/caching/registry.py,sha256=oXE4kMnVpdp7qvb_goVkCTY5qeY8kB3EDKVX65fPtYc,6207
108
108
  vng_api_common/caching/signals.py,sha256=78ej5cVan-JpNHKzZNAfs0m2ON_TXKphe8ZKtP-6FVY,4615
109
109
  vng_api_common/conf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
110
- vng_api_common/conf/api.py,sha256=F1Aj8jOdN-T0ZYqUtuu-jUjST5XcyYcRFqZc057M0Bc,3160
110
+ vng_api_common/conf/api.py,sha256=FTiT_HYL2pzRF9o5aAsPErwo8wsphxOFM2RNVBxBFi8,3197
111
111
  vng_api_common/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
112
  vng_api_common/contrib/setup_configuration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
113
113
  vng_api_common/contrib/setup_configuration/models.py,sha256=1-G0hXeRe-x6GYtsAaQeMGXq0-cwU5LMb8KTQWj-pQk,1201
@@ -180,11 +180,11 @@ vng_api_common/templates/vng_api_common/ref/scopes.html,sha256=M3lTbnTOFr1OqDxkK
180
180
  vng_api_common/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
181
181
  vng_api_common/templatetags/vng_api_common.py,sha256=j4Fu4rNErLcXStBbuZ9Zd_xD_ft0HA6kfpPz6wLXnOw,1251
182
182
  vng_api_common/tests/__init__.py,sha256=zBI2etwu2lEkKkIN1_cPuugJgPcoohQuJbdiUGtoceU,426
183
- vng_api_common/tests/auth.py,sha256=IKDWTEFv4Bign4F70-ibsFcnJqRxEJaXvqaPQJWa1xY,4544
183
+ vng_api_common/tests/auth.py,sha256=fOTe8J69pk7axDPfY73vw4NcrIcTOGBvl0HEYD5Zc4Y,4614
184
184
  vng_api_common/tests/caching.py,sha256=zfIw5cRRvO9cekHZZKfRqZc8cx5IfJUYNmcH6cuIMg4,624
185
185
  vng_api_common/tests/schema.py,sha256=WDvifDQQiKqIpQijpeQ7rYkFroJmuPuHe7zNhl1Bigk,2293
186
186
  vng_api_common/tests/urls.py,sha256=PFrYzQbBC0TFPMEn3uPhcBG0IQs9JsEPqckicJT1UA4,2159
187
- commonground_api_common-2.6.6.dist-info/METADATA,sha256=cWsFJ1YO-TZLhYWVXu6r7Dp-9Qbt9RXbb8lfB97JAvM,7195
188
- commonground_api_common-2.6.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
189
- commonground_api_common-2.6.6.dist-info/top_level.txt,sha256=vPismc83zPzWXTmlNCCwfDlFV9iygJYxNJW5iDjKTgw,15
190
- commonground_api_common-2.6.6.dist-info/RECORD,,
187
+ commonground_api_common-2.6.7.dist-info/METADATA,sha256=HKmZYlZc5kA0yCwWIOz-zg7RyM141g1lNaJQFiY_qtI,7196
188
+ commonground_api_common-2.6.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
189
+ commonground_api_common-2.6.7.dist-info/top_level.txt,sha256=vPismc83zPzWXTmlNCCwfDlFV9iygJYxNJW5iDjKTgw,15
190
+ commonground_api_common-2.6.7.dist-info/RECORD,,
@@ -1 +1 @@
1
- __version__ = "2.6.6"
1
+ __version__ = "2.6.7"
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import time
2
3
  from typing import Any, Dict, Iterable, List, Optional
3
4
 
4
5
  from django.conf import settings
@@ -147,12 +148,31 @@ class JWTAuth:
147
148
  key,
148
149
  algorithms=["HS256"],
149
150
  leeway=settings.TIME_LEEWAY,
151
+ options={
152
+ "require": ["iat"],
153
+ "verify_iat": False,
154
+ }, # iat is validated in _check_jwt_expiry
150
155
  )
151
156
  except jwt.InvalidSignatureError:
152
157
  logger.exception("Invalid signature - possible payload tampering?")
153
158
  raise PermissionDenied(
154
159
  "Client credentials zijn niet geldig", code="invalid-jwt-signature"
155
160
  )
161
+ except jwt.MissingRequiredClaimError as exc:
162
+ msg = "Missing required {} claim".format(exc.claim)
163
+ logger.exception(msg)
164
+ raise PermissionDenied(
165
+ _(msg),
166
+ code="jwt-missing-{}-claim".format(exc.claim),
167
+ )
168
+ except jwt.PyJWTError as exc:
169
+ logger.exception("Invalid JWT encountered")
170
+ raise PermissionDenied(
171
+ _("JWT did not validate"),
172
+ code="jwt-{}".format(type(exc).__name__.lower()),
173
+ )
174
+
175
+ self._check_jwt_expiry(payload)
156
176
 
157
177
  self._payload = payload
158
178
 
@@ -164,6 +184,37 @@ class JWTAuth:
164
184
  return None
165
185
  return self.payload["client_id"]
166
186
 
187
+ def _check_jwt_expiry(self, payload: Dict[str, Any]) -> None:
188
+ """
189
+ Verify that the token was issued recently enough.
190
+
191
+ The Django settings define how long a JWT is considered to be valid. Adding
192
+ that duration to the issued-at claim determines the upper limit for token
193
+ validity.
194
+ """
195
+ iat = payload.get("iat")
196
+
197
+ try:
198
+ iat = int(iat)
199
+ except ValueError:
200
+ raise PermissionDenied(_("The iat claim must be an integer."))
201
+
202
+ current_timestamp = time.time()
203
+ difference = current_timestamp - iat
204
+
205
+ if difference < -settings.TIME_LEEWAY:
206
+ logger.warning(
207
+ "The JWT used for this request is not valid yet, the `iat` claim is "
208
+ "newer than the current time stamp. You may want to check the clock drift "
209
+ "on the Open Zaak server and/or tweak the `TIME_LEEWAY` setting.",
210
+ extra={"payload": payload},
211
+ )
212
+
213
+ if difference >= (settings.JWT_EXPIRY + settings.TIME_LEEWAY):
214
+ raise PermissionDenied(
215
+ _("The JWT used for this request is expired"), code="jwt-expired"
216
+ )
217
+
167
218
  def filter_vertrouwelijkheidaanduiding(self, base: QuerySet, value) -> QuerySet:
168
219
  if value is None:
169
220
  return base
@@ -9,6 +9,7 @@ __all__ = [
9
9
  "GEMMA_URL_INFORMATIEMODEL",
10
10
  "GEMMA_URL_TEMPLATE",
11
11
  "TIME_LEEWAY",
12
+ "JWT_EXPIRY",
12
13
  "JWT_SPECTACULAR_SETTINGS",
13
14
  "LINK_FETCHER",
14
15
  "NOTIFICATIONS_DISABLED",
@@ -97,4 +98,6 @@ COMMON_SPEC = f"https://raw.githubusercontent.com/{vng_repo}/feature/{vng_branch
97
98
 
98
99
  TIME_LEEWAY = 0 # default in PyJWT
99
100
 
101
+ JWT_EXPIRY = 3600
102
+
100
103
  COMMONGROUND_API_COMMON_GET_DOMAIN = "vng_api_common.utils.get_site_domain"
@@ -26,6 +26,8 @@ class AuthCheckMixin:
26
26
  request_kwargs = request_kwargs or {}
27
27
 
28
28
  with self.subTest(case="JWT missing"):
29
+ self.client.credentials() # explicity clear credentials
30
+
29
31
  response = do_request(url, **request_kwargs)
30
32
 
31
33
  self.assertEqual(