From 91d7d4981f1e70900c56d4e100b0b5ab29dd1600 Mon Sep 17 00:00:00 2001 From: red Date: Thu, 3 Oct 2024 16:43:25 +0800 Subject: [PATCH 1/6] improved showing of error in vci module --- spp_openid_vci/models/res_partner.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/spp_openid_vci/models/res_partner.py b/spp_openid_vci/models/res_partner.py index 68e8ec920..366e5273c 100644 --- a/spp_openid_vci/models/res_partner.py +++ b/spp_openid_vci/models/res_partner.py @@ -1,6 +1,7 @@ import base64 import calendar import json +import logging from datetime import datetime from io import BytesIO @@ -9,7 +10,9 @@ from qrcode.image.pil import PilImage from odoo import _, fields, models -from odoo.exceptions import UserError +from odoo.exceptions import AccessError, UserError + +_logger = logging.getLogger(__name__) class SPPRegistry(models.Model): @@ -44,7 +47,16 @@ def _issue_vc(self, vci_issuer): try: credential_issuer_response = requests.get(url, timeout=5) except requests.exceptions.Timeout as e: - raise UserError("The request to the credential issuer timed out.") from e + raise AccessError("The request to the credential issuer timed out.") from e + + if not credential_issuer_response.ok: + _logger.error(f"Request to the url {url} failed.") + _logger.error(credential_issuer_response.json()) + _logger.error("Status code: %s", credential_issuer_response.status_code) + raise AccessError( + f"Failed to get credential issuer data. Status code: {credential_issuer_response.status_code}" + ) + issuer_data = credential_issuer_response.json() credential_issuer = f"{issuer_data['credential_issuer']}/api/v1/security" From 3c201178da53fe28f7ea2ffd606522a3d9d89a8d Mon Sep 17 00:00:00 2001 From: red Date: Wed, 30 Oct 2024 14:33:17 +0800 Subject: [PATCH 2/6] improved vci issuer --- spp_openid_vci/models/res_partner.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/spp_openid_vci/models/res_partner.py b/spp_openid_vci/models/res_partner.py index 366e5273c..789c9c010 100644 --- a/spp_openid_vci/models/res_partner.py +++ b/spp_openid_vci/models/res_partner.py @@ -6,11 +6,10 @@ from io import BytesIO import qrcode -import requests from qrcode.image.pil import PilImage from odoo import _, fields, models -from odoo.exceptions import AccessError, UserError +from odoo.exceptions import UserError _logger = logging.getLogger(__name__) @@ -41,23 +40,7 @@ def _issue_vc(self, vci_issuer): if not reg_id: raise UserError(f"No Registrant found with this ID Type: {vci_issuer.auth_sub_id_type_id.name}.") - web_base_url = self.env["ir.config_parameter"].sudo().get_param("web.base.url").rstrip("/") - - url = f"{web_base_url}/api/v1/vci/.well-known/openid-credential-issuer/{vci_issuer.name}" - try: - credential_issuer_response = requests.get(url, timeout=5) - except requests.exceptions.Timeout as e: - raise AccessError("The request to the credential issuer timed out.") from e - - if not credential_issuer_response.ok: - _logger.error(f"Request to the url {url} failed.") - _logger.error(credential_issuer_response.json()) - _logger.error("Status code: %s", credential_issuer_response.status_code) - raise AccessError( - f"Failed to get credential issuer data. Status code: {credential_issuer_response.status_code}" - ) - - issuer_data = credential_issuer_response.json() + issuer_data = self.env["g2p.openid.vci.issuers"].get_issuer_metadata_by_name(issuer_name=vci_issuer.name) credential_issuer = f"{issuer_data['credential_issuer']}/api/v1/security" credentials_supported = issuer_data.get("credentials_supported", None) From 106f1420f304619d500738e170c9c32fd0432097 Mon Sep 17 00:00:00 2001 From: red Date: Mon, 18 Nov 2024 18:09:05 +0800 Subject: [PATCH 3/6] added logger --- spp_openid_vci/models/res_partner.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spp_openid_vci/models/res_partner.py b/spp_openid_vci/models/res_partner.py index 68ed54f0e..a9e0d0364 100644 --- a/spp_openid_vci/models/res_partner.py +++ b/spp_openid_vci/models/res_partner.py @@ -70,6 +70,8 @@ def _create_qr_code(self, data): border=4, ) + _logger.info(f"Data: {data}") + qr.add_data(data) qr.make(fit=True) From 02b0f142f392e3d20528f6548825aceeaab05c9f Mon Sep 17 00:00:00 2001 From: red Date: Tue, 19 Nov 2024 14:02:33 +0800 Subject: [PATCH 4/6] [FIX][IMP] spp_openid_vci: Fix issue when data is too long; Improve function of creating qrcode --- spp_openid_vci/models/res_partner.py | 31 ++----------- spp_openid_vci/tools/__init__.py | 3 ++ spp_openid_vci/tools/tools.py | 65 ++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 28 deletions(-) create mode 100644 spp_openid_vci/tools/__init__.py create mode 100644 spp_openid_vci/tools/tools.py diff --git a/spp_openid_vci/models/res_partner.py b/spp_openid_vci/models/res_partner.py index a9e0d0364..f74750909 100644 --- a/spp_openid_vci/models/res_partner.py +++ b/spp_openid_vci/models/res_partner.py @@ -1,16 +1,13 @@ -import base64 import calendar import json import logging from datetime import datetime -from io import BytesIO - -import qrcode -from qrcode.image.pil import PilImage from odoo import _, fields, models from odoo.exceptions import UserError +from ..tools import create_qr_code + _logger = logging.getLogger(__name__) @@ -62,28 +59,6 @@ def _issue_vc(self, vci_issuer): return self.env["g2p.openid.vci.issuers"].issue_vc(credential_request, signed_data) - def _create_qr_code(self, data): - qr = qrcode.QRCode( - error_correction=qrcode.constants.ERROR_CORRECT_L, - image_factory=PilImage, - box_size=10, - border=4, - ) - - _logger.info(f"Data: {data}") - - qr.add_data(data) - qr.make(fit=True) - - img = qr.make_image() - - temp = BytesIO() - img.save(temp, format="PNG") - qr_img = base64.b64encode(temp.getvalue()) - temp.close() - - return qr_img - def registry_issue_card(self): self.ensure_one() @@ -109,7 +84,7 @@ def _issue_vc_qr(self, vci_issuer): result = self._issue_vc(vci_issuer) - qr_img = self._create_qr_code(json.dumps(result)) + qr_img = create_qr_code(json.dumps(result)) self.vc_qr_code = qr_img diff --git a/spp_openid_vci/tools/__init__.py b/spp_openid_vci/tools/__init__.py new file mode 100644 index 000000000..1e2295789 --- /dev/null +++ b/spp_openid_vci/tools/__init__.py @@ -0,0 +1,3 @@ +from .tools import delete_keys_except +from .tools import qr_image +from .tools import create_qr_code diff --git a/spp_openid_vci/tools/tools.py b/spp_openid_vci/tools/tools.py new file mode 100644 index 000000000..61759afde --- /dev/null +++ b/spp_openid_vci/tools/tools.py @@ -0,0 +1,65 @@ +import base64 +import json +import logging +from io import BytesIO + +import qrcode +from qrcode.exceptions import DataOverflowError +from qrcode.image.pil import PilImage + +_logger = logging.getLogger(__name__) + + +def delete_keys_except(d, keys_to_keep): + """ + Deletes all keys from the dictionary except for the specified keys. + + Parameters: + - d: The dictionary from which keys are to be deleted. + - keys_to_keep: A key or a list of keys that should not be deleted. + + Returns: + None; the operation modifies the dictionary in place. + """ + # Ensure keys_to_keep is a list + if not isinstance(keys_to_keep, list): + keys_to_keep = [keys_to_keep] + + keys_to_delete = [key for key in d.keys() if key not in keys_to_keep] + for key in keys_to_delete: + del d[key] + + return d + + +def qr_image(data): + _logger.info(f"Data: {data}") + qr = qrcode.QRCode( + error_correction=qrcode.constants.ERROR_CORRECT_L, + image_factory=PilImage, + box_size=10, + border=4, + ) + qr.add_data(data) + qr.make(fit=True) + + return qr.make_image() + + +def create_qr_code(data): + try: + img = qr_image(data) + except DataOverflowError as e: + _logger.warning(f"Data Overflow Error: {e}") + _logger.info("Deleting keys in 'proof' except jws") + + data_dict = json.loads(data) + data_dict["credential"]["proof"] = delete_keys_except(data_dict["credential"]["proof"], keys_to_keep="jws") + img = qr_image(json.dumps(data_dict)) + + temp = BytesIO() + img.save(temp, format="PNG") + qr_img = base64.b64encode(temp.getvalue()) + temp.close() + + return qr_img From 47b19a6774c683ad436301faa01b05289d5cd767 Mon Sep 17 00:00:00 2001 From: red Date: Tue, 19 Nov 2024 16:33:48 +0800 Subject: [PATCH 5/6] [REM] spp_openid_vci: Remove test_create_qr_code --- spp_openid_vci/tests/test_vci_issuer.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spp_openid_vci/tests/test_vci_issuer.py b/spp_openid_vci/tests/test_vci_issuer.py index 5d17d6e96..38e68e26c 100644 --- a/spp_openid_vci/tests/test_vci_issuer.py +++ b/spp_openid_vci/tests/test_vci_issuer.py @@ -119,11 +119,6 @@ def test_issue_vc(self, mock_get): self.res_partner_complete._issue_vc(self.vci_issuer_complete) issue_vc.assert_called_once() - def test_create_qr_code(self): - qr_img = self.res_partner_complete._create_qr_code("Test Data") - self.assertIsNotNone(qr_img) - self.assertIsInstance(qr_img, bytes) - def test_registry_issue_card(self): registry_issue_card_action = self.res_partner_complete.registry_issue_card() From 0e917a8396900d93c11c69f537d4cce255ecabf3 Mon Sep 17 00:00:00 2001 From: red Date: Tue, 19 Nov 2024 16:54:07 +0800 Subject: [PATCH 6/6] [ADD] spp_openid_vci: Add test case to test delete_keys_except, qr_image, create_qr_code --- spp_openid_vci/tests/__init__.py | 1 + spp_openid_vci/tests/test_tools.py | 38 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 spp_openid_vci/tests/test_tools.py diff --git a/spp_openid_vci/tests/__init__.py b/spp_openid_vci/tests/__init__.py index 1f8df8e65..b8aaeb6f4 100644 --- a/spp_openid_vci/tests/__init__.py +++ b/spp_openid_vci/tests/__init__.py @@ -1 +1,2 @@ from . import test_vci_issuer +from . import test_tools diff --git a/spp_openid_vci/tests/test_tools.py b/spp_openid_vci/tests/test_tools.py new file mode 100644 index 000000000..8ce9ec091 --- /dev/null +++ b/spp_openid_vci/tests/test_tools.py @@ -0,0 +1,38 @@ +from qrcode.image.pil import PilImage + +from odoo.tests.common import TransactionCase + +from ..tools import create_qr_code, delete_keys_except, qr_image + + +class VCITools(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + def test_delete_keys_except(self): + sample_data = { + "name": "John Doe", + "age": 30, + } + delete_keys_except(sample_data, "name") + + self.assertEqual(sample_data, {"name": "John Doe"}) + + sample_data = { + "name": "John Doe", + "age": 30, + } + delete_keys_except(sample_data, ["age"]) + + self.assertEqual(sample_data, {"age": 30}) + + def test_qr_image(self): + img = qr_image("Hello, World!") + self.assertTrue(img) + self.assertIsInstance(img, PilImage) + + def test_create_qr_code(self): + img = create_qr_code("Hello, World!") + self.assertTrue(img) + self.assertIsInstance(img, bytes)