From 7710ffcad811edc1d4672e839b8a1e8cc83a848f Mon Sep 17 00:00:00 2001 From: Zicchio Date: Mon, 11 Nov 2024 17:50:46 +0100 Subject: [PATCH 1/3] fix: qr code value --- pyeudiw/satosa/default/openid4vp_backend.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pyeudiw/satosa/default/openid4vp_backend.py b/pyeudiw/satosa/default/openid4vp_backend.py index 4db4edbd..d6c38022 100644 --- a/pyeudiw/satosa/default/openid4vp_backend.py +++ b/pyeudiw/satosa/default/openid4vp_backend.py @@ -205,20 +205,18 @@ def pre_request_endpoint(self, context: Context, internal_request, **kwargs) -> 'client_id': self.client_id, 'request_uri': f"{self.absolute_request_url}?id={state}", } - url_params = urlencode(payload, quote_via=quote_plus) + + respose_url = self._build_authz_request_url(payload) if is_smartphone(context.http_headers.get('HTTP_USER_AGENT')): # Same Device flow - res_url = f'{self.config["authorization"]["url_scheme"]}://authorize?{url_params}' - return Redirect(res_url) + return Redirect(respose_url) # Cross Device flow - res_url = f'{self.client_id}?{url_params}' - result = self.template.qrcode_page.render( { "qrcode_color": self.config["qrcode"]["color"], - "qrcode_text": res_url, + "qrcode_text": respose_url, "qrcode_size": self.config["qrcode"]["size"], "qrcode_logo_path": self.config["qrcode"]["logo_path"], "qrcode_expiration_time": self.config["qrcode"]["expiration_time"], @@ -342,6 +340,15 @@ def db_engine(self) -> DBEngine: return self._db_engine + def _build_authz_request_url(self, payload: dict) -> str: + scheme = self.config["authorization"]["url_scheme"] + # NOTE: path component is currently unused by the protocol, but currently + # we leave it there as 'authorize' to stress the fact that this is an + # OAuth 2.0 request modified by JAR (RFC9101) + path = "authorize" + query_params = urlencode(payload, quote_via=quote_plus) + return f"{scheme}://{path}?{query_params}" + @property def default_metadata_private_jwk(self) -> tuple: """Returns the default metadata private JWK""" From dff844ed117e021f98868167aa0c00e12c745395 Mon Sep 17 00:00:00 2001 From: Zicchio Date: Tue, 12 Nov 2024 08:35:18 +0100 Subject: [PATCH 2/3] Applied suggestions from code review --- README.SATOSA.md | 2 +- pyeudiw/satosa/default/openid4vp_backend.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.SATOSA.md b/README.SATOSA.md index 393b158a..3e5f8674 100644 --- a/README.SATOSA.md +++ b/README.SATOSA.md @@ -77,7 +77,7 @@ To install the OpenID4VP SATOSA backend you just need to: | Parameter | Description | Example value | | -------------------------------------- | ------------------------------------------------------------------------------ | --------------------------------------------- | -| config.authorization.url_scheme | The URL scheme for the authorization | eudiw | +| config.authorization.url_scheme | Either a custom URL scheme for the authorization, or a universal link | haip, https://wallet.example | | config.authorization.scopes | The list of scopes for the authorization | [pid-sd-jwt:unique_id+given_name+family_name] | | config.authorization.default_acr_value | The default authentication context class reference value for the authorization | https://www.spid.gov.it/SpidL2 | diff --git a/pyeudiw/satosa/default/openid4vp_backend.py b/pyeudiw/satosa/default/openid4vp_backend.py index d6c38022..bf991786 100644 --- a/pyeudiw/satosa/default/openid4vp_backend.py +++ b/pyeudiw/satosa/default/openid4vp_backend.py @@ -206,17 +206,17 @@ def pre_request_endpoint(self, context: Context, internal_request, **kwargs) -> 'request_uri': f"{self.absolute_request_url}?id={state}", } - respose_url = self._build_authz_request_url(payload) + response_url = self._build_authz_request_url(payload) if is_smartphone(context.http_headers.get('HTTP_USER_AGENT')): # Same Device flow - return Redirect(respose_url) + return Redirect(response_url) # Cross Device flow result = self.template.qrcode_page.render( { "qrcode_color": self.config["qrcode"]["color"], - "qrcode_text": respose_url, + "qrcode_text": response_url, "qrcode_size": self.config["qrcode"]["size"], "qrcode_logo_path": self.config["qrcode"]["logo_path"], "qrcode_expiration_time": self.config["qrcode"]["expiration_time"], @@ -342,12 +342,14 @@ def db_engine(self) -> DBEngine: def _build_authz_request_url(self, payload: dict) -> str: scheme = self.config["authorization"]["url_scheme"] + if "://" not in scheme: + scheme = scheme + "://" # NOTE: path component is currently unused by the protocol, but currently # we leave it there as 'authorize' to stress the fact that this is an # OAuth 2.0 request modified by JAR (RFC9101) path = "authorize" query_params = urlencode(payload, quote_via=quote_plus) - return f"{scheme}://{path}?{query_params}" + return f"{scheme}{path}?{query_params}" @property def default_metadata_private_jwk(self) -> tuple: From 5a928f156af0aada45cb12029d1f818760b1300b Mon Sep 17 00:00:00 2001 From: Zicchio Date: Tue, 12 Nov 2024 09:14:42 +0100 Subject: [PATCH 3/3] feat: universal link --- .../integration_test/same_device_integration_test.py | 9 +++++++-- pyeudiw/satosa/default/openid4vp_backend.py | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/example/satosa/integration_test/same_device_integration_test.py b/example/satosa/integration_test/same_device_integration_test.py index d52cd447..79aa9946 100644 --- a/example/satosa/integration_test/same_device_integration_test.py +++ b/example/satosa/integration_test/same_device_integration_test.py @@ -21,8 +21,9 @@ db_engine_inst = setup_test_db_engine() db_engine_inst = apply_trust_settings(db_engine_inst) -def _extract_request_uri(e: requests.exceptions.InvalidSchema) -> str: - request_uri = re.search(r'request_uri=(.*?)(?:\'|$)', urllib.parse.unquote_plus(e.args[0])).group(1) +def _extract_request_uri(e: Exception) -> str: + request_uri: str = re.search(r'request_uri=(.*?)(?:\'|\s|$)', urllib.parse.unquote_plus(e.args[0])).group(1) + request_uri = request_uri.rstrip() return request_uri @@ -43,7 +44,11 @@ def _extract_request_uri(e: requests.exceptions.InvalidSchema) -> str: timeout=TIMEOUT_S ) except requests.exceptions.InvalidSchema as e: + # custom url scheme such as 'haip' or 'eudiw' will raise this exception request_uri = _extract_request_uri(e) +except requests.exceptions.ConnectionError as e: + # universal link such as 'https://wallet.example' will raise this exception + request_uri = _extract_request_uri(e.args[0]) sign_request_obj = http_user_agent.get( request_uri, diff --git a/pyeudiw/satosa/default/openid4vp_backend.py b/pyeudiw/satosa/default/openid4vp_backend.py index bf991786..e55c6b1c 100644 --- a/pyeudiw/satosa/default/openid4vp_backend.py +++ b/pyeudiw/satosa/default/openid4vp_backend.py @@ -344,6 +344,8 @@ def _build_authz_request_url(self, payload: dict) -> str: scheme = self.config["authorization"]["url_scheme"] if "://" not in scheme: scheme = scheme + "://" + if not scheme.endswith("/"): + scheme = scheme + "/" # NOTE: path component is currently unused by the protocol, but currently # we leave it there as 'authorize' to stress the fact that this is an # OAuth 2.0 request modified by JAR (RFC9101)