-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#299-feature/presentation submission dynamic handler #314
base: refac
Are you sure you want to change the base?
Changes from 3 commits
52682cd
df31213
1eb207b
35d23c8
68a81d5
384813a
6ae5b6c
6b4a1f9
72fe694
6c6a456
345006e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import os | ||
import yaml | ||
import importlib | ||
from typing import Dict, Any | ||
import logging | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
class PresentationSubmission: | ||
def __init__(self, submission: Dict[str, Any]): | ||
""" | ||
Initialize the PresentationSubmission handler with the submission data. | ||
|
||
Args: | ||
submission (Dict[str, Any]): The presentation submission data. | ||
|
||
Raises: | ||
KeyError: If the 'format' key is missing in the submission. | ||
ValueError: If the format is not supported or not defined in the configuration. | ||
ImportError: If the module or class cannot be loaded. | ||
""" | ||
self.config = self._load_config() | ||
self.submission = submission | ||
self.handlers = self._initialize_handlers() | ||
|
||
def _load_config(self) -> Dict[str, Any]: | ||
""" | ||
Load the configuration from format_config.yml located in the same directory. | ||
|
||
Returns: | ||
Dict[str, Any]: The configuration dictionary. | ||
|
||
Raises: | ||
FileNotFoundError: If the configuration file is not found. | ||
""" | ||
config_path = os.path.join(os.path.dirname(__file__), "format_config.yml") | ||
if not os.path.exists(config_path): | ||
raise FileNotFoundError(f"Configuration file not found: {config_path}") | ||
|
||
with open(config_path, "r") as config_file: | ||
return yaml.safe_load(config_file) | ||
|
||
def _initialize_handlers(self) -> Dict[int, object]: | ||
""" | ||
Initialize handlers for each item in the 'descriptor_map' of the submission. | ||
|
||
Returns: | ||
Dict[int, object]: A dictionary mapping indices to handler instances. | ||
|
||
Raises: | ||
KeyError: If the 'format' key is missing in any descriptor. | ||
ValueError: If a format is not supported or not defined in the configuration. | ||
ImportError: If a module or class cannot be loaded. | ||
""" | ||
handlers = {} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please create and add a pydantic schema validation here for the prensentation_submission object There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please put some limits on the presentation submission size, in a way that an attacker might not push 10mb of presentation submission causing resource exaustion to the RP There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added control for byte size and credential number too |
||
try: | ||
descriptor_map = self.submission.get("descriptor_map", []) | ||
except KeyError: | ||
raise KeyError("The 'descriptor_map' key is missing in the submission.") | ||
|
||
for index, descriptor in enumerate(descriptor_map): | ||
format_name = descriptor.get("format") | ||
if not format_name: | ||
raise KeyError(f"The 'format' key is missing in descriptor at index {index}.") | ||
|
||
# Search for the format in the configuration | ||
format_conf = next((fmt for fmt in self.config.get("formats", []) if fmt["name"] == format_name), None) | ||
if not format_conf: | ||
raise ValueError(f"Format '{format_name}' is not supported or not defined in the configuration.") | ||
|
||
module_name = format_conf["module"] | ||
class_name = format_conf["class"] | ||
|
||
try: | ||
# Dynamically load the module and class | ||
module = importlib.import_module(module_name) | ||
cls = getattr(module, class_name) | ||
handlers[index] = cls() # Instantiate the class | ||
except ModuleNotFoundError: | ||
logger.warning(f"Module '{module_name}' not found for format '{format_name}'. Skipping index {index}.") | ||
except AttributeError: | ||
logger.warning(f"Class '{class_name}' not found in module '{module_name}' for format '{format_name}'. Skipping index {index}.") | ||
except Exception as e: | ||
logger.warning(f"Error loading format '{format_name}' for index {index}: {e}") | ||
|
||
return handlers |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
formats: | ||
- name: "vc+sd-jwt" | ||
LadyCodesItBetter marked this conversation as resolved.
Show resolved
Hide resolved
|
||
module: "pyeudiw.openid4vp.presentation_submission" | ||
class: "VcSdJwt" | ||
- name: "ldp_vp" | ||
module: "pyeudiw.openid4vp.presentation_submission" | ||
class: "LdpVp" | ||
- name: "jwt_vp_json" | ||
module: "pyeudiw.openid4vp.presentation_submission" | ||
class: "JwtVpJson" | ||
- name: "ac_vp" | ||
module: "pyeudiw.openid4vp.presentation_submission" | ||
class: "AcVp" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import pytest | ||
from unittest.mock import patch, MagicMock | ||
from pyeudiw.openid4vp.presentation_submission import PresentationSubmission | ||
|
||
|
||
# Mock data for testing | ||
mock_format_config = { | ||
"formats": [ | ||
{"name": "ldp_vp", "module": "mock.module", "class": "MockLdpVpHandler"}, | ||
{"name": "jwt_vp_json", "module": "mock.module", "class": "MockJwtVpJsonHandler"} | ||
] | ||
} | ||
|
||
valid_submission = { | ||
"descriptor_map": [ | ||
{"format": "ldp_vp", "id": "descriptor_1", "path": "$"}, | ||
{"format": "jwt_vp_json", "id": "descriptor_2", "path": "$"} | ||
] | ||
} | ||
|
||
def test_presentation_submission_initialization(): | ||
""" | ||
Test that the PresentationSubmission class initializes correctly, | ||
loads handlers for all valid formats, and handles missing configurations. | ||
""" | ||
# Mock handler classes | ||
mock_ldp_vp_handler = MagicMock(name="MockLdpVpHandler") | ||
mock_jwt_vp_json_handler = MagicMock(name="MockJwtVpJsonHandler") | ||
|
||
# Mock import_module to return a fake module with our mock classes | ||
mock_module = MagicMock() | ||
setattr(mock_module, "MockLdpVpHandler", mock_ldp_vp_handler) | ||
setattr(mock_module, "MockJwtVpJsonHandler", mock_jwt_vp_json_handler) | ||
|
||
with patch("pyeudiw.openid4vp.presentation_submission.PresentationSubmission._load_config", return_value=mock_format_config), \ | ||
patch("importlib.import_module", return_value=mock_module): | ||
|
||
# Initialize the class | ||
ps = PresentationSubmission(valid_submission) | ||
|
||
# Assert that handlers were created for all formats in descriptor_map | ||
assert len(ps.handlers) == len(valid_submission["descriptor_map"]), "Not all handlers were created." | ||
|
||
# Check that the handlers are instances of the mocked classes | ||
assert ps.handlers[0] is mock_ldp_vp_handler(), "Handler for 'ldp_vp' format is incorrect." | ||
assert ps.handlers[1] is mock_jwt_vp_json_handler(), "Handler for 'jwt_vp_json' format is incorrect." | ||
|
||
def test_presentation_submission_invalid_format(): | ||
""" | ||
Test that the PresentationSubmission class handles unsupported formats gracefully. | ||
""" | ||
invalid_submission = { | ||
"descriptor_map": [ | ||
{"format": "unsupported_format", "id": "descriptor_3", "path": "$"} | ||
] | ||
} | ||
|
||
with patch("pyeudiw.openid4vp.presentation_submission.PresentationSubmission._load_config", return_value=mock_format_config): | ||
# Expect a ValueError for unsupported format | ||
with pytest.raises(ValueError, match="Format 'unsupported_format' is not supported or not defined in the configuration."): | ||
PresentationSubmission(invalid_submission) | ||
|
||
def test_presentation_submission_missing_format_key(): | ||
""" | ||
Test that the PresentationSubmission class raises KeyError | ||
when the 'format' key is missing in a descriptor. | ||
""" | ||
missing_format_submission = { | ||
"descriptor_map": [ | ||
{"id": "descriptor_4", "path": "$"} | ||
] | ||
} | ||
|
||
with patch("pyeudiw.openid4vp.presentation_submission.PresentationSubmission._load_config", return_value=mock_format_config): | ||
# Expect a KeyError for missing 'format' | ||
with pytest.raises(KeyError, match="The 'format' key is missing in descriptor at index 0."): | ||
PresentationSubmission(missing_format_submission) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't put the classes within the init file
we are going to refactor all the code within this project to remove this code-style in favour or a proper division of files by scopes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done