Skip to content

Commit 4a36853

Browse files
fix: openid4vci par endpoint
- fix mandatory for scope and authorization_details
1 parent 69a604b commit 4a36853

3 files changed

Lines changed: 42 additions & 56 deletions

File tree

pyeudiw/openid4vci/models/par_request.py

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from typing import List
2+
from typing import List, Optional
33
from urllib.parse import urlparse
44

55
from pydantic import model_validator
@@ -28,8 +28,8 @@ class ParRequest(OpenId4VciBaseModel):
2828
state: str = None
2929
code_challenge: str = None
3030
code_challenge_method: str = None
31-
scope: str = None
32-
authorization_details: List[AuthorizationDetail] = None
31+
scope: Optional[str] = None
32+
authorization_details: Optional[List[AuthorizationDetail]] = None
3333
redirect_uri: str = None
3434
jti: str = None
3535
issuer_state: str = None
@@ -61,27 +61,31 @@ def check_par_request(self) -> "ParRequest":
6161
self.validate_code_challenge(endpoint)
6262
self.validate_code_challenge_method(config.code_challenge_methods_supported, endpoint)
6363
self.validate_scope(config.scopes_supported, endpoint)
64+
self.validate_authorization_details(endpoint)
65+
if not self.scope and (not self.authorization_details or len(self.authorization_details) == 0):
66+
raise InvalidRequestException("Missing `scope` and `authorization_details` in `par` endpoint")
67+
6468
self.validate_redirect_uri(endpoint)
6569
self.validate_jti(endpoint)
66-
self.validate_authorization_details(endpoint)
6770
return self
6871

6972
def validate_authorization_details(self, endpoint: str):
70-
self.check_missing_parameter(self.authorization_details, "authorization_details", endpoint)
71-
for ad in self.authorization_details:
72-
AuthorizationDetail.model_validate(ad, context = {
73-
CONFIG_CTX: self.get_config(),
74-
})
73+
if self.authorization_details:
74+
for ad in self.authorization_details:
75+
AuthorizationDetail.model_validate(ad, context = {
76+
CONFIG_CTX: self.get_config(),
77+
ENDPOINT_CTX: endpoint
78+
})
7579

7680

7781
def validate_scope(self, scopes_supported: list[str], endpoint: str):
7882
self.scope = self.strip(self.scope)
79-
self.check_missing_parameter(self.scope, "scope", endpoint)
80-
scopes = self.scope.split(" ")
81-
for s in scopes:
82-
if s not in scopes_supported:
83-
logger.error(f"invalid scope value '{s}' in `{endpoint}` endpoint")
84-
raise InvalidRequestException("invalid `scope` parameter")
83+
if self.scope:
84+
scopes = self.scope.split(" ")
85+
for s in scopes:
86+
if s not in scopes_supported:
87+
logger.error(f"invalid scope value '{s}' in `{endpoint}` endpoint")
88+
raise InvalidRequestException("invalid `scope` parameter")
8589

8690

8791
def validate_code_challenge(self, endpoint: str):

pyeudiw/tests/openid4vci/endpoints/test_pushed_authorization_request_endpoint.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -199,13 +199,6 @@ def _mock_request_deserialized(overrides=None):
199199
(_mock_request_deserialized({"code_challenge_method": " "}), "missing `code_challenge_method` parameter"),
200200
(_mock_request_deserialized({"code_challenge_method": REMOVE}), "missing `code_challenge_method` parameter"),
201201
(_mock_request_deserialized({"code_challenge_method": "code_challenge_method"}), "invalid `code_challenge_method` parameter"),
202-
# invalid scope
203-
(_mock_request_deserialized({"scope": ""}), "missing `scope` parameter"),
204-
(_mock_request_deserialized({"scope": None}), "invalid `scope` parameter"),
205-
(_mock_request_deserialized({"scope": " "}), "missing `scope` parameter"),
206-
(_mock_request_deserialized({"scope": REMOVE}), "missing `scope` parameter"),
207-
(_mock_request_deserialized({"scope": "scope_invalid"}), "invalid `scope` parameter"),
208-
(_mock_request_deserialized({"scope": "scope1 scope_invalid"}), "invalid `scope` parameter"),
209202
# invalid redirect_uri
210203
(_mock_request_deserialized({"redirect_uri": ""}), "missing `redirect_uri` parameter"),
211204
(_mock_request_deserialized({"redirect_uri": None}), "invalid `redirect_uri` parameter"),
@@ -222,12 +215,12 @@ def _mock_request_deserialized(overrides=None):
222215
(_mock_request_deserialized({"jti": REMOVE}), "missing `jti` parameter"),
223216
(_mock_request_deserialized({"jti": "invalid_jti"}), "invalid `jti` parameter"),
224217
(_mock_request_deserialized({"jti": "urn:example:wallet:12345"}), "invalid `jti` parameter"),
225-
# invalid authorization_details
226-
(_mock_request_deserialized({"authorization_details": ""}), "invalid `authorization_details` parameter"),
227-
(_mock_request_deserialized({"authorization_details": None}), "invalid `authorization_details` parameter"),
228-
(_mock_request_deserialized({"authorization_details": " "}), "invalid `authorization_details` parameter"),
229-
(_mock_request_deserialized({"authorization_details": REMOVE}), "missing `authorization_details` parameter"),
230-
(_mock_request_deserialized({"authorization_details": []}), "missing `authorization_details` parameter"),
218+
# invalid authorization_details and scope
219+
(_mock_request_deserialized({"scope": "","authorization_details": ""}), "invalid `authorization_details` parameter"),
220+
(_mock_request_deserialized({"scope": None,"authorization_details": None}), "Missing `scope` and `authorization_details` in `par` endpoint"),
221+
(_mock_request_deserialized({"scope": " ","authorization_details": " "}), "invalid `authorization_details` parameter"),
222+
(_mock_request_deserialized({"scope": REMOVE,"authorization_details": REMOVE}), "Missing `scope` and `authorization_details` in `par` endpoint"),
223+
(_mock_request_deserialized({"scope": None,"authorization_details": []}), "Missing `scope` and `authorization_details` in `par` endpoint"),
231224
(_mock_request_deserialized({"authorization_details": [{"type": OPEN_ID_CREDENTIAL_TYPE}]}), "missing `authorization_details.credential_configuration_id` parameter"),
232225
(_mock_request_deserialized({"authorization_details": [{"credential_configuration_id": "dc_sd_jwt_EuropeanDisabilityCard"}]}), "missing `authorization_details.type` parameter"),
233226
(_mock_request_deserialized({"authorization_details": [{"type": OPEN_ID_CREDENTIAL_TYPE, "credential_configuration_id": "invalid_credential_configuration_id"}]}), "invalid `authorization_details.credential_configuration_id` parameter"),

pyeudiw/tests/openid4vci/models/test_par_request.py

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -284,27 +284,6 @@ def test_invalid_code_challenge_method(value):
284284
with pytest.raises(InvalidRequestException, match="invalid `code_challenge_method` parameter"):
285285
ParRequest.model_validate(payload, context=get_valid_context())
286286

287-
@pytest.mark.parametrize("value", ["", " ", None])
288-
def test_empty_or_missing_scope(value):
289-
now = int(datetime.datetime.now(datetime.timezone.utc).timestamp())
290-
payload = {
291-
"iss": "client-123",
292-
"aud": "entity-123",
293-
"state": "A" * 32,
294-
"client_id": "client-123",
295-
"iat": now + 29,
296-
"exp": now +30,
297-
"response_type": "code",
298-
"response_mode": "query",
299-
"code_challenge": "code_challenge_test",
300-
"code_challenge_method": "S256",
301-
}
302-
if value is not None:
303-
payload["scope"] = value
304-
305-
with pytest.raises(InvalidRequestException, match="missing `scope` parameter"):
306-
ParRequest.model_validate(payload, context=get_valid_context())
307-
308287
@pytest.mark.parametrize("value", ["test_0", " test_1", "test_2", " test_3 ", "scope1, pippo"])
309288
def test_invalid_code_challenge_method(value):
310289
now = int(datetime.datetime.now(datetime.timezone.utc).timestamp())
@@ -325,28 +304,38 @@ def test_invalid_code_challenge_method(value):
325304
with pytest.raises(InvalidRequestException, match="invalid `scope` parameter"):
326305
ParRequest.model_validate(payload, context=get_valid_context())
327306

328-
@pytest.mark.parametrize("value", [None, []])
329-
def test_empty_or_missing_authorization_details(value):
307+
308+
@pytest.mark.parametrize("authorization_details, scope", [
309+
([], None),
310+
(None, None),
311+
(None, ""),
312+
(None, " "),
313+
([], ""),
314+
([], " "),
315+
])
316+
def test_empty_or_missing_authorization_details(authorization_details, scope):
330317
now = int(datetime.datetime.now(datetime.timezone.utc).timestamp())
331318
payload = {
332319
"iss": "client-123",
333320
"aud": "entity-123",
334321
"state": "A" * 32,
335322
"client_id": "client-123",
336323
"iat": now + 29,
337-
"exp": now +30,
324+
"exp": now + 30,
338325
"response_type": "code",
339326
"response_mode": "query",
340327
"code_challenge": "code_challenge_test",
341328
"code_challenge_method": "S256",
342-
"scope": "scope1",
343329
"redirect_uri": "https://client.example.org/cb",
344330
"jti": "client-123" + str(uuid4())
345331
}
346-
if value is not None:
347-
payload["authorization_details"] = value
348332

349-
with pytest.raises(InvalidRequestException, match="missing `authorization_details` parameter"):
333+
if authorization_details is not None:
334+
payload["authorization_details"] = authorization_details
335+
if scope is not None:
336+
payload["scope"] = scope
337+
338+
with pytest.raises(InvalidRequestException, match="Missing `scope` and `authorization_details` in `par` endpoint"):
350339
ParRequest.model_validate(payload, context=get_valid_context())
351340

352341
@pytest.mark.parametrize("value", ["", " ", None])

0 commit comments

Comments
 (0)