rp_python_sdk 0.7.1__tar.gz → 0.10.0__tar.gz

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,3 +1,22 @@
1
+ Metadata-Version: 2.1
2
+ Name: rp_python_sdk
3
+ Version: 0.10.0
4
+ Summary: Python SDK for Relying Parties to enable simple integration with the Digital Identity ecosystem
5
+ License: Proprietary
6
+ Author: Erik Pragt
7
+ Author-email: erik.pragt@connectid.com.au
8
+ Requires-Python: >=3.12,<4.0
9
+ Classifier: License :: Other/Proprietary License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Dist: authlib (>=1.3.2,<2.0.0)
14
+ Requires-Dist: cffi (>=1.17.1,<2.0.0)
15
+ Requires-Dist: cryptography (>=43.0.1,<44.0.0)
16
+ Requires-Dist: joserfc (>=1.0.0,<2.0.0)
17
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
18
+ Description-Content-Type: text/markdown
19
+
1
20
  # Python SDK for Relying Parties
2
21
 
3
22
  [![Build Python SDK](https://github.com/connectid-tools/rp-python-sdk/actions/workflows/build.yml/badge.svg)](https://github.com/connectid-tools/rp-python-sdk/actions/workflows/build.yml)
@@ -407,6 +426,29 @@ poetry publish
407
426
 
408
427
  ## Release Notes
409
428
 
429
+ ### 0.10.0 (Oct 24, 2024)
430
+ - JWT signature verification for id_token in the retrieve_tokens step.
431
+
432
+ ### 0.9.0 (Oct 18, 2024)
433
+ - Added support for Python 3.13
434
+ - Added support for `payment` ApiResources
435
+ - Updated all dependencies
436
+
437
+ ### 0.8.0 (Sep 3, 2024)
438
+ - Breaking change: When `essential_claims` was empty it used the `default_claims` list. That logic has been removed.
439
+
440
+ ```python
441
+ def send_pushed_authorisation_request(config: SdkConfig,
442
+ authorisation_server_id: str,
443
+ essential_claims: set[str],
444
+ voluntary_claims: set[str],
445
+ purpose: str) -> PARResponse:
446
+ # this has been removed
447
+ if len(essential_claims) == 0:
448
+ essential_claims = default_claims
449
+ ...
450
+ ```
451
+
410
452
  ### 0.7.1 (June 28, 2024)
411
453
  - Made `scope` field optional in accordance with rfc-6749.
412
454
 
@@ -429,4 +471,5 @@ poetry publish
429
471
  - Implemented all endpoints
430
472
 
431
473
  ### 0.1.0 (March 27, 2024)
432
- - Initial version
474
+ - Initial version
475
+
@@ -1,19 +1,3 @@
1
- Metadata-Version: 2.1
2
- Name: rp_python_sdk
3
- Version: 0.7.1
4
- Summary: Python SDK for Relying Parties to enable simple integration with the Digital Identity ecosystem
5
- License: Proprietary
6
- Author: Erik Pragt
7
- Author-email: erik.pragt@connectid.com.au
8
- Requires-Python: >=3.12,<4.0
9
- Classifier: License :: Other/Proprietary License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.12
12
- Requires-Dist: authlib (>=1.3.0,<2.0.0)
13
- Requires-Dist: dataclass-wizard (>=0.22.3,<0.23.0)
14
- Requires-Dist: requests (>=2.31.0,<3.0.0)
15
- Description-Content-Type: text/markdown
16
-
17
1
  # Python SDK for Relying Parties
18
2
 
19
3
  [![Build Python SDK](https://github.com/connectid-tools/rp-python-sdk/actions/workflows/build.yml/badge.svg)](https://github.com/connectid-tools/rp-python-sdk/actions/workflows/build.yml)
@@ -423,6 +407,29 @@ poetry publish
423
407
 
424
408
  ## Release Notes
425
409
 
410
+ ### 0.10.0 (Oct 24, 2024)
411
+ - JWT signature verification for id_token in the retrieve_tokens step.
412
+
413
+ ### 0.9.0 (Oct 18, 2024)
414
+ - Added support for Python 3.13
415
+ - Added support for `payment` ApiResources
416
+ - Updated all dependencies
417
+
418
+ ### 0.8.0 (Sep 3, 2024)
419
+ - Breaking change: When `essential_claims` was empty it used the `default_claims` list. That logic has been removed.
420
+
421
+ ```python
422
+ def send_pushed_authorisation_request(config: SdkConfig,
423
+ authorisation_server_id: str,
424
+ essential_claims: set[str],
425
+ voluntary_claims: set[str],
426
+ purpose: str) -> PARResponse:
427
+ # this has been removed
428
+ if len(essential_claims) == 0:
429
+ essential_claims = default_claims
430
+ ...
431
+ ```
432
+
426
433
  ### 0.7.1 (June 28, 2024)
427
434
  - Made `scope` field optional in accordance with rfc-6749.
428
435
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "rp_python_sdk"
3
- version = "0.7.1"
3
+ version = "0.10.0"
4
4
  description = "Python SDK for Relying Parties to enable simple integration with the Digital Identity ecosystem"
5
5
  authors = ["Erik Pragt <erik.pragt@connectid.com.au>"]
6
6
  license = "Proprietary"
@@ -8,20 +8,23 @@ readme = "README.md"
8
8
 
9
9
  [tool.poetry.dependencies]
10
10
  python = "^3.12"
11
- dataclass-wizard = "^0.22.3"
12
- requests = "^2.31.0"
13
- authlib = "^1.3.0"
11
+ requests = "^2.32.3"
12
+ authlib = "^1.3.2"
13
+ cryptography = "^43.0.1"
14
+ joserfc = "^1.0.0"
15
+ cffi = "^1.17.1"
14
16
 
15
17
  [tool.poetry.group.test.dependencies]
16
- pytest = "^8.0.0"
17
- httpx = "^0.27.0"
18
+ pytest = "^8.3.3"
19
+ httpx = "^0.27.2"
18
20
 
19
21
  [tool.poetry.group.dev.dependencies]
20
- black = "^24.3.0"
21
- pre-commit = "^3.5.0"
22
- mypy = "^1.9.0"
22
+ black = "^24.10.0"
23
+ pre-commit = "^3.8.0"
24
+ mypy = "^1.13.0"
23
25
  requests-mock = "^1.12.1"
24
26
  freezegun = "^1.4.0"
27
+ flake8 = "^7.1.1"
25
28
 
26
29
  [build-system]
27
30
  requires = ["poetry-core"]
@@ -1,7 +1,9 @@
1
1
  from datetime import date
2
2
 
3
3
  import requests
4
+ import json
4
5
 
6
+ from rp_python_sdk.endpoints.util.mapping import from_dict
5
7
  from rp_python_sdk.filters.participant_filters import remove_out_of_date_certifications, \
6
8
  remove_unofficial_certifications, filter_for_fallback_identity_service_providers, \
7
9
  remove_participants_without_auth_servers, remove_fallback_identity_service_provider, \
@@ -65,7 +67,8 @@ def _retrieve_full_participants_list(config: SdkConfig) -> list[Participant]:
65
67
  raise RelyingPartyClientSdkException(
66
68
  f"Call to identity provider endpoint failed with code: {response.status_code}")
67
69
 
68
- participant_list: list[Participant] = Participant.from_json(response.text)
70
+ # participant_list: list[Participant] = Participant.from_json(response.text)
71
+ participant_list: list[Participant] = from_dict(Participant, json.loads(response.text), pascal_case=True)
69
72
 
70
73
  logger.info(f"Retrieved identity providers, num orgs found: {len(participant_list)}")
71
74
  return participant_list
@@ -83,14 +86,14 @@ def get_auth_server_details(config: SdkConfig, authorisation_server_id: str) ->
83
86
 
84
87
 
85
88
  def get_issuer_metadata(authorisation_server: AuthorisationServer):
86
- auth_server_issuer = authorisation_server.open_id_discovery_document
89
+ auth_server_issuer = authorisation_server.open_i_d_discovery_document
87
90
 
88
91
  try:
89
92
  logger.info(f"Getting discovery document from {auth_server_issuer}")
90
93
  response = requests.get(auth_server_issuer)
91
94
  response.raise_for_status() # Raises an exception for 4XX or 5XX errors
92
95
 
93
- return IssuerMetadata.from_json(response.text)
96
+ return from_dict(IssuerMetadata, json.loads(response.text))
94
97
 
95
98
  except requests.exceptions.HTTPError as http_err:
96
99
  raise RelyingPartyClientSdkException(
@@ -16,9 +16,6 @@ from rp_python_sdk.relying_party_client_sdk_exception import RelyingPartyClientS
16
16
  from rp_python_sdk.sdk_config import SdkConfig
17
17
  from rp_python_sdk.setup_logger import logger
18
18
 
19
- default_claims = {"name", "given_name", "middle_name", "family_name", "phone_number", "email", "address", "birthdate",
20
- "txn"}
21
-
22
19
  extended_claims = {"over16", "over18", "over21", "over25", "over65", "beneficiary_account_au",
23
20
  "beneficiary_account_au_payid", "beneficiary_account_international"}
24
21
 
@@ -28,9 +25,6 @@ def send_pushed_authorisation_request(config: SdkConfig,
28
25
  essential_claims: set[str],
29
26
  voluntary_claims: set[str],
30
27
  purpose: str) -> PARResponse:
31
- if len(essential_claims) == 0:
32
- essential_claims = default_claims
33
-
34
28
  _check_valid_purpose(purpose)
35
29
 
36
30
  checked_essential_claims = _ensure_mandatory_claims_present(essential_claims, voluntary_claims)
@@ -117,10 +111,12 @@ def send_pushed_authorisation_request(config: SdkConfig,
117
111
  cert=(config.transport_pem, config.transport_key),
118
112
  verify=config.ca_pem)
119
113
 
120
- log_x_fapi_interaction_id_matches(authorisation_server, "send_pushed_authorisation_request", x_fapi_interaction_id, response)
114
+ log_x_fapi_interaction_id_matches(authorisation_server, "send_pushed_authorisation_request",
115
+ x_fapi_interaction_id, response)
121
116
 
122
117
  if not response.ok:
123
- raise RelyingPartyClientSdkException(f"Response code for PAR to {authorisation_server_id} not successful, was: {response.status_code} x-fapi-interaction-id: {x_fapi_interaction_id}")
118
+ raise RelyingPartyClientSdkException(f"Response code for PAR to {authorisation_server_id} not successful, "
119
+ f"was: {response.status_code} x-fapi-interaction-id: {x_fapi_interaction_id}")
124
120
 
125
121
  request_uri = response.json()['request_uri']
126
122
  logger.debug(f"PAR was sent, request_uri='{request_uri}'")
@@ -138,7 +134,8 @@ def _check_valid_purpose(purpose) -> None:
138
134
  contains_illegal_char = any(char in purpose for char in illegal_chars)
139
135
  if contains_illegal_char:
140
136
  raise RelyingPartyClientSdkException(
141
- f"Purpose cannot contain any of the following characters: {', '.join(illegal_chars)}, purpose supplied: [{purpose}]")
137
+ f"Purpose cannot contain any of the following characters: {', '.join(illegal_chars)}, "
138
+ f"purpose supplied: [{purpose}]")
142
139
 
143
140
 
144
141
  def _ensure_mandatory_claims_present(essential_claims: set[str], voluntary_claims: set[str]) -> set[str]:
@@ -5,12 +5,18 @@ import uuid
5
5
  from typing import Tuple, Any
6
6
 
7
7
  import requests
8
- from authlib.jose import JsonWebSignature
8
+ from joserfc import jws
9
+ from joserfc.jwk import RSAKey
10
+ from joserfc.jws import JWSRegistry
11
+ from authlib.jose import JsonWebToken
12
+ from authlib.jose.errors import BadSignatureError, DecodeError
13
+ from authlib.jose.util import extract_header
9
14
 
10
15
  from rp_python_sdk.endpoints.get_participants import get_issuer_metadata, \
11
16
  get_auth_server_details
12
17
  from rp_python_sdk.endpoints.user_info import call_user_info
13
18
  from rp_python_sdk.endpoints.util.fapi import create_x_fapi_interaction_id, log_x_fapi_interaction_id_matches
19
+ from rp_python_sdk.endpoints.util.mapping import from_dict
14
20
  from rp_python_sdk.model import CallbackBody, TokenSet, TokenInput, IssuerMetadata
15
21
  from rp_python_sdk.relying_party_client_sdk_exception import RelyingPartyClientSdkException
16
22
  from rp_python_sdk.sdk_config import SdkConfig
@@ -60,18 +66,19 @@ def retrieve_tokens(config: SdkConfig, authorisation_server_id: str, callback_bo
60
66
  'iat': iat,
61
67
  }
62
68
 
63
- jws = JsonWebSignature()
64
- header = {'alg': 'PS256', 'kid': config.signing_kid}
69
+ # See https://jose.authlib.org/en/guide/registry/
70
+ registry = JWSRegistry(algorithms=["PS256"])
65
71
 
66
- client_assertion_jwt = jws.serialize_compact(header, json.dumps(client_assertion_payload),
67
- config.signing_key)
72
+ key = RSAKey.import_key(config.signing_key)
73
+ header = {'alg': 'PS256', 'kid': config.signing_kid}
74
+ client_assertion_jwt = jws.serialize_compact(header, json.dumps(client_assertion_payload), key, registry = registry)
68
75
 
69
76
  # Data payload
70
77
  data = {
71
78
  "grant_type": "authorization_code",
72
79
  "code": callback_body.code,
73
80
  "redirect_uri": redirect_uri,
74
- "client_assertion": client_assertion_jwt.decode("utf-8"),
81
+ "client_assertion": client_assertion_jwt,
75
82
  "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
76
83
  "code_verifier": original_code_verifier
77
84
  }
@@ -94,12 +101,12 @@ def retrieve_tokens(config: SdkConfig, authorisation_server_id: str, callback_bo
94
101
  f"Response code for token endpoint to {token_endpoint} not successful, was: {response.status_code}, "
95
102
  f"x-fapi-interaction-id: {x_fapi_interaction_id}, response body: " + response.text)
96
103
 
97
- token_input = TokenInput.from_json(response.text)
104
+ token_input = from_dict(TokenInput, json.loads(response.text))
98
105
 
99
106
  jwks_response_body = _get_jwks_json(issuer_metadata, x_fapi_interaction_id)
100
107
  allowed_algs = issuer_metadata.id_token_signing_alg_values_supported
101
108
 
102
- header, payload = _validate(token_input, jwks_response_body, allowed_algs, nonce, client_id, callback_body)
109
+ header, payload = _validate(token_input, jwks_response_body, allowed_algs, nonce, client_id, callback_body, issuer_metadata.issuer)
103
110
 
104
111
  token_set = TokenSet(token_input, payload, x_fapi_interaction_id)
105
112
 
@@ -115,7 +122,7 @@ def retrieve_tokens(config: SdkConfig, authorisation_server_id: str, callback_bo
115
122
 
116
123
  def _validate(token_input: TokenInput, jwks_response_body: dict[str, Any], allowed_algs: list[str],
117
124
  nonce: str, client_id: str,
118
- callback_body: CallbackBody) -> Tuple[dict[str, Any], dict[str, Any]]:
125
+ callback_body: CallbackBody, issuer: str) -> Tuple[dict[str, Any], dict[str, Any]]:
119
126
  """
120
127
  Decode and parse an ID token into JSON objects for header and payload.
121
128
 
@@ -125,18 +132,18 @@ def _validate(token_input: TokenInput, jwks_response_body: dict[str, Any], allow
125
132
  :param nonce
126
133
  :param client_id
127
134
  :param callback_body
135
+ :param issuer
128
136
  :return: two dictionaries for header and payload
129
137
  """
130
138
  id_token = token_input.id_token
131
- header_base64 = id_token.split('.')[0]
132
- header = _decode_base64(header_base64)
133
- header_json = json.loads(header)
139
+ header_json = _get_unverified_header(id_token)
134
140
 
135
141
  payload_base64 = id_token.split('.')[1]
136
142
  payload = _decode_base64(payload_base64)
137
143
  payload_json = json.loads(payload)
138
144
 
139
- _validate_header(header_json, jwks_response_body, allowed_algs, id_token)
145
+ _validate_header(header_json, allowed_algs)
146
+ _validate_signature(issuer, client_id, allowed_algs, id_token, jwks_response_body)
140
147
  _validate_payload(payload_json, nonce, callback_body, client_id)
141
148
 
142
149
  return header_json, payload_json
@@ -189,6 +196,8 @@ def _validate_payload(json_payload: dict[str, Any], nonce: str, callback_body: C
189
196
  if json_payload['aud'] != client_id:
190
197
  raise RelyingPartyClientSdkException('aud claim in token response does not match client ID')
191
198
 
199
+ def _get_unverified_header(id_token:str) -> Any:
200
+ return extract_header(id_token.split('.')[0].encode(), DecodeError)
192
201
 
193
202
  def _decode_base64(base64_str: str) -> str:
194
203
  """
@@ -200,51 +209,61 @@ def _decode_base64(base64_str: str) -> str:
200
209
  return decoded_bytes.decode('utf-8')
201
210
 
202
211
 
203
- def _validate_header(json_header, jwks, allowed_algs, id_token: str) -> None:
212
+ def _validate_header(json_header: dict, allowed_algs: list) -> None:
204
213
  if 'alg' not in json_header or json_header['alg'] == 'none':
205
214
  raise RelyingPartyClientSdkException("no alg value in the token response jwt header")
206
215
 
207
216
  if 'kid' not in json_header:
208
217
  raise RelyingPartyClientSdkException("no kid value in the token response jwt header")
209
218
 
210
- kid = json_header['kid']
211
- jwk = None
212
-
213
- for key_element in jwks.get('keys', []):
214
- if _matching_kid(kid, key_element) and _none_or_matching_alg(json_header, key_element):
215
- jwk = key_element
216
- break
217
-
218
- if jwk is None:
219
- raise RelyingPartyClientSdkException(
220
- f"kid provided does not match any key in the jwks for specified algorithm in "
221
- f"token header: {json_header['alg']}")
222
-
223
219
  found_alg = any(alg.lower() == json_header['alg'].lower() for alg in allowed_algs)
224
220
  if not found_alg:
225
221
  raise RelyingPartyClientSdkException(
226
- "alg value in id token header is not one of the supported algorithms listed on the well-known")
222
+ f"alg value in id token header is not one of the supported algorithms listed on the well-known. "
223
+ f"Provided alg {json_header['alg']}")
227
224
 
228
- # try:
229
- # validate_signature(token_input.get('id_token'), jwk)
230
- # except (JOSEError, ValueError) as e:
231
- # raise RelyingPartyClientSdkException(f"exception while validation jwt signature: {e}")
232
-
233
-
234
- def _validate_signature(self, id_token, jwk):
235
- # Implement JWT signature validation based on the jwk provided and the library you're using
236
- pass
237
-
238
-
239
- def _matching_kid(kid: str, key_element) -> bool:
240
- # Implement this to match the 'kid' from the JWT with one from the JWKS
225
+ def _find_key_in_jwks(kid: str, token_alg: str, jwks: dict) -> Any:
226
+ for key_element in jwks.get('keys', []):
227
+ if _matching_kid(kid, key_element) and _matching_alg(token_alg, key_element) and _matching_use(key_element):
228
+ return key_element
229
+ return None
230
+
231
+ def _validate_signature(issuer: str, audience: str, allowed_alg: list, id_token: str, jwks: dict) -> None:
232
+ try:
233
+ # Pub key extraction
234
+ header = _get_unverified_header(id_token)
235
+ kid = header['kid']
236
+ alg = header['alg']
237
+ jwk = _find_key_in_jwks(kid, alg, jwks)
238
+
239
+ if jwk is None:
240
+ raise RelyingPartyClientSdkException(
241
+ f"kid and alg provided does not match any key in the jwks for specified algorithm in "
242
+ f"token header, kid: {kid}, alg: {alg}")
243
+
244
+ # Decode JWT with signature verification
245
+ claims_options = {
246
+ "iss": {"essential": True, "value": issuer},
247
+ "aud": {"essential": True, "value": audience}
248
+ }
249
+ jwt = JsonWebToken(allowed_alg)
250
+ claims = jwt.decode(id_token, key=jwk, claims_options=claims_options)
251
+ claims.validate()
252
+ logger.info("JWT signature is valid")
253
+ except BadSignatureError as e:
254
+ raise RelyingPartyClientSdkException(f"Invalid JWT signature: {e}, token data: {e.result}")
255
+ except Exception as e:
256
+ logger.debug(f"Invalid JWT token: {id_token}")
257
+ raise RelyingPartyClientSdkException(f"Invalid JWT signature: {e}")
258
+
259
+ def _matching_use(key_element: dict) -> bool:
260
+ return key_element.get('use') == "sig"
261
+
262
+ def _matching_kid(kid: str, key_element: dict) -> bool:
241
263
  return key_element.get('kid') == kid
242
264
 
243
-
244
- def _none_or_matching_alg(json_header, key_element) -> bool:
245
- # Implement this to check if the 'alg' in JWT matches the one in the key element, if required
246
- return True
247
-
265
+ def _matching_alg(token_alg: str, key_element: dict) -> bool:
266
+ return key_element.get("alg") is None or key_element.get("alg") == token_alg
248
267
 
249
268
  def _get_jwks_json(issuer_metadata: IssuerMetadata, x_fapi_interaction_id: str) -> Any:
250
269
  headers = {
@@ -259,4 +278,4 @@ def _get_jwks_json(issuer_metadata: IssuerMetadata, x_fapi_interaction_id: str)
259
278
  f"Response code for JWKS to {issuer_metadata.jwks_uri} not successful, was: {response.status_code}, "
260
279
  f"x-fapi-interaction-id: {x_fapi_interaction_id}, response body: {response.text}")
261
280
 
262
- return response.json()
281
+ return response.json()
@@ -0,0 +1,54 @@
1
+ from typing import Type, TypeVar, Union, List, get_type_hints
2
+
3
+ T = TypeVar('T')
4
+
5
+
6
+ def snake_to_pascal(name: str) -> str:
7
+ """
8
+ Converts a snake_case string to PascalCase.
9
+ Example: organisation_id -> OrganisationId
10
+ """
11
+ return ''.join(word.capitalize() for word in name.split('_'))
12
+
13
+
14
+ def from_dict(data_class: Type[T], data: Union[dict, list], pascal_case=False) -> Union[T, List[T]]:
15
+ """
16
+ Recursively maps a dictionary or a list of dictionaries to a dataclass or a list of dataclasses.
17
+ Handles key transformations from snake_case to PascalCase, and resolves forward references.
18
+ """
19
+ if isinstance(data, list):
20
+ # Map each item in the list to the dataclass
21
+ return [from_dict(data_class, item, pascal_case) for item in data]
22
+
23
+ if not isinstance(data, dict):
24
+ raise ValueError(
25
+ f"Expected a dictionary to map to {data_class.__name__}, but got {type(data).__name__}: {data}")
26
+
27
+ # Get the fields of the dataclass, resolving forward references
28
+ fieldtypes = get_type_hints(data_class)
29
+
30
+ # Prepare the arguments for the dataclass constructor
31
+ data_class_kwargs = {}
32
+ for field, field_type in fieldtypes.items():
33
+ # Convert the snake_case field name to PascalCase to match the JSON key
34
+ json_field = field
35
+ if pascal_case:
36
+ json_field = snake_to_pascal(field)
37
+
38
+ # Fetch the corresponding value from the JSON data
39
+ field_value = data.get(json_field)
40
+
41
+ if isinstance(field_value, list):
42
+ inner_type = field_type.__args__[0]
43
+ data_class_kwargs[field] = [from_dict(inner_type, item, pascal_case) if isinstance(item, dict) else item for
44
+ item in field_value]
45
+ elif isinstance(field_value, dict):
46
+ data_class_kwargs[field] = from_dict(field_type, field_value, pascal_case)
47
+ else:
48
+ # Handle authorisation_servers case
49
+ if field == "authorisation_servers" and field_value is None:
50
+ data_class_kwargs[field] = []
51
+ else:
52
+ data_class_kwargs[field] = field_value
53
+
54
+ return data_class(**data_class_kwargs)
@@ -96,4 +96,3 @@ def filter_for_fallback_identity_service_providers(participants: List[Participan
96
96
  def to_date(date_string: str):
97
97
  date_format = "%d/%m/%Y"
98
98
  return datetime.strptime(date_string, date_format).date()
99
-
@@ -2,11 +2,8 @@ from dataclasses import dataclass
2
2
  from typing import Any, Dict, Optional
3
3
  from typing import List
4
4
 
5
- from dataclass_wizard import JSONWizard
6
-
7
-
8
5
  @dataclass
9
- class Participant(JSONWizard):
6
+ class Participant:
10
7
  organisation_id: str
11
8
  status: str
12
9
  organisation_name: str
@@ -29,7 +26,7 @@ class Participant(JSONWizard):
29
26
 
30
27
 
31
28
  @dataclass
32
- class AuthorisationServerCertification(JSONWizard):
29
+ class AuthorisationServerCertification:
33
30
  certification_start_date: str # date
34
31
  certification_expiration_date: str # date
35
32
  certification_id: str
@@ -42,31 +39,49 @@ class AuthorisationServerCertification(JSONWizard):
42
39
 
43
40
 
44
41
  @dataclass
45
- class OrgDomainClaim(JSONWizard):
42
+ class OrgDomainClaim:
46
43
  authorisation_domain_name: str
47
44
  authority_name: str
48
45
  registration_id: str
49
46
  status: str
50
47
 
48
+ @dataclass
49
+ class ApiDiscoveryEndpoint:
50
+ api_discovery_id: str
51
+ api_endpoint: str
52
+
53
+ @dataclass
54
+ class ApiResource:
55
+ api_resource_id: str
56
+ api_version: str
57
+ api_discovery_endpoints: List[ApiDiscoveryEndpoint]
58
+ family_complete: bool
59
+ api_certification_uri: Optional[str]
60
+ certification_status: Optional[str]
61
+ # should be a date
62
+ certification_start_date: str
63
+ # should be a date
64
+ certification_expiration_date: str
65
+ api_family_type: Optional[str]
51
66
 
52
67
  @dataclass
53
- class AuthorisationServer(JSONWizard):
68
+ class AuthorisationServer:
54
69
  authorisation_server_id: str
55
- api_resources: List[str]
70
+ api_resources: List[ApiResource]
56
71
  authorisation_server_certifications: List[AuthorisationServerCertification]
57
72
  customer_friendly_description: str
58
73
  customer_friendly_logo_uri: str
59
74
  customer_friendly_name: str
60
75
  developer_portal_uri: str
61
76
  terms_of_service_uri: str
62
- open_id_discovery_document: str
77
+ open_i_d_discovery_document: str
63
78
  issuer: str
64
79
  payload_signing_cert_location_uri: str
65
80
  parent_authorisation_server_id: str
66
81
 
67
82
 
68
83
  @dataclass
69
- class OrgDomainRoleClaim(JSONWizard):
84
+ class OrgDomainRoleClaim:
70
85
  status: str
71
86
  authorisation_domain: str
72
87
  role: str
@@ -97,7 +112,7 @@ class TokenSet:
97
112
 
98
113
 
99
114
  @dataclass
100
- class TokenInput(JSONWizard):
115
+ class TokenInput:
101
116
  access_token: str
102
117
  id_token: str
103
118
  token_type: str
@@ -106,7 +121,7 @@ class TokenInput(JSONWizard):
106
121
 
107
122
 
108
123
  @dataclass
109
- class MtlsEndpointAliases(JSONWizard):
124
+ class MtlsEndpointAliases:
110
125
  token_endpoint: Optional[str] = None
111
126
  pushed_authorization_request_endpoint: Optional[str] = None
112
127
  authorization_endpoint: Optional[str] = None
@@ -114,7 +129,7 @@ class MtlsEndpointAliases(JSONWizard):
114
129
 
115
130
 
116
131
  @dataclass
117
- class IssuerMetadata(JSONWizard):
132
+ class IssuerMetadata:
118
133
  issuer: str
119
134
  jwks_uri: str
120
135
  id_token_signing_alg_values_supported: List[str]
@@ -126,20 +141,20 @@ class IssuerMetadata(JSONWizard):
126
141
 
127
142
  def get_preferred_token_endpoint(self) -> str:
128
143
  return self.mtls_endpoint_aliases.token_endpoint \
129
- if self.mtls_endpoint_aliases.token_endpoint \
144
+ if self.mtls_endpoint_aliases and self.mtls_endpoint_aliases.token_endpoint \
130
145
  else self.token_endpoint
131
146
 
132
147
  def get_preferred_pushed_authorization_request_endpoint(self) -> str:
133
148
  return self.mtls_endpoint_aliases.pushed_authorization_request_endpoint \
134
- if self.mtls_endpoint_aliases.pushed_authorization_request_endpoint \
149
+ if self.mtls_endpoint_aliases and self.mtls_endpoint_aliases.pushed_authorization_request_endpoint \
135
150
  else self.pushed_authorization_request_endpoint
136
151
 
137
152
  def get_preferred_authorization_endpoint(self) -> str:
138
153
  return self.mtls_endpoint_aliases.authorization_endpoint \
139
- if self.mtls_endpoint_aliases.authorization_endpoint \
154
+ if self.mtls_endpoint_aliases and self.mtls_endpoint_aliases.authorization_endpoint \
140
155
  else self.authorization_endpoint
141
156
 
142
157
  def get_preferred_userinfo_endpoint(self) -> str:
143
158
  return self.mtls_endpoint_aliases.userinfo_endpoint \
144
- if self.mtls_endpoint_aliases.userinfo_endpoint \
159
+ if self.mtls_endpoint_aliases and self.mtls_endpoint_aliases.userinfo_endpoint \
145
160
  else self.userinfo_endpoint
@@ -14,7 +14,11 @@ class RelyingPartyClientSdk:
14
14
  self.config = config
15
15
 
16
16
  if config.custom_config.enable_auto_compliance_verification:
17
- logger.info(f"Auto Compliance Verification mode enabled, printing config information: {self.config}")
17
+ logger.info(f"Auto Compliance Verification mode enabled, printing config information: "
18
+ f"client_id={self.config.client_id}, "
19
+ f"signing_kid={self.config.signing_kid}, "
20
+ f"application_redirect_uri={self.config.application_redirect_uri}, "
21
+ f"registry_participants_uri={self.config.registry_participants_uri}")
18
22
 
19
23
  def get_participants(self) -> list[Participant]:
20
24
  return get_participants(self.config)