From dec981c48a9ba2fa3c83cd29a9d28c7a71e17574 Mon Sep 17 00:00:00 2001 From: Giuseppe De Marco Date: Tue, 2 Jan 2024 15:29:03 +0100 Subject: [PATCH 1/8] chore: some additional logs --- pyeudiw/federation/__init__.py | 7 +++-- pyeudiw/federation/statements.py | 15 ++-------- pyeudiw/federation/trust_chain_validator.py | 26 +++++++++++++---- pyeudiw/oauth2/dpop/__init__.py | 2 ++ pyeudiw/openid4vp/direct_post_response.py | 32 ++++++++++++++------- pyeudiw/satosa/dpop.py | 13 ++++++--- pyeudiw/satosa/trust.py | 29 ++++++++++--------- pyeudiw/x509/verify.py | 28 ++++++++++++------ 8 files changed, 94 insertions(+), 58 deletions(-) diff --git a/pyeudiw/federation/__init__.py b/pyeudiw/federation/__init__.py index afdc418d..e6c380cb 100644 --- a/pyeudiw/federation/__init__.py +++ b/pyeudiw/federation/__init__.py @@ -1,10 +1,11 @@ from pyeudiw.federation.schemas.entity_configuration import EntityStatementPayload, EntityConfigurationPayload + def is_es(payload: dict) -> bool: """ - Determines if payload dict is an Entity Statement + Determines if payload dict is a Subordinate Entity Statement - :param payload: the object to determine if is an Entity Statement + :param payload: the object to determine if is a Subordinate Entity Statement :type payload: dict :returns: True if is an Entity Statement and False otherwise @@ -34,4 +35,4 @@ def is_ec(payload: dict) -> bool: EntityConfigurationPayload(**payload) return True except Exception as e: - return False \ No newline at end of file + return False diff --git a/pyeudiw/federation/statements.py b/pyeudiw/federation/statements.py index b74012fa..afe9f27a 100644 --- a/pyeudiw/federation/statements.py +++ b/pyeudiw/federation/statements.py @@ -1,5 +1,3 @@ -from __future__ import annotations - from copy import deepcopy from pyeudiw.federation.exceptions import ( UnknownKid, @@ -13,22 +11,15 @@ EntityConfigurationHeader, EntityStatementPayload ) +from pydantic import ValidationError from pyeudiw.jwt.utils import decode_jwt_payload, decode_jwt_header from pyeudiw.jwt import JWSHelper -from pyeudiw.tools.utils import get_http_url -from pydantic import ValidationError - from pyeudiw.jwk import find_jwk +from pyeudiw.tools.utils import get_http_url import json import logging -try: - pass -except ImportError: # pragma: no cover - pass - - OIDCFED_FEDERATION_WELLKNOWN_URL = ".well-known/openid-federation" logger = logging.getLogger(__name__) @@ -67,7 +58,6 @@ def get_federation_jwks(jwt_payload: dict) -> list[dict]: jwks = jwt_payload.get("jwks", {}) keys = jwks.get("keys", []) - return keys @@ -87,7 +77,6 @@ def get_entity_statements(urls: list[str] | str, httpc_params: dict, http_async: """ urls = urls if isinstance(urls, list) else [urls] - for url in urls: logger.debug(f"Starting Entity Statement Request to {url}") diff --git a/pyeudiw/federation/trust_chain_validator.py b/pyeudiw/federation/trust_chain_validator.py index 6426d9b8..6b6026b6 100644 --- a/pyeudiw/federation/trust_chain_validator.py +++ b/pyeudiw/federation/trust_chain_validator.py @@ -132,18 +132,27 @@ def validate(self) -> bool: ) if not ta_jwk: + logger.error( + f"Trust chain validation error: TA jwks not found." + ) return False # Validate the last statement with ta_jwk jwsh = JWSHelper(ta_jwk) if not jwsh.verify(last_element): + logger.error( + f"Trust chain signature validation error: {last_element} using {ta_jwk}" + ) return False # then go ahead with other checks self.exp = es_payload["exp"] if self._check_expired(self.exp): + logger.error( + f"Trust chain validation error, statement expired: {es_payload}" + ) return False fed_jwks = es_payload["jwks"]["keys"] @@ -160,10 +169,16 @@ def validate(self) -> bool: st_header.get("kid", None), fed_jwks ) except (KidNotFoundError, InvalidKid): + logger.error( + f"Trust chain validation KidNotFoundError: {st_header} not in {fed_jwks}" + ) return False jwsh = JWSHelper(jwk) if not jwsh.verify(st): + logger.error( + f"Trust chain signature validation error: {st} using {jwk}" + ) return False else: fed_jwks = st_payload["jwks"]["keys"] @@ -184,8 +199,9 @@ def _retrieve_ec(self, iss: str) -> str: """ jwt = get_entity_configurations(iss, self.httpc_params) if not jwt: - raise HttpError( - f"Cannot get the Entity Configuration from {iss}") + _msg = f"Cannot get the Entity Configuration from {iss}" + logger.error(_msg) + raise HttpError(_msg) # is something weird these will raise their Exceptions return jwt[0] @@ -204,9 +220,9 @@ def _retrieve_es(self, download_url: str, iss: str) -> str: """ jwt = get_entity_statements(download_url, self.httpc_params) if not jwt: - logger.warning( - f"Cannot fast refresh Entity Statement {iss}" - ) + _msg = f"Cannot fast refresh Entity Statement {iss}" + logger.warning(_msg) + raise HttpError(_msg) if isinstance(jwt, list) and jwt: return jwt[0] return jwt diff --git a/pyeudiw/oauth2/dpop/__init__.py b/pyeudiw/oauth2/dpop/__init__.py index 986efd44..edfdffdf 100644 --- a/pyeudiw/oauth2/dpop/__init__.py +++ b/pyeudiw/oauth2/dpop/__init__.py @@ -101,6 +101,8 @@ def __init__( "Jwk validation error, " f"{e.__class__.__name__}: {e}" ) + raise ValueError("JWK schema validation error during DPoP init") + # If the jwt is invalid, this will raise an exception try: decode_jwt_header(http_header_dpop) diff --git a/pyeudiw/openid4vp/direct_post_response.py b/pyeudiw/openid4vp/direct_post_response.py index a51ad414..97d1e3a7 100644 --- a/pyeudiw/openid4vp/direct_post_response.py +++ b/pyeudiw/openid4vp/direct_post_response.py @@ -12,6 +12,7 @@ from pyeudiw.openid4vp.vp import Vp from pydantic import ValidationError + class DirectPostResponse: """ Helper class for generate Direct Post Response. @@ -90,8 +91,10 @@ def _validate_vp(self, vp: dict) -> bool: ) VPTokenPayload(**vp.payload) VPTokenHeader(**vp.headers) - except ValidationError: - return False + except ValidationError as e: + raise InvalidVPToken( + f"VP is not valid, {e}: {vp.headers}.{vp.payload}" + ) return True @@ -102,12 +105,19 @@ def validate(self) -> bool: :returns: True if all VP are valid, False otherwhise. :rtype: bool """ - + all_valid = None for vp in self.get_presentation_vps(): - if not self._validate_vp(vp): - return False - - return True + try: + self._validate_vp(vp) + if all_valid == None: + all_valid = True + except Exception as e: + logger.error( + + ) + all_valid = False + + return all_valid def get_presentation_vps(self) -> list[Vp]: """ @@ -125,16 +135,18 @@ def get_presentation_vps(self) -> list[Vp]: vps = [_vps] if isinstance(_vps, str) else _vps if not vps: - raise VPNotFound(f"Vps are empty for response with nonce \"{self.nonce}\"") + raise VPNotFound( + f'Vps are empty for response with nonce "{self.nonce}"' + ) for vp in vps: + # TODO - add an exception handling here _vp = Vp(vp) self._vps.append(_vp) cred_iss = _vp.credential_payload['iss'] if not self.credentials_by_issuer.get(cred_iss, None): self.credentials_by_issuer[cred_iss] = [] - self.credentials_by_issuer[cred_iss].append(_vp.payload['vp']) return self._vps @@ -151,4 +163,4 @@ def payload(self) -> dict: """Returns the decoded payload of presentation""" if not self._payload: self._decode_payload() - return self._payload \ No newline at end of file + return self._payload diff --git a/pyeudiw/satosa/dpop.py b/pyeudiw/satosa/dpop.py index 778b0696..9e4da208 100644 --- a/pyeudiw/satosa/dpop.py +++ b/pyeudiw/satosa/dpop.py @@ -12,6 +12,7 @@ from pyeudiw.tools.base_logger import BaseLogger from .base_http_error_handler import BaseHTTPErrorHandler + class BackendDPoP(BaseHTTPErrorHandler, BaseLogger): """ Backend DPoP class. @@ -50,9 +51,13 @@ def _request_endpoint_dpop(self, context: Context, *args) -> Union[JsonResponse, try: WalletInstanceAttestationPayload(**wia) except ValidationError as e: - self._log_warning(context, message=f"[FOUND WIA] Invalid WIA: {wia}! \nValidation error: {e}") + _msg = f"[FOUND WIA] Invalid WIA: {wia}! \nValidation error: {e}" + self._log_warning(context, message=_msg) + # return self._handle_401(context, _msg, e) except Exception as e: - self._log_warning(context, message=f"[FOUND WIA] Invalid WIA: {wia}! \nUnexpected error: {e}") + _msg = f"[FOUND WIA] Invalid WIA: {wia}! \nUnexpected error: {e}" + self._log_warning(context, message=_msg) + # return self._handle_401(context, _msg, e) try: self._validate_trust(context, dpop_jws) @@ -84,7 +89,7 @@ def _request_endpoint_dpop(self, context: Context, *args) -> Union[JsonResponse, else: _msg = ( - "The Wallet Instance doesn't provide a valid Wallet Instance Attestation " + "The Wallet Instance doesn't provide a valid Wallet Attestation " "a default set of capabilities and a low security level are applied." ) - self._log_warning(context, message=_msg) \ No newline at end of file + self._log_warning(context, message=_msg) diff --git a/pyeudiw/satosa/trust.py b/pyeudiw/satosa/trust.py index 86540b31..a3232444 100644 --- a/pyeudiw/satosa/trust.py +++ b/pyeudiw/satosa/trust.py @@ -16,6 +16,7 @@ from pyeudiw.tools.base_logger import BaseLogger + class BackendTrust(BaseLogger): """ Backend Trust class. @@ -41,7 +42,10 @@ def init_trust_resources(self) -> None: try: self.get_backend_trust_chain() except Exception as e: - self._log_critical("Backend Trust", f"Cannot fetch the trust anchor configuration: {e}") + self._log_critical( + "Backend Trust", + f"Cannot fetch the trust anchor configuration: {e}" + ) self.db_engine.close() self._db_engine = None @@ -57,10 +61,9 @@ def entity_configuration_endpoint(self, context: Context) -> Response: :rtype: Response """ - data = self.entity_configuration_as_dict if context.qs_params.get('format', '') == 'json': return Response( - json.dumps(data), + json.dumps(self.entity_configuration_as_dict), status="200", content="application/json" ) @@ -101,21 +104,21 @@ def get_backend_trust_chain(self) -> list[str]: """ try: trust_evaluation_helper = TrustEvaluationHelper.build_trust_chain_for_entity_id( - storage=self.db_engine, - entity_id=self.client_id, - entity_configuration=self.entity_configuration, - httpc_params=self.config['network']['httpc_params'] + storage = self.db_engine, + entity_id = self.client_id, + entity_configuration = self.entity_configuration, + httpc_params = self.config['network']['httpc_params'] ) self.db_engine.add_or_update_trust_attestation( - entity_id=self.client_id, - attestation=trust_evaluation_helper.trust_chain, - exp=trust_evaluation_helper.exp + entity_id = self.client_id, + attestation = trust_evaluation_helper.trust_chain, + exp = trust_evaluation_helper.exp ) return trust_evaluation_helper.trust_chain except (DiscoveryFailedError, EntryNotFound, Exception) as e: message = ( - f"Error while building trust chain for client with id: {self.client_id}\n" + f"Error while building trust chain for client with id: {self.client_id}. " f"{e.__class__.__name__}: {e}" ) self._log_warning("Trust Chain", message) @@ -154,7 +157,6 @@ def _validate_trust(self, context: Context, jws: str) -> TrustEvaluationHelper: f"{trust_eval.entity_id}" ) self._log_error(context, message) - raise NotTrustedFederationError( f"{trust_eval.entity_id} not found for Trust evaluation." ) @@ -164,7 +166,6 @@ def _validate_trust(self, context: Context, jws: str) -> TrustEvaluationHelper: f"{trust_eval.entity_id}: {e}" ) self._log_error(context, message) - raise NotTrustedFederationError( f"{trust_eval.entity_id} is not trusted." ) @@ -208,4 +209,4 @@ def entity_configuration(self) -> dict: "typ": "entity-statement+jwt" }, plain_dict=data - ) \ No newline at end of file + ) diff --git a/pyeudiw/x509/verify.py b/pyeudiw/x509/verify.py index a5835318..768f73f2 100644 --- a/pyeudiw/x509/verify.py +++ b/pyeudiw/x509/verify.py @@ -9,6 +9,7 @@ logger = logging.getLogger(__name__) + def _verify_x509_certificate_chain(pems: list[str]): """ Verify the x509 certificate chain. @@ -21,25 +22,28 @@ def _verify_x509_certificate_chain(pems: list[str]): """ try: store = crypto.X509Store() - - x509_certs = [crypto.load_certificate(crypto.FILETYPE_PEM, str(pem)) for pem in pems] + x509_certs = [ + crypto.load_certificate(crypto.FILETYPE_PEM, str(pem)) + for pem in pems + ] for cert in x509_certs[:-1]: store.add_cert(cert) store_ctx = crypto.X509StoreContext(store, x509_certs[-1]) - store_ctx.verify_certificate() return True except crypto.Error as e: _message = f"cert's chain result invalid for the following reason -> {e}" logging.warning(LOG_ERROR.format(_message)) + return False except Exception as e: _message = f"cert's chain cannot be validated for error -> {e}" logging.warning(LOG_ERROR.format(e)) return False - + + def _check_chain_len(pems: list) -> bool: """ Check the x509 certificate chain lenght. @@ -50,16 +54,15 @@ def _check_chain_len(pems: list) -> bool: :returns: True if the x509 certificate chain lenght is valid else False :rtype: bool """ - chain_len = len(pems) - if chain_len < 2: message = f"invalid chain lenght -> minimum expected 2 found {chain_len}" logging.warning(LOG_ERROR.format(message)) return False return True - + + def _check_datetime(exp: datetime | None): """ Check the x509 certificate chain expiration date. @@ -80,6 +83,7 @@ def _check_datetime(exp: datetime | None): return True + def verify_x509_attestation_chain(x5c: list[bytes], exp: datetime | None = None) -> bool: """ Verify the x509 attestation certificate chain. @@ -99,7 +103,8 @@ def verify_x509_attestation_chain(x5c: list[bytes], exp: datetime | None = None) pems = [DER_cert_to_PEM_cert(cert) for cert in x5c] return _verify_x509_certificate_chain(pems) - + + def verify_x509_anchor(pem_str: str, exp: datetime | None = None) -> bool: """ Verify the x509 anchor certificate. @@ -113,15 +118,18 @@ def verify_x509_anchor(pem_str: str, exp: datetime | None = None) -> bool: :rtype: bool """ if not _check_datetime(exp): + logging.error(LOG_ERROR.format("check datetime failed")) return False pems = [str(cert) for cert in pem.parse(pem_str)] if not _check_chain_len(pems): + logging.error(LOG_ERROR.format("check chain len failed")) return False return _verify_x509_certificate_chain(pems) + def get_issuer_from_x5c(x5c: list[bytes]) -> str: """ Get the issuer from the x509 certificate chain. @@ -135,6 +143,7 @@ def get_issuer_from_x5c(x5c: list[bytes]) -> str: cert = load_der_x509_certificate(x5c[-1]) return cert.subject.rfc4514_string().split("=")[1] + def is_der_format(cert: bytes) -> str: """ Check if the certificate is in DER format. @@ -149,5 +158,6 @@ def is_der_format(cert: bytes) -> str: pem = DER_cert_to_PEM_cert(cert) crypto.load_certificate(crypto.FILETYPE_PEM, str(pem)) return True - except crypto.Error: + except crypto.Error as e: + logging.error(LOG_ERROR.format(e)) return False From 59673ef9104ec1857dd505b28abc0272851de5e6 Mon Sep 17 00:00:00 2001 From: Giuseppe De Marco Date: Tue, 2 Jan 2024 15:35:43 +0100 Subject: [PATCH 2/8] fix: CI --- pyeudiw/federation/statements.py | 4 +++- pyeudiw/federation/trust_chain_validator.py | 4 ++-- pyeudiw/openid4vp/direct_post_response.py | 5 +++++ pyeudiw/tests/satosa/test_backend.py | 4 ++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pyeudiw/federation/statements.py b/pyeudiw/federation/statements.py index afe9f27a..1173c805 100644 --- a/pyeudiw/federation/statements.py +++ b/pyeudiw/federation/statements.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from copy import deepcopy from pyeudiw.federation.exceptions import ( UnknownKid, @@ -221,7 +223,7 @@ def __init__( jwt: str, httpc_params: dict, filter_by_allowed_trust_marks: list[str] = [], - trust_anchor_entity_conf: 'EntityStatement' | None = None, + trust_anchor_entity_conf: EntityStatement | None = None, trust_mark_issuers_entity_confs: list[EntityStatement] = [], ): """ diff --git a/pyeudiw/federation/trust_chain_validator.py b/pyeudiw/federation/trust_chain_validator.py index 6b6026b6..29a47d48 100644 --- a/pyeudiw/federation/trust_chain_validator.py +++ b/pyeudiw/federation/trust_chain_validator.py @@ -222,14 +222,14 @@ def _retrieve_es(self, download_url: str, iss: str) -> str: if not jwt: _msg = f"Cannot fast refresh Entity Statement {iss}" logger.warning(_msg) - raise HttpError(_msg) + # raise HttpError(_msg) if isinstance(jwt, list) and jwt: return jwt[0] return jwt def _update_st(self, st: str) -> str: """ - Updates the statement retrieving the new one using the source end_point and the sub fields of st payload. + Updates the statement retrieving the new one using the source_endpoint and the sub fields of the entity statement payload. :param st: The statement in form of a JWT. :type st: str diff --git a/pyeudiw/openid4vp/direct_post_response.py b/pyeudiw/openid4vp/direct_post_response.py index 97d1e3a7..3007d12e 100644 --- a/pyeudiw/openid4vp/direct_post_response.py +++ b/pyeudiw/openid4vp/direct_post_response.py @@ -1,9 +1,12 @@ +import logging + from typing import Dict from pyeudiw.jwk import JWK from pyeudiw.jwt import JWEHelper, JWSHelper from pyeudiw.jwk.exceptions import KidNotFoundError from pyeudiw.jwt.utils import decode_jwt_header, is_jwe_format from pyeudiw.openid4vp.exceptions import ( + InvalidVPToken, VPNotFound, VPInvalidNonce, NoNonceInVPToken @@ -12,6 +15,8 @@ from pyeudiw.openid4vp.vp import Vp from pydantic import ValidationError +logger = logging.getLogger(__name__) + class DirectPostResponse: """ diff --git a/pyeudiw/tests/satosa/test_backend.py b/pyeudiw/tests/satosa/test_backend.py index 2caa4887..e38d30c7 100644 --- a/pyeudiw/tests/satosa/test_backend.py +++ b/pyeudiw/tests/satosa/test_backend.py @@ -245,7 +245,7 @@ def test_vp_validation_in_redirect_endpoint(self, context): assert request_endpoint.status == "400" msg = json.loads(request_endpoint.message) assert msg["error"] == "invalid_request" - assert msg["error_description"] == "Error while validating VP: unexpected value." + assert msg["error_description"] # Recreate data without nonce # This will trigger a `NoNonceInVPToken` error @@ -287,7 +287,7 @@ def test_vp_validation_in_redirect_endpoint(self, context): assert request_endpoint.status == "400" msg = json.loads(request_endpoint.message) assert msg["error"] == "invalid_request" - assert msg["error_description"] == "Error while validating VP: vp has no nonce." + assert msg["error_description"] # This will trigger a `UnicodeDecodeError` which will be caught by the generic `Exception case`. response["vp_token"] = "asd.fgh.jkl" From f75b6df04b7a120d68c510369b44737f7c94f2cc Mon Sep 17 00:00:00 2001 From: Giuseppe De Marco Date: Tue, 2 Jan 2024 16:00:15 +0100 Subject: [PATCH 3/8] fix: remove new lines in logging msg --- pyeudiw/satosa/dpop.py | 8 ++++---- pyeudiw/trust/__init__.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pyeudiw/satosa/dpop.py b/pyeudiw/satosa/dpop.py index 9e4da208..7fb4b419 100644 --- a/pyeudiw/satosa/dpop.py +++ b/pyeudiw/satosa/dpop.py @@ -44,18 +44,18 @@ def _request_endpoint_dpop(self, context: Context, *args) -> Union[JsonResponse, try: WalletInstanceAttestationHeader(**_head) except ValidationError as e: - self._log_warning(context, message=f"[FOUND WIA] Invalid Headers: {_head}! \nValidation error: {e}") + self._log_warning(context, message=f"[FOUND WIA] Invalid Headers: {_head}. Validation error: {e}") except Exception as e: - self._log_warning(context, message=f"[FOUND WIA] Invalid Headers: {_head}! \nUnexpected error: {e}") + self._log_warning(context, message=f"[FOUND WIA] Invalid Headers: {_head}. Unexpected error: {e}") try: WalletInstanceAttestationPayload(**wia) except ValidationError as e: - _msg = f"[FOUND WIA] Invalid WIA: {wia}! \nValidation error: {e}" + _msg = f"[FOUND WIA] Invalid WIA: {wia}. Validation error: {e}" self._log_warning(context, message=_msg) # return self._handle_401(context, _msg, e) except Exception as e: - _msg = f"[FOUND WIA] Invalid WIA: {wia}! \nUnexpected error: {e}" + _msg = f"[FOUND WIA] Invalid WIA: {wia}. Unexpected error: {e}" self._log_warning(context, message=_msg) # return self._handle_401(context, _msg, e) diff --git a/pyeudiw/trust/__init__.py b/pyeudiw/trust/__init__.py index 7952fae1..a61b86c5 100644 --- a/pyeudiw/trust/__init__.py +++ b/pyeudiw/trust/__init__.py @@ -261,7 +261,8 @@ def discovery(self, entity_id: str, entity_configuration: EntityStatement | None is_good = tcbuilder.is_valid if not is_good: raise DiscoveryFailedError( - f"Discovery failed for entity {entity_id}\nwith configuration {entity_configuration}") + f"Discovery failed for entity {entity_id} with configuration {entity_configuration}" + ) @staticmethod def build_trust_chain_for_entity_id(storage: DBEngine, entity_id, entity_configuration, httpc_params): From eb3756a862eac4a0e4f5e6ca883ed0e139f8ca16 Mon Sep 17 00:00:00 2001 From: PascalDR Date: Wed, 10 Jan 2024 12:21:48 +0100 Subject: [PATCH 4/8] fix: error handling --- pyeudiw/federation/__init__.py | 30 +++++++++------------ pyeudiw/federation/trust_chain_validator.py | 8 ++++-- pyeudiw/tests/federation/test_schema.py | 15 ++++++++--- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/pyeudiw/federation/__init__.py b/pyeudiw/federation/__init__.py index afdc418d..a43d2d26 100644 --- a/pyeudiw/federation/__init__.py +++ b/pyeudiw/federation/__init__.py @@ -1,37 +1,33 @@ +from .exceptions import InvalidEntityStatement, InvalidEntityConfiguration from pyeudiw.federation.schemas.entity_configuration import EntityStatementPayload, EntityConfigurationPayload -def is_es(payload: dict) -> bool: +def is_es(payload: dict) -> None: """ Determines if payload dict is an Entity Statement :param payload: the object to determine if is an Entity Statement :type payload: dict - - :returns: True if is an Entity Statement and False otherwise - :rtype: bool """ try: EntityStatementPayload(**payload) - if payload["iss"] != payload["sub"]: - return True - except Exception: - return False - - -def is_ec(payload: dict) -> bool: + if payload["iss"] == payload["sub"]: + _msg = f"Invalid Entity Statement: iss and sub cannot be the same" + raise InvalidEntityStatement(_msg) + except ValueError as e: + _msg = f"Invalid Entity Statement: {e}" + raise InvalidEntityStatement(_msg) + +def is_ec(payload: dict) -> None: """ Determines if payload dict is an Entity Configuration :param payload: the object to determine if is an Entity Configuration :type payload: dict - - :returns: True if is an Entity Configuration and False otherwise - :rtype: bool """ try: EntityConfigurationPayload(**payload) - return True - except Exception as e: - return False \ No newline at end of file + except ValueError as e: + _msg = f"Invalid Entity Configuration: {e}" + raise InvalidEntityConfiguration(_msg) \ No newline at end of file diff --git a/pyeudiw/federation/trust_chain_validator.py b/pyeudiw/federation/trust_chain_validator.py index 6426d9b8..27bb6c44 100644 --- a/pyeudiw/federation/trust_chain_validator.py +++ b/pyeudiw/federation/trust_chain_validator.py @@ -12,7 +12,8 @@ HttpError, MissingTrustAnchorPublicKey, TimeValidationError, - KeyValidationError + KeyValidationError, + InvalidEntityStatement ) from pyeudiw.jwk import find_jwk @@ -223,8 +224,11 @@ def _update_st(self, st: str) -> str: """ payload = decode_jwt_payload(st) iss = payload['iss'] - if not is_es(payload): + + try: + is_es(payload) # It's an entity configuration + except InvalidEntityStatement: return self._retrieve_ec(iss) # if it has the source_endpoint let's try a fast renewal diff --git a/pyeudiw/tests/federation/test_schema.py b/pyeudiw/tests/federation/test_schema.py index fcfe5b10..e8d50dba 100644 --- a/pyeudiw/tests/federation/test_schema.py +++ b/pyeudiw/tests/federation/test_schema.py @@ -1,6 +1,7 @@ from pyeudiw.tools.utils import iat_now, exp_from_now from pyeudiw.federation import is_es, is_ec +from pyeudiw.federation.exceptions import InvalidEntityStatement, InvalidEntityConfiguration NOW = iat_now() EXP = exp_from_now(5) @@ -127,16 +128,22 @@ def test_is_es(): - assert is_es(ta_es) + is_es(ta_es) def test_is_es_false(): - assert not is_es(ta_ec) + try: + is_es(ta_ec) + except InvalidEntityStatement as e: + pass def test_is_ec(): - assert is_ec(ta_ec) + is_ec(ta_ec) def test_is_ec_false(): - assert not is_ec(ta_es) + try: + is_ec(ta_es) + except InvalidEntityConfiguration as e: + pass From fbf62c46b77aa129caebffc8db7141cc5a9a34b2 Mon Sep 17 00:00:00 2001 From: Pasquale De Rose Date: Wed, 10 Jan 2024 16:07:29 +0100 Subject: [PATCH 5/8] [Fix/error handling] Different error handling for is_ec and is_es (#221) * QRCode features (#217) * feat: changed qrcode handling * feat: copied static files from Satosa-saml2spid * feat: modified expiration time handling * fix: update test configuration * fix: remove of connection params * [Feat/retention rule] Added ttl rule for sessions (#218) * feat: added retention rule for session collection * test: added test for retention rule * Update pyeudiw/storage/mongo_storage.py Co-authored-by: Giuseppe De Marco * Update pyeudiw/storage/mongo_storage.py Co-authored-by: Giuseppe De Marco * Update pyeudiw/storage/base_storage.py Co-authored-by: Giuseppe De Marco * chore: added config parameter --------- Co-authored-by: Giuseppe De Marco * fix: error handling --------- Co-authored-by: Giuseppe De Marco Co-authored-by: Ghenadie Artic <57416779+Gartic99@users.noreply.github.com> --- example/satosa/pyeudiw_backend.yaml | 2 ++ example/satosa/templates/qr_code.html | 14 +++++----- pyeudiw/federation/__init__.py | 30 +++++++++------------ pyeudiw/federation/trust_chain_validator.py | 8 ++++-- pyeudiw/satosa/backend.py | 1 + pyeudiw/storage/base_storage.py | 18 +++++++++++++ pyeudiw/storage/mongo_storage.py | 18 ++++++++++++- pyeudiw/tests/federation/test_schema.py | 15 ++++++++--- pyeudiw/tests/settings.py | 1 + pyeudiw/tests/storage/test_mongo_storage.py | 29 +++++++++++++++++++- 10 files changed, 103 insertions(+), 33 deletions(-) diff --git a/example/satosa/pyeudiw_backend.yaml b/example/satosa/pyeudiw_backend.yaml index 795e0565..ef8380ec 100644 --- a/example/satosa/pyeudiw_backend.yaml +++ b/example/satosa/pyeudiw_backend.yaml @@ -21,6 +21,7 @@ config: qrcode: size: 100 color: '#2B4375' + expiration_time: 120 # seconds logo_path: use_zlib: false @@ -137,6 +138,7 @@ config: db_sessions_collection: sessions db_trust_attestations_collection: trust_attestations db_trust_anchors_collection: trust_anchors + data_ttl: 63072000 # 2 years # - connection_params: #This is the configuration for the relaying party metadata diff --git a/example/satosa/templates/qr_code.html b/example/satosa/templates/qr_code.html index 5ec1f997..f511e7ab 100644 --- a/example/satosa/templates/qr_code.html +++ b/example/satosa/templates/qr_code.html @@ -25,9 +25,6 @@

Il codice è valido per secondi

-

- Ho bisogno di più tempo -