diff --git a/NOTICES b/NOTICES index a24b3a4eee..bf9636c516 100644 --- a/NOTICES +++ b/NOTICES @@ -355,25 +355,6 @@ THE SOFTWARE. ----------------------------------------------------------------- ----------------------------------------------------------------- -=> msgpack - -Copyright (C) 2008-2011 INADA Naoki - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ------------------------------------------------------------------ ------------------------------------------------------------------ - => requests Copyright (C) 2019 Kenneith Reitz diff --git a/UpgradingACA-Py.md b/UpgradingACA-Py.md index 0ad8708564..a829bc4f7f 100644 --- a/UpgradingACA-Py.md +++ b/UpgradingACA-Py.md @@ -59,6 +59,25 @@ is high (such as a "resave connections" upgrade to a deployment with many, many connections), you may want to do a test upgrade offline first, to see if there is likely to be a service disruption during the upgrade. Plan accordingly! +## Tagged upgrades +Upgrades are defined in the [Upgrade Definition YML file], in addition to specifying upgrade actions by version they can also be specified by named tags. Unlike version based upgrades where all applicable version based actions will be performed based upon sorted order of versions, with named tags only actions corresponding to provided tags will be performed. Note: `--force-upgrade` is required when running name tags based upgrade [i.e. provding `--named-tag`] + +Tags are specfied in YML file as below: +``` +fix_issue_rev_reg: + fix_issue_rev_reg_records: true +``` + +Example +``` + ./scripts/run_docker upgrade --force-upgrade --named-tag fix_issue_rev_reg + +In case, running multiple tags [say test1 & test2]: + ./scripts/run_docker upgrade --force-upgrade --named-tag test1 --named-tag test2 +``` + + + ## Exceptions There are a couple of upgrade exception conditions to consider, as outlined diff --git a/aries_cloudagent/commands/default_version_upgrade_config.yml b/aries_cloudagent/commands/default_version_upgrade_config.yml index 8c04b9e48f..25cc32538b 100644 --- a/aries_cloudagent/commands/default_version_upgrade_config.yml +++ b/aries_cloudagent/commands/default_version_upgrade_config.yml @@ -13,4 +13,6 @@ v0.7.1: v0.7.0: update_existing_records: false v0.6.0: - update_existing_records: false \ No newline at end of file + update_existing_records: false +fix_issue_rev_reg: + fix_issue_rev_reg_records: true diff --git a/aries_cloudagent/commands/tests/test_upgrade.py b/aries_cloudagent/commands/tests/test_upgrade.py index 3cbdda9831..d6c5c66e08 100644 --- a/aries_cloudagent/commands/tests/test_upgrade.py +++ b/aries_cloudagent/commands/tests/test_upgrade.py @@ -121,6 +121,44 @@ async def test_upgrade_callable(self): } ) + async def test_upgrade_callable_named_tag(self): + version_storage_record = await self.storage.find_record( + type_filter="acapy_version", tag_query={} + ) + await self.storage.delete_record(version_storage_record) + with async_mock.patch.object( + test_module, + "wallet_config", + async_mock.CoroutineMock( + return_value=( + self.profile, + async_mock.CoroutineMock(did="public DID", verkey="verkey"), + ) + ), + ), async_mock.patch.object( + test_module.yaml, + "safe_load", + async_mock.MagicMock( + return_value={ + "v0.7.2": { + "resave_records": { + "base_record_path": [ + "aries_cloudagent.connections.models.conn_record.ConnRecord" + ] + }, + "update_existing_records": True, + }, + "fix_issue_rev_reg": {"fix_issue_rev_reg_records": True}, + } + ), + ): + await test_module.upgrade( + settings={ + "upgrade.named_tags": ["fix_issue_rev_reg"], + "upgrade.force_upgrade": True, + } + ) + async def test_upgrade_x_same_version(self): version_storage_record = await self.storage.find_record( type_filter="acapy_version", tag_query={} @@ -169,7 +207,9 @@ async def test_upgrade_missing_from_version(self): "upgrade.config_path": "./aries_cloudagent/commands/default_version_upgrade_config.yml", } ) - assert "No upgrade from version found in wallet or" in str(ctx.exception) + assert "Error during upgrade: No upgrade from version or tags found" in str( + ctx.exception + ) async def test_upgrade_x_callable_not_set(self): version_storage_record = await self.storage.find_record( diff --git a/aries_cloudagent/commands/upgrade.py b/aries_cloudagent/commands/upgrade.py index 8706c61bcf..bf2d0dc95a 100644 --- a/aries_cloudagent/commands/upgrade.py +++ b/aries_cloudagent/commands/upgrade.py @@ -3,6 +3,7 @@ import asyncio import logging import os +import json import yaml from configargparse import ArgumentParser @@ -19,16 +20,18 @@ Tuple, ) -from ..core.profile import Profile +from ..core.profile import Profile, ProfileSession from ..config import argparse as arg from ..config.default_context import DefaultContextBuilder from ..config.base import BaseError, BaseSettings from ..config.util import common_config from ..config.wallet import wallet_config -from ..messaging.models.base_record import BaseRecord +from ..messaging.models.base import BaseModelError +from ..messaging.models.base_record import BaseRecord, RecordType from ..storage.base import BaseStorage from ..storage.error import StorageNotFoundError from ..storage.record import StorageRecord +from ..revocation.models.issuer_rev_reg_record import IssuerRevRegRecord from ..utils.classloader import ClassLoader, ClassNotFoundError from ..version import __version__, RECORD_TYPE_ACAPY_VERSION @@ -78,10 +81,10 @@ def setup_version_upgrade_config(self, path: str): """Set ups config dict from the provided YML file.""" with open(path, "r") as stream: config_dict = yaml.safe_load(stream) - version_config_dict = {} - for version, provided_config in config_dict.items(): + tagged_config_dict = {} + for config_id, provided_config in config_dict.items(): recs_list = [] - version_config_dict[version] = {} + tagged_config_dict[config_id] = {} if "resave_records" in provided_config: if provided_config.get("resave_records").get("base_record_path"): recs_list = recs_list + provided_config.get( @@ -93,14 +96,14 @@ def setup_version_upgrade_config(self, path: str): recs_list = recs_list + provided_config.get( "resave_records" ).get("base_exch_record_path") - version_config_dict[version]["resave_records"] = recs_list + tagged_config_dict[config_id]["resave_records"] = recs_list config_key_set = set(provided_config.keys()) try: config_key_set.remove("resave_records") except KeyError: pass if "explicit_upgrade" in provided_config: - version_config_dict[version][ + tagged_config_dict[config_id][ "explicit_upgrade" ] = provided_config.get("explicit_upgrade") try: @@ -108,12 +111,12 @@ def setup_version_upgrade_config(self, path: str): except KeyError: pass for executable in config_key_set: - version_config_dict[version][executable] = ( + tagged_config_dict[config_id][executable] = ( provided_config.get(executable) or False ) - if version_config_dict == {}: + if tagged_config_dict == {}: raise UpgradeError(f"No version configs found in {path}") - self.upgrade_configs = version_config_dict + self.upgrade_configs = tagged_config_dict def get_callable(self, executable: str) -> Optional[Callable]: """Return callable function for executable name.""" @@ -159,9 +162,12 @@ def get_upgrade_version_list( if not sorted_version_list: version_upgrade_config_inst = VersionUpgradeConfig(config_path) upgrade_configs = version_upgrade_config_inst.upgrade_configs - versions_found_in_config = upgrade_configs.keys() + tags_found_in_config = upgrade_configs.keys() + version_found_in_config, _ = _get_version_and_name_tags( + list(tags_found_in_config) + ) sorted_version_list = sorted( - versions_found_in_config, key=lambda x: package_version.parse(x) + version_found_in_config, key=lambda x: package_version.parse(x) ) version_list = [] @@ -193,6 +199,46 @@ async def add_version_record(profile: Profile, version: str): LOGGER.info(f"{RECORD_TYPE_ACAPY_VERSION} storage record set to {version}") +def _get_version_and_name_tags(tags_found_in_config: List) -> Tuple[List, List]: + """Get version and named tag key lists from config.""" + version_found_in_config = [] + named_tag_found_in_config = [] + for tag in tags_found_in_config: + try: + package_version.parse(tag) + version_found_in_config.append(tag) + except package_version.InvalidVersion: + named_tag_found_in_config.append(tag) + return version_found_in_config, named_tag_found_in_config + + +def _perform_upgrade( + upgrade_config: dict, + resave_record_path_sets: set, + executables_call_set: set, + tag: str, +) -> Tuple[set, set]: + """Update and return resave record path and executables call sets.""" + LOGGER.info(f"Running upgrade process for {tag}") + # Step 1 re-saving all BaseRecord and BaseExchangeRecord + if "resave_records" in upgrade_config: + resave_record_paths = upgrade_config.get("resave_records") + for record_path in resave_record_paths: + resave_record_path_sets.add(record_path) + + # Step 2 Update existing records, if required + config_key_set = set(upgrade_config.keys()) + try: + config_key_set.remove("resave_records") + except KeyError: + pass + for callable_name in list(config_key_set): + if upgrade_config.get(callable_name) is False: + continue + executables_call_set.add(callable_name) + return resave_record_path_sets, executables_call_set + + async def upgrade( settings: Optional[Union[Mapping[str, Any], BaseSettings]] = None, profile: Optional[Profile] = None, @@ -211,11 +257,18 @@ async def upgrade( version_upgrade_config_inst = VersionUpgradeConfig( settings.get("upgrade.config_path") ) + upgrade_from_tags = None + force_upgrade_flag = settings.get("upgrade.force_upgrade") or False + if force_upgrade_flag: + upgrade_from_tags = settings.get("upgrade.named_tags") upgrade_configs = version_upgrade_config_inst.upgrade_configs upgrade_to_version = f"v{__version__}" - versions_found_in_config = upgrade_configs.keys() + tags_found_in_config = upgrade_configs.keys() + version_found_in_config, named_tag_found_in_config = _get_version_and_name_tags( + list(tags_found_in_config) + ) sorted_versions_found_in_config = sorted( - versions_found_in_config, key=lambda x: package_version.parse(x) + version_found_in_config, key=lambda x: package_version.parse(x) ) upgrade_from_version_storage = None upgrade_from_version_config = None @@ -240,7 +293,6 @@ async def upgrade( ) ) - force_upgrade_flag = settings.get("upgrade.force_upgrade") or False if upgrade_from_version_storage and upgrade_from_version_config: if ( package_version.parse(upgrade_from_version_storage) @@ -261,103 +313,100 @@ async def upgrade( and not upgrade_from_version_config ): upgrade_from_version = upgrade_from_version_storage - if not upgrade_from_version: + if not upgrade_from_version and not upgrade_from_tags: raise UpgradeError( - "No upgrade from version found in wallet or settings [--from-version]" + "No upgrade from version or tags found in wallet" + " or settings [--from-version or --named-tag]" ) - upgrade_version_in_config = get_upgrade_version_list( - sorted_version_list=sorted_versions_found_in_config, - from_version=upgrade_from_version, - ) - # Perform explicit upgrade check if the function was called during startup - if profile: - ( - explicit_flag, - to_skip_explicit_versions, - explicit_upg_ver, - ) = explicit_upgrade_required_check( - to_apply_version_list=upgrade_version_in_config, - upgrade_config=upgrade_configs, - ) - if explicit_flag: - raise UpgradeError( - "Explicit upgrade flag with critical value found " - f"for {explicit_upg_ver} config. Please use ACA-Py " - "upgrade command to complete the process and proceed." - ) - if len(to_skip_explicit_versions) >= 1: - LOGGER.warning( - "Explicit upgrade flag with warning value found " - f"for {str(to_skip_explicit_versions)} versions. " - "Proceeding with ACA-Py startup. You can apply " - "the explicit upgrades using the ACA-Py upgrade " - "command later." - ) - return + resave_record_path_sets = set() + executables_call_set = set() to_update_flag = False - if upgrade_from_version == upgrade_to_version: - LOGGER.info( + if upgrade_from_version: + upgrade_version_in_config = get_upgrade_version_list( + sorted_version_list=sorted_versions_found_in_config, + from_version=upgrade_from_version, + ) + # Perform explicit upgrade check if the function was called during startup + if profile: ( - f"Version {upgrade_from_version} to upgrade from and " - f"current version to upgrade to {upgrade_to_version} " - "are same. You can apply upgrade from a lower " - "version by running the upgrade command with " - f"--from-version [< {upgrade_to_version}] and " - "--force-upgrade" + explicit_flag, + to_skip_explicit_versions, + explicit_upg_ver, + ) = explicit_upgrade_required_check( + to_apply_version_list=upgrade_version_in_config, + upgrade_config=upgrade_configs, ) - ) - else: - resave_record_path_sets = set() - executables_call_set = set() - for config_from_version in upgrade_version_in_config: - LOGGER.info(f"Running upgrade process for {config_from_version}") - upgrade_config = upgrade_configs.get(config_from_version) - # Step 1 re-saving all BaseRecord and BaseExchangeRecord - if "resave_records" in upgrade_config: - resave_record_paths = upgrade_config.get("resave_records") - for record_path in resave_record_paths: - resave_record_path_sets.add(record_path) - - # Step 2 Update existing records, if required - config_key_set = set(upgrade_config.keys()) - try: - config_key_set.remove("resave_records") - except KeyError: - pass - for callable_name in list(config_key_set): - if upgrade_config.get(callable_name) is False: - continue - executables_call_set.add(callable_name) - - if len(resave_record_path_sets) >= 1 or len(executables_call_set) >= 1: - to_update_flag = True - for record_path in resave_record_path_sets: - try: - rec_type = ClassLoader.load_class(record_path) - except ClassNotFoundError as err: - raise UpgradeError(f"Unknown Record type {record_path}") from err - if not issubclass(rec_type, BaseRecord): + if explicit_flag: raise UpgradeError( - f"Only BaseRecord can be resaved, found: {str(rec_type)}" + "Explicit upgrade flag with critical value found " + f"for {explicit_upg_ver} config. Please use ACA-Py " + "upgrade command to complete the process and proceed." ) - async with root_profile.session() as session: - all_records = await rec_type.query(session) - for record in all_records: - await record.save( - session, - reason="re-saving record during the upgrade process", - ) - if len(all_records) == 0: - LOGGER.info(f"No records of {str(rec_type)} found") - else: - LOGGER.info( - f"All recs of {str(rec_type)} successfully re-saved" - ) - for callable_name in executables_call_set: - _callable = version_upgrade_config_inst.get_callable(callable_name) - if not _callable: - raise UpgradeError(f"No function specified for {callable_name}") - await _callable(root_profile) + if len(to_skip_explicit_versions) >= 1: + LOGGER.warning( + "Explicit upgrade flag with warning value found " + f"for {str(to_skip_explicit_versions)} versions. " + "Proceeding with ACA-Py startup. You can apply " + "the explicit upgrades using the ACA-Py upgrade " + "command later." + ) + return + if upgrade_from_version == upgrade_to_version: + LOGGER.info( + ( + f"Version {upgrade_from_version} to upgrade from and " + f"current version to upgrade to {upgrade_to_version} " + "are same. You can apply upgrade from a lower " + "version by running the upgrade command with " + f"--from-version [< {upgrade_to_version}] and " + "--force-upgrade" + ) + ) + else: + for config_from_version in upgrade_version_in_config: + resave_record_path_sets, executables_call_set = _perform_upgrade( + upgrade_config=upgrade_configs.get(config_from_version), + resave_record_path_sets=resave_record_path_sets, + executables_call_set=executables_call_set, + tag=config_from_version, + ) + if upgrade_from_tags and len(upgrade_from_tags) >= 1: + for named_tag in upgrade_from_tags: + if named_tag not in named_tag_found_in_config: + continue + resave_record_path_sets, executables_call_set = _perform_upgrade( + upgrade_config=upgrade_configs.get(named_tag), + resave_record_path_sets=resave_record_path_sets, + executables_call_set=executables_call_set, + tag=named_tag, + ) + if len(resave_record_path_sets) >= 1 or len(executables_call_set) >= 1: + to_update_flag = True + for record_path in resave_record_path_sets: + try: + rec_type = ClassLoader.load_class(record_path) + except ClassNotFoundError as err: + raise UpgradeError(f"Unknown Record type {record_path}") from err + if not issubclass(rec_type, BaseRecord): + raise UpgradeError( + f"Only BaseRecord can be resaved, found: {str(rec_type)}" + ) + async with root_profile.session() as session: + all_records = await rec_type.query(session) + for record in all_records: + await record.save( + session, + reason="re-saving record during the upgrade process", + ) + if len(all_records) == 0: + LOGGER.info(f"No records of {str(rec_type)} found") + else: + LOGGER.info(f"All recs of {str(rec_type)} successfully re-saved") + for callable_name in executables_call_set: + _callable = version_upgrade_config_inst.get_callable(callable_name) + if not _callable: + raise UpgradeError(f"No function specified for {callable_name}") + await _callable(root_profile) # Update storage version if to_update_flag: @@ -394,6 +443,67 @@ async def update_existing_records(profile: Profile): pass +########################################################## +# Fix for ACA-Py Issue #2485 +# issuance_type attribue in IssuerRevRegRecord was removed +# in 0.5.3 version. IssuerRevRegRecord created previously +# will need +########################################################## + + +async def find_affected_issue_rev_reg_records( + session: ProfileSession, +) -> Sequence[RecordType]: + """Get IssuerRevRegRecord records with issuance_type for re-saving. + + Args: + session: The profile session to use + """ + storage = session.inject(BaseStorage) + rows = await storage.find_all_records( + IssuerRevRegRecord.RECORD_TYPE, + ) + issue_rev_reg_records_to_update = [] + for record in rows: + vals = json.loads(record.value) + to_update = False + try: + record_id = record.id + record_id_name = IssuerRevRegRecord.RECORD_ID_NAME + if record_id_name in vals: + raise ValueError(f"Duplicate {record_id_name} inputs; {vals}") + params = dict(**vals) + # Check for issuance_type and add record_id for later tracking + if "issuance_type" in params: + LOGGER.info( + f"IssuerRevRegRecord {record_id} tagged for fixing issuance_type." + ) + del params["issuance_type"] + to_update = True + params[record_id_name] = record_id + if to_update: + issue_rev_reg_records_to_update.append(IssuerRevRegRecord(**params)) + except BaseModelError as err: + raise BaseModelError(f"{err}, for record id {record.id}") + return issue_rev_reg_records_to_update + + +async def fix_issue_rev_reg_records(profile: Profile): + """Update IssuerRevRegRecord records. + + Args: + profile: Root profile + + """ + async with profile.session() as session: + issue_rev_reg_records = await find_affected_issue_rev_reg_records(session) + for record in issue_rev_reg_records: + await record.save( + session, + reason="re-saving issue_rev_reg record without issuance type", + ) + + def execute(argv: Sequence[str] = None): """Entrypoint.""" parser = arg.create_argument_parser(prog=PROG) @@ -413,7 +523,8 @@ def main(): UPGRADE_EXISTING_RECORDS_FUNCTION_MAPPING = { - "update_existing_records": update_existing_records + "update_existing_records": update_existing_records, + "fix_issue_rev_reg_records": fix_issue_rev_reg_records, } main() diff --git a/aries_cloudagent/config/argparse.py b/aries_cloudagent/config/argparse.py index 143a521da6..9e1da6410e 100644 --- a/aries_cloudagent/config/argparse.py +++ b/aries_cloudagent/config/argparse.py @@ -1144,7 +1144,8 @@ def add_arguments(self, parser: ArgumentParser): help=( "Emit protocol messages with new DIDComm prefix; i.e., " "'https://didcomm.org/' instead of (default) prefix " - "'did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/'." + "'did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/'. " + "Forced to `true` as the old prefix must never be used." ), ) parser.add_argument( @@ -1154,7 +1155,8 @@ def add_arguments(self, parser: ArgumentParser): help=( "Send packed agent messages with the DIDComm MIME type " "as of RFC 0044; i.e., 'application/didcomm-envelope-enc' " - "instead of 'application/ssi-agent-wire'." + "instead of 'application/ssi-agent-wire'. " + "Forced to `true` as the old MIME type must never be used." ), ) parser.add_argument( @@ -1225,10 +1227,10 @@ def get_settings(self, args: Namespace) -> dict: raise ArgsParseError("Error writing trace event " + str(e)) if args.preserve_exchange_records: settings["preserve_exchange_records"] = True - if args.emit_new_didcomm_prefix: - settings["emit_new_didcomm_prefix"] = True - if args.emit_new_didcomm_mime_type: - settings["emit_new_didcomm_mime_type"] = True + # NOT setting the following two parameters `True` is no longer supported + # Even if the args are not set, the config setting is True. + settings["emit_new_didcomm_prefix"] = True + settings["emit_new_didcomm_mime_type"] = True if args.exch_use_unencrypted_tags: settings["exch_use_unencrypted_tags"] = True environ["EXCH_UNENCRYPTED_TAGS"] = "True" @@ -2076,6 +2078,13 @@ def add_arguments(self, parser: ArgumentParser): ), ) + parser.add_argument( + "--named-tag", + action="append", + env_var="ACAPY_UPGRADE_NAMED_TAGS", + help=("Runs upgrade steps associated with tags provided in the config"), + ) + def get_settings(self, args: Namespace) -> dict: """Extract ACA-Py upgrade process settings.""" settings = {} @@ -2085,4 +2094,8 @@ def get_settings(self, args: Namespace) -> dict: settings["upgrade.from_version"] = args.from_version if args.force_upgrade: settings["upgrade.force_upgrade"] = args.force_upgrade + if args.named_tag: + settings["upgrade.named_tags"] = ( + list(args.named_tag) if args.named_tag else [] + ) return settings diff --git a/aries_cloudagent/messaging/models/base_record.py b/aries_cloudagent/messaging/models/base_record.py index 3e94c58a5c..9ebbf74ff6 100644 --- a/aries_cloudagent/messaging/models/base_record.py +++ b/aries_cloudagent/messaging/models/base_record.py @@ -81,11 +81,11 @@ class Meta: def __init__( self, - id: str = None, - state: str = None, + id: Optional[str] = None, + state: Optional[str] = None, *, - created_at: Union[str, datetime] = None, - updated_at: Union[str, datetime] = None, + created_at: Union[str, datetime, None] = None, + updated_at: Union[str, datetime, None] = None, new_with_id: bool = False, ): """Initialize a new BaseRecord.""" diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index e2e47eb6cf..08bf05f8a7 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -200,6 +200,26 @@ def __init__(self): ) +class NonSDList(Regexp): + """Validate NonSD List.""" + + EXAMPLE = [ + "name", + "address", + "address.street_address", + "nationalities[1:3]", + ] + PATTERN = r"[a-z0-9:\[\]_\.@?\(\)]" + + def __init__(self): + """Initialize the instance.""" + + super().__init__( + NonSDList.PATTERN, + error="Value {input} is not a valid NonSDList", + ) + + class JSONWebToken(Regexp): """Validate JSON Web Token.""" @@ -208,7 +228,7 @@ class JSONWebToken(Regexp): "eyJhIjogIjAifQ." "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" ) - PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*$" + PATTERN = r"^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$" def __init__(self): """Initialize the instance.""" @@ -219,6 +239,28 @@ def __init__(self): ) +class SDJSONWebToken(Regexp): + """Validate SD-JSON Web Token.""" + + EXAMPLE = ( + "eyJhbGciOiJFZERTQSJ9." + "eyJhIjogIjAifQ." + "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" + "~WyJEM3BUSFdCYWNRcFdpREc2TWZKLUZnIiwgIkRFIl0" + "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" + "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" + ) + PATTERN = r"^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+(?:~[a-zA-Z0-9._-]+)*~?$" + + def __init__(self): + """Initialize the instance.""" + + super().__init__( + SDJSONWebToken.PATTERN, + error="Value {input} is not a valid SD-JSON Web token", + ) + + class DIDKey(Regexp): """Validate value against DID key specification.""" @@ -800,9 +842,15 @@ def __init__( JWS_HEADER_KID_VALIDATE = JWSHeaderKid() JWS_HEADER_KID_EXAMPLE = JWSHeaderKid.EXAMPLE +NON_SD_LIST_VALIDATE = NonSDList() +NON_SD_LIST_EXAMPLE = NonSDList().EXAMPLE + JWT_VALIDATE = JSONWebToken() JWT_EXAMPLE = JSONWebToken.EXAMPLE +SD_JWT_VALIDATE = SDJSONWebToken() +SD_JWT_EXAMPLE = SDJSONWebToken.EXAMPLE + DID_KEY_VALIDATE = DIDKey() DID_KEY_EXAMPLE = DIDKey.EXAMPLE diff --git a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py index fbd3a8f6fe..b881d2fd34 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -463,6 +463,7 @@ async def test_create_request_multitenant(self): async def test_create_request_mediation_id(self): mediation_record = MediationRecord( + mediation_id="test_medation_id", role=MediationRecord.ROLE_CLIENT, state=MediationRecord.STATE_GRANTED, connection_id=self.test_mediator_conn_id, @@ -866,6 +867,7 @@ async def test_create_response_multitenant(self): ) mediation_record = MediationRecord( + mediation_id="test_medation_id", role=MediationRecord.ROLE_CLIENT, state=MediationRecord.STATE_GRANTED, connection_id=self.test_mediator_conn_id, @@ -936,6 +938,7 @@ async def test_create_response_bad_state(self): async def test_create_response_mediation(self): mediation_record = MediationRecord( + mediation_id="test_medation_id", role=MediationRecord.ROLE_CLIENT, state=MediationRecord.STATE_GRANTED, connection_id=self.test_mediator_conn_id, diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_deny_handler.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_deny_handler.py index 86471117d0..08a8b42c97 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_deny_handler.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_deny_handler.py @@ -11,8 +11,6 @@ from ..mediation_deny_handler import MediationDenyHandler TEST_CONN_ID = "conn-id" -TEST_MEDIATOR_TERMS = ["test", "mediator", "terms"] -TEST_RECIPIENT_TERMS = ["test", "recipient", "terms"] class TestMediationDenyHandler(AsyncTestCase): @@ -22,9 +20,7 @@ async def setUp(self): """Setup test dependencies.""" self.context = RequestContext.test_context() self.session = await self.context.session() - self.context.message = MediationDeny( - mediator_terms=TEST_MEDIATOR_TERMS, recipient_terms=TEST_RECIPIENT_TERMS - ) + self.context.message = MediationDeny() self.context.connection_ready = True self.context.connection_record = ConnRecord(connection_id=TEST_CONN_ID) @@ -50,5 +46,3 @@ async def test_handler(self): ) assert record assert record.state == MediationRecord.STATE_DENIED - assert record.mediator_terms == TEST_MEDIATOR_TERMS - assert record.recipient_terms == TEST_RECIPIENT_TERMS diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py index 8a5a1143ec..0ab45b1434 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py @@ -139,11 +139,8 @@ async def receive_request( "MediationRecord already exists for connection" ) - # TODO: Determine if terms are acceptable record = MediationRecord( connection_id=connection_id, - mediator_terms=request.mediator_terms, - recipient_terms=request.recipient_terms, ) await record.save(session, reason="New mediation request received") return record @@ -186,19 +183,11 @@ async def grant_request( async def deny_request( self, mediation_id: str, - *, - mediator_terms: Sequence[str] = None, - recipient_terms: Sequence[str] = None, ) -> Tuple[MediationRecord, MediationDeny]: """Deny a mediation request and prepare a deny message. Args: mediation_id: mediation record ID to deny - mediator_terms (Sequence[str]): updated mediator terms to return to - requester. - recipient_terms (Sequence[str]): updated recipient terms to return to - requester. - Returns: MediationDeny: message to return to denied client. @@ -215,9 +204,7 @@ async def deny_request( mediation_record.state = MediationRecord.STATE_DENIED await mediation_record.save(session, reason="Mediation request denied") - deny = MediationDeny( - mediator_terms=mediator_terms, recipient_terms=recipient_terms - ) + deny = MediationDeny() return mediation_record, deny async def _handle_keylist_update_add( @@ -442,15 +429,11 @@ async def clear_default_mediator(self): async def prepare_request( self, connection_id: str, - mediator_terms: Sequence[str] = None, - recipient_terms: Sequence[str] = None, ) -> Tuple[MediationRecord, MediationRequest]: """Prepare a MediationRequest Message, saving a new mediation record. Args: connection_id (str): ID representing mediator - mediator_terms (Sequence[str]): mediator_terms - recipient_terms (Sequence[str]): recipient_terms Returns: MediationRequest: message to send to mediator @@ -459,15 +442,11 @@ async def prepare_request( record = MediationRecord( role=MediationRecord.ROLE_CLIENT, connection_id=connection_id, - mediator_terms=mediator_terms, - recipient_terms=recipient_terms, ) async with self._profile.session() as session: await record.save(session, reason="Creating new mediation request.") - request = MediationRequest( - mediator_terms=mediator_terms, recipient_terms=recipient_terms - ) + request = MediationRequest() return record, request async def request_granted(self, record: MediationRecord, grant: MediationGrant): @@ -495,9 +474,6 @@ async def request_denied(self, record: MediationRecord, deny: MediationDeny): """ record.state = MediationRecord.STATE_DENIED - # TODO Record terms elsewhere? - record.mediator_terms = deny.mediator_terms - record.recipient_terms = deny.recipient_terms async with self._profile.session() as session: await record.save(session, reason="Mediation request denied.") diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_deny.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_deny.py index bd6496ab63..b37f58dba7 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_deny.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_deny.py @@ -1,8 +1,5 @@ """mediate-deny message used to notify mediation client of a denied mediation request.""" -from typing import Sequence - -from marshmallow import fields from .....messaging.agent_message import AgentMessage, AgentMessageSchema from ..message_types import MEDIATE_DENY, PROTOCOL_PACKAGE @@ -24,20 +21,10 @@ class Meta: def __init__( self, - *, - mediator_terms: Sequence[str] = None, - recipient_terms: Sequence[str] = None, **kwargs, ): - """Initialize mediation deny object. - - Args: - mediator_terms: Terms that were agreed by the recipient - recipient_terms: Terms that recipient wants to mediator to agree to - """ + """Initialize mediation deny object.""" super(MediationDeny, self).__init__(**kwargs) - self.mediator_terms = list(mediator_terms) if mediator_terms else [] - self.recipient_terms = list(recipient_terms) if recipient_terms else [] class MediationDenySchema(AgentMessageSchema): @@ -47,12 +34,3 @@ class Meta: """Mediation deny schema metadata.""" model_class = MediationDeny - - mediator_terms = fields.List( - fields.Str(metadata={"description": "Terms for mediator to agree"}), - required=False, - ) - recipient_terms = fields.List( - fields.Str(metadata={"description": "Terms for recipient to agree"}), - required=False, - ) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_grant.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_grant.py index 587f61730a..d2595ede53 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_grant.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_grant.py @@ -3,7 +3,7 @@ Used to notify mediation client of a granted mediation request. """ -from typing import Sequence +from typing import Optional, Sequence from marshmallow import fields @@ -29,8 +29,8 @@ class Meta: def __init__( self, *, - endpoint: str = None, - routing_keys: Sequence[str] = None, + endpoint: Optional[str] = None, + routing_keys: Optional[Sequence[str]] = None, **kwargs, ): """Initialize mediation grant object. diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_request.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_request.py index 84417ab830..2d47dc45eb 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_request.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_request.py @@ -1,8 +1,5 @@ """mediate-request message used to request mediation from a mediator.""" -from typing import Sequence - -from marshmallow import fields from .....messaging.agent_message import AgentMessage, AgentMessageSchema from ..message_types import MEDIATE_REQUEST, PROTOCOL_PACKAGE @@ -22,22 +19,9 @@ class Meta: message_type = MEDIATE_REQUEST schema_class = "MediationRequestSchema" - def __init__( - self, - *, - mediator_terms: Sequence[str] = None, - recipient_terms: Sequence[str] = None, - **kwargs, - ): - """Initialize mediation request object. - - Args: - mediator_terms: Mediator's terms for granting mediation. - recipient_terms: Recipient's proposed mediation terms. - """ + def __init__(self, **kwargs): + """Initialize mediation request object.""" super(MediationRequest, self).__init__(**kwargs) - self.mediator_terms = list(mediator_terms) if mediator_terms else [] - self.recipient_terms = list(recipient_terms) if recipient_terms else [] class MediationRequestSchema(AgentMessageSchema): @@ -47,27 +31,3 @@ class Meta: """Mediation request schema metadata.""" model_class = MediationRequest - - mediator_terms = fields.List( - fields.Str( - metadata={ - "description": ( - "Indicate terms that the mediator requires the recipient to" - " agree to" - ) - } - ), - required=False, - metadata={"description": "List of mediator rules for recipient"}, - ) - recipient_terms = fields.List( - fields.Str( - metadata={ - "description": ( - "Indicate terms that the recipient requires the mediator to" - " agree to" - ) - } - ), - required=False, - ) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_deny.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_deny.py index 5b82e5430e..bff806b0ed 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_deny.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_deny.py @@ -12,4 +12,4 @@ class TestMediateDeny(MessageTest, TestCase): TYPE = MEDIATE_DENY CLASS = MediationDeny SCHEMA = MediationDenySchema - VALUES = {"mediator_terms": ["test", "terms"], "recipient_terms": ["test", "terms"]} + VALUES = {} diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_request.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_request.py index fa99cf71b9..bb9ee0713b 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_request.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_request.py @@ -12,4 +12,4 @@ class TestMediateRequest(MessageTest, TestCase): TYPE = MEDIATE_REQUEST CLASS = MediationRequest SCHEMA = MediationRequestSchema - VALUES = {"mediator_terms": ["test", "terms"], "recipient_terms": ["test", "terms"]} + VALUES = {} diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/mediation_record.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/mediation_record.py index 6e3af3c726..f616864a47 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/mediation_record.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/mediation_record.py @@ -1,6 +1,6 @@ """Store state for Mediation requests.""" -from typing import Sequence +from typing import Optional, Sequence from marshmallow import EXCLUDE, fields @@ -33,14 +33,15 @@ class Meta: def __init__( self, *, - mediation_id: str = None, - state: str = None, - role: str = None, - connection_id: str = None, - mediator_terms: Sequence[str] = None, - recipient_terms: Sequence[str] = None, - routing_keys: Sequence[str] = None, - endpoint: str = None, + mediation_id: Optional[str] = None, + state: Optional[str] = None, + role: Optional[str] = None, + connection_id: Optional[str] = None, + routing_keys: Optional[Sequence[str]] = None, + endpoint: Optional[str] = None, + # Included for record backwards compat + mediator_terms: Optional[Sequence[str]] = None, + recipient_terms: Optional[Sequence[str]] = None, **kwargs, ): """__init__. @@ -50,8 +51,6 @@ def __init__( state (str): state, defaults to 'request_received' role (str): role in mediation, defaults to 'server' connection_id (str): ID of connection requesting or managing mediation - mediator_terms (Sequence[str]): mediator_terms - recipient_terms (Sequence[str]): recipient_terms routing_keys (Sequence[str]): keys in mediator control used to receive incoming messages endpoint (str): mediators endpoint @@ -61,8 +60,6 @@ def __init__( super().__init__(mediation_id, state or self.STATE_REQUEST, **kwargs) self.role = role if role else self.ROLE_SERVER self.connection_id = connection_id - self.mediator_terms = list(mediator_terms) if mediator_terms else [] - self.recipient_terms = list(recipient_terms) if recipient_terms else [] self.routing_keys = list(routing_keys) if routing_keys else [] self.endpoint = endpoint @@ -79,6 +76,8 @@ def __eq__(self, other: "MediationRecord"): @property def mediation_id(self) -> str: """Get Mediation ID.""" + if not self._id: + raise ValueError("Record not yet stored") return self._id @property @@ -109,8 +108,6 @@ def record_value(self) -> dict: return { prop: getattr(self, prop) for prop in ( - "mediator_terms", - "recipient_terms", "routing_keys", "endpoint", ) @@ -170,10 +167,12 @@ class Meta: mediation_id = fields.Str(required=False) role = fields.Str(required=True) connection_id = fields.Str(required=True) - mediator_terms = fields.List(fields.Str(), required=False) - recipient_terms = fields.List(fields.Str(), required=False) routing_keys = fields.List( fields.Str(validate=DID_KEY_VALIDATE, metadata={"example": DID_KEY_EXAMPLE}), required=False, ) endpoint = fields.Str(required=False) + + # Included for backwards compat with old records + mediator_terms = fields.List(fields.Str(), required=False) + recipient_terms = fields.List(fields.Str(), required=False) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/__init__.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/__init__.py new file mode 100644 index 0000000000..7c3c4e3c4d --- /dev/null +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for models.""" diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/test_mediation_record.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/test_mediation_record.py new file mode 100644 index 0000000000..546f1f2128 --- /dev/null +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/test_mediation_record.py @@ -0,0 +1,62 @@ +"""Test mediation record.""" +import json + +import pytest + +from ......core.in_memory import InMemoryProfile +from ......core.profile import ProfileSession +from ......storage.base import BaseStorage +from ......storage.record import StorageRecord +from ..mediation_record import MediationRecord + + +@pytest.fixture() +async def session(): + profile = InMemoryProfile.test_profile() + async with profile.session() as session: + yield session + + +@pytest.mark.asyncio +async def test_backwards_compat_terms(session: ProfileSession): + """Make sure old records can be loaded still.""" + + old_record = StorageRecord( + MediationRecord.RECORD_TYPE, + id="test_mediation_id", + value=json.dumps( + { + "state": "granted", + "mediator_terms": ["dummy_terms"], + "recipient_terms": ["dummy_terms"], + } + ), + ) + storage = session.inject(BaseStorage) + await storage.add_record(old_record) + + record = await MediationRecord.retrieve_by_id(session, old_record.id) + assert isinstance(record, MediationRecord) + assert record.mediation_id == "test_mediation_id" + assert record.state == "granted" + + +@pytest.mark.asyncio +async def test_mediation_record_eq(): + record_0 = MediationRecord(mediation_id="test_medation_id_0", endpoint="zero") + record_1 = MediationRecord(mediation_id="test_medation_id_1", endpoint="one") + assert record_0 != record_1 + + with pytest.raises(ValueError): + record_0.state = "bad state" + + +@pytest.mark.asyncio +async def test_mediation_record_duplicate_means_exists(session: ProfileSession): + await MediationRecord(connection_id="test_connection_id", endpoint="abc").save( + session + ) + await MediationRecord(connection_id="test_connection_id", endpoint="def").save( + session + ) + assert await MediationRecord.exists_for_connection_id(session, "test_connection_id") diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py index 82e04faabc..52b87058fd 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py @@ -62,32 +62,6 @@ ) -MEDIATOR_TERMS_SCHEMA = fields.List( - fields.Str( - metadata={ - "description": ( - "Indicate terms to which the mediator requires the recipient to agree" - ) - } - ), - required=False, - metadata={"description": "List of mediator rules for recipient"}, -) - - -RECIPIENT_TERMS_SCHEMA = fields.List( - fields.Str( - metadata={ - "description": ( - "Indicate terms to which the recipient requires the mediator to agree" - ) - } - ), - required=False, - metadata={"description": "List of recipient rules for mediation"}, -) - - class MediationListSchema(OpenAPISchema): """Result schema for mediation list query.""" @@ -101,24 +75,16 @@ class MediationListQueryStringSchema(OpenAPISchema): """Parameters and validators for mediation record list request query string.""" conn_id = CONNECTION_ID_SCHEMA - mediator_terms = MEDIATOR_TERMS_SCHEMA - recipient_terms = RECIPIENT_TERMS_SCHEMA state = MEDIATION_STATE_SCHEMA class MediationCreateRequestSchema(OpenAPISchema): """Parameters and validators for create Mediation request query string.""" - mediator_terms = MEDIATOR_TERMS_SCHEMA - recipient_terms = RECIPIENT_TERMS_SCHEMA - class AdminMediationDenySchema(OpenAPISchema): """Parameters and validators for Mediation deny admin request query string.""" - mediator_terms = MEDIATOR_TERMS_SCHEMA - recipient_terms = RECIPIENT_TERMS_SCHEMA - class MediationIdMatchInfoSchema(OpenAPISchema): """Path parameters and validators for request taking mediation request id.""" @@ -281,10 +247,6 @@ async def request_mediation(request: web.BaseRequest): conn_id = request.match_info["conn_id"] - body = await request.json() - mediator_terms = body.get("mediator_terms") - recipient_terms = body.get("recipient_terms") - try: async with profile.session() as session: connection_record = await ConnRecord.retrieve_by_id(session, conn_id) @@ -300,11 +262,7 @@ async def request_mediation(request: web.BaseRequest): mediation_record, mediation_request = await MediationManager( profile - ).prepare_request( - connection_id=conn_id, - mediator_terms=mediator_terms, - recipient_terms=recipient_terms, - ) + ).prepare_request(connection_id=conn_id) result = mediation_record.serialize() except StorageNotFoundError as err: @@ -348,15 +306,10 @@ async def mediation_request_deny(request: web.BaseRequest): context: AdminRequestContext = request["context"] outbound_handler = request["outbound_message_router"] mediation_id = request.match_info.get("mediation_id") - body = await request.json() - mediator_terms = body.get("mediator_terms") - recipient_terms = body.get("recipient_terms") try: mediation_manager = MediationManager(context.profile) record, deny_request = await mediation_manager.deny_request( mediation_id=mediation_id, - mediator_terms=mediator_terms, - recipient_terms=recipient_terms, ) result = record.serialize() except StorageNotFoundError as err: diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py index 69cdf9760f..2ebca54939 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py @@ -129,8 +129,6 @@ async def test_deny_request(self, manager): record = await manager.receive_request(TEST_CONN_ID, request) assert record.connection_id == TEST_CONN_ID record, deny = await manager.deny_request(record.mediation_id) - assert deny.mediator_terms == [] - assert deny.recipient_terms == [] async def test_update_keylist_delete(self, session, manager, record): """test_update_keylist_delete.""" @@ -215,20 +213,6 @@ async def test_get_keylist_no_granted_record(self, manager): with pytest.raises(MediationNotGrantedError): await manager.get_keylist(record) - async def test_mediation_record_eq(self): - record_0 = MediationRecord(endpoint="zero") - record_1 = MediationRecord(endpoint="one") - assert record_0 != record_1 - assert record_0 != ValueError("not a mediation record") - - with pytest.raises(ValueError): - record_0.state = "bad state" - - async def test_mediation_record_duplicate_means_exists(self, session): - await MediationRecord(connection_id=TEST_CONN_ID, endpoint="abc").save(session) - await MediationRecord(connection_id=TEST_CONN_ID, endpoint="def").save(session) - assert await MediationRecord.exists_for_connection_id(session, TEST_CONN_ID) - async def test_create_keylist_query_response(self, session, manager, record): """test_create_keylist_query_response.""" await RouteRecord(connection_id=TEST_CONN_ID, recipient_key=TEST_VERKEY).save( diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py index ababf0ea16..d4b3a83c99 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py @@ -33,8 +33,6 @@ def setUp(self): "state": "granted", "role": "server", "connection_id": "c3dd00cf-f6a2-4ddf-93d8-49ae74bdacef", - "mediator_terms": [], - "recipient_terms": [], "routing_keys": ["EwUKjVLboiLSuoWSEtDvrgrd41EUxG5bLecQrkHB63Up"], "endpoint": "http://192.168.1.13:3005", "created_at": "1234567890", @@ -212,10 +210,7 @@ async def test_delete_mediation_request_x_storage_error(self): await test_module.delete_mediation_request(self.request) async def test_request_mediation(self): - body = { - "mediator_terms": ["meaningless string because terms are not used"], - "recipient_terms": ["meaningless string because terms are not a 'thing'"], - } + body = {} self.request.json.return_value = body with async_mock.patch.object( test_module, "MediationManager", autospec=True @@ -243,10 +238,7 @@ async def test_request_mediation(self): self.outbound_message_router.assert_called() async def test_request_mediation_x_conn_not_ready(self): - body = { - "mediator_terms": ["meaningless string because terms are not used"], - "recipient_terms": ["meaningless string because terms are not a 'thing'"], - } + body = {} self.request.json.return_value = body with async_mock.patch.object( test_module.ConnRecord, @@ -259,10 +251,7 @@ async def test_request_mediation_x_conn_not_ready(self): assert "request connection is not ready" in exc.msg async def test_request_mediation_x_already_exists(self): - body = { - "mediator_terms": ["meaningless string because terms are not used"], - "recipient_terms": ["meaningless string because terms are not a 'thing'"], - } + body = {} self.request.json.return_value = body with async_mock.patch.object( test_module.ConnRecord, "retrieve_by_id", async_mock.CoroutineMock() @@ -277,10 +266,7 @@ async def test_request_mediation_x_already_exists(self): assert "already exists for connection" in exc.msg async def test_request_mediation_x_conn_not_found(self): - body = { - "mediator_terms": ["meaningless string because terms are not used"], - "recipient_terms": ["meaningless string because terms are not a 'thing'"], - } + body = {} self.request.json.return_value = body with async_mock.patch.object( test_module.ConnRecord, @@ -292,10 +278,7 @@ async def test_request_mediation_x_conn_not_found(self): await test_module.request_mediation(self.request) async def test_request_mediation_x_storage_error(self): - body = { - "mediator_terms": ["meaningless string because terms are not used"], - "recipient_terms": ["meaningless string because terms are not a 'thing'"], - } + body = {} self.request.json.return_value = body with async_mock.patch.object( test_module.ConnRecord, diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py index e00411efed..1e08f969fd 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py @@ -692,13 +692,8 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await report_problem( - err, - ProblemReportReason.ISSUANCE_ABANDONED.value, - web.HTTPBadRequest, - cred_ex_record or connection_record, - outbound_handler, - ) + # other party cannot yet receive a problem report about our failed protocol start + raise web.HTTPBadRequest(reason=err.roll_up) await outbound_handler( credential_proposal, @@ -903,13 +898,8 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await report_problem( - err, - ProblemReportReason.ISSUANCE_ABANDONED.value, - web.HTTPBadRequest, - cred_ex_record or connection_record, - outbound_handler, - ) + # other party cannot yet receive a problem report about our failed protocol start + raise web.HTTPBadRequest(reason=err.roll_up) await outbound_handler(credential_offer_message, connection_id=connection_id) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/handler.py index 02231f134d..f7e2ff8a43 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/handler.py @@ -303,9 +303,6 @@ async def _get_suite( did_info: DIDInfo = None, ): """Get signature suite for issuance of verification.""" - session = await self.profile.session() - wallet = session.inject(BaseWallet) - # Get signature class based on proof type SignatureClass = PROOF_TYPE_SIGNATURE_SUITE_MAPPING[proof_type] @@ -314,7 +311,7 @@ async def _get_suite( verification_method=verification_method, proof=proof, key_pair=WalletKeyPair( - wallet=wallet, + profile=self.profile, key_type=SIGNATURE_SUITE_KEY_TYPE_MAPPING[SignatureClass], public_key_base58=did_info.verkey if did_info else None, ), diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py index 721128b4ae..d1f0995624 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py @@ -835,13 +835,8 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await report_problem( - err, - ProblemReportReason.ISSUANCE_ABANDONED.value, - web.HTTPBadRequest, - cred_ex_record or conn_record, - outbound_handler, - ) + # other party cannot yet receive a problem report about our failed protocol start + raise web.HTTPBadRequest(reason=err.roll_up) await outbound_handler(cred_proposal_message, connection_id=connection_id) @@ -1042,13 +1037,8 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await report_problem( - err, - ProblemReportReason.ISSUANCE_ABANDONED.value, - web.HTTPBadRequest, - cred_ex_record or conn_record, - outbound_handler, - ) + # other party cannot yet receive a problem report about our failed protocol start + raise web.HTTPBadRequest(reason=err.roll_up) await outbound_handler(cred_offer_message, connection_id=connection_id) @@ -1252,13 +1242,8 @@ async def credential_exchange_send_free_request(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await report_problem( - err, - ProblemReportReason.ISSUANCE_ABANDONED.value, - web.HTTPBadRequest, - cred_ex_record, - outbound_handler, - ) + # other party cannot yet receive a problem report about our failed protocol start + raise web.HTTPBadRequest(reason=err.roll_up) await outbound_handler(cred_request_message, connection_id=connection_id) diff --git a/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py index 46492d8e87..5f70982eb2 100644 --- a/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py @@ -115,7 +115,6 @@ def __init__( async def _get_issue_suite( self, *, - wallet: BaseWallet, issuer_id: str, ): """Get signature suite for signing presentation.""" @@ -139,17 +138,13 @@ async def _get_issue_suite( return SignatureClass( verification_method=verification_method, key_pair=WalletKeyPair( - wallet=wallet, + profile=self.profile, key_type=self.ISSUE_SIGNATURE_SUITE_KEY_TYPE_MAPPING[SignatureClass], public_key_base58=did_info.verkey if did_info else None, ), ) - async def _get_derive_suite( - self, - *, - wallet: BaseWallet, - ): + async def _get_derive_suite(self): """Get signature suite for deriving credentials.""" # Get signature class based on proof type SignatureClass = self.DERIVED_PROOF_TYPE_SIGNATURE_SUITE_MAPPING[ @@ -159,7 +154,7 @@ async def _get_derive_suite( # Generically create signature class return SignatureClass( key_pair=WalletKeyPair( - wallet=wallet, + profile=self.profile, key_type=self.DERIVE_SIGNATURE_SUITE_KEY_TYPE_MAPPING[SignatureClass], ), ) @@ -406,18 +401,14 @@ async def filter_constraints( new_credential_dict = self.reveal_doc( credential_dict=credential_dict, constraints=constraints ) - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - derive_suite = await self._get_derive_suite( - wallet=wallet, - ) - signed_new_credential_dict = await derive_credential( - credential=credential_dict, - reveal_document=new_credential_dict, - suite=derive_suite, - document_loader=document_loader, - ) - credential = self.create_vcrecord(signed_new_credential_dict) + derive_suite = await self._get_derive_suite() + signed_new_credential_dict = await derive_credential( + credential=credential_dict, + reveal_document=new_credential_dict, + suite=derive_suite, + document_loader=document_loader, + ) + credential = self.create_vcrecord(signed_new_credential_dict) result.append(credential) return result @@ -1297,18 +1288,15 @@ async def create_vp( vp["presentation_submission"] = submission_property.serialize() if self.proof_type is BbsBlsSignature2020.signature_type: vp["@context"].append(SECURITY_CONTEXT_BBS_URL) - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - issue_suite = await self._get_issue_suite( - wallet=wallet, - issuer_id=issuer_id, - ) - signed_vp = await sign_presentation( - presentation=vp, - suite=issue_suite, - challenge=challenge, - document_loader=document_loader, - ) + issue_suite = await self._get_issue_suite( + issuer_id=issuer_id, + ) + signed_vp = await sign_presentation( + presentation=vp, + suite=issue_suite, + challenge=challenge, + document_loader=document_loader, + ) result_vp.append(signed_vp) if len(result_vp) == 1: return result_vp[0] diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py index 868d7148bf..8a656368f8 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py @@ -21,7 +21,6 @@ WalletKeyPair, ) from ......vc.vc_ld.verify import verify_presentation -from ......wallet.base import BaseWallet from ......wallet.key_type import ED25519, BLS12381G2 from .....problem_report.v1_0.message import ProblemReport @@ -65,13 +64,13 @@ class DIFPresFormatHandler(V20PresFormatHandler): ISSUE_SIGNATURE_SUITE_KEY_TYPE_MAPPING[BbsBlsSignature2020] = BLS12381G2 ISSUE_SIGNATURE_SUITE_KEY_TYPE_MAPPING[BbsBlsSignatureProof2020] = BLS12381G2 - async def _get_all_suites(self, wallet: BaseWallet): + async def _get_all_suites(self): """Get all supported suites for verifying presentation.""" suites = [] for suite, key_type in self.ISSUE_SIGNATURE_SUITE_KEY_TYPE_MAPPING.items(): suites.append( suite( - key_pair=WalletKeyPair(wallet=wallet, key_type=key_type), + key_pair=WalletKeyPair(profile=self._profile, key_type=key_type), ) ) return suites @@ -471,33 +470,31 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: presentation exchange record, updated """ - async with self._profile.session() as session: - wallet = session.inject(BaseWallet) - dif_proof = pres_ex_record.pres.attachment(DIFPresFormatHandler.format) - pres_request = pres_ex_record.pres_request.attachment( - DIFPresFormatHandler.format - ) - challenge = None - if "options" in pres_request: - challenge = pres_request["options"].get("challenge", str(uuid4())) - if not challenge: - challenge = str(uuid4()) - if isinstance(dif_proof, Sequence): - for proof in dif_proof: - pres_ver_result = await verify_presentation( - presentation=proof, - suites=await self._get_all_suites(wallet=wallet), - document_loader=self._profile.inject(DocumentLoader), - challenge=challenge, - ) - if not pres_ver_result.verified: - break - else: + dif_proof = pres_ex_record.pres.attachment(DIFPresFormatHandler.format) + pres_request = pres_ex_record.pres_request.attachment( + DIFPresFormatHandler.format + ) + challenge = None + if "options" in pres_request: + challenge = pres_request["options"].get("challenge", str(uuid4())) + if not challenge: + challenge = str(uuid4()) + if isinstance(dif_proof, Sequence): + for proof in dif_proof: pres_ver_result = await verify_presentation( - presentation=dif_proof, - suites=await self._get_all_suites(wallet=wallet), + presentation=proof, + suites=await self._get_all_suites(), document_loader=self._profile.inject(DocumentLoader), challenge=challenge, ) - pres_ex_record.verified = json.dumps(pres_ver_result.verified) - return pres_ex_record + if not pres_ver_result.verified: + break + else: + pres_ver_result = await verify_presentation( + presentation=dif_proof, + suites=await self._get_all_suites(), + document_loader=self._profile.inject(DocumentLoader), + challenge=challenge, + ) + pres_ex_record.verified = json.dumps(pres_ver_result.verified) + return pres_ex_record diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py index 8143083c0a..8986ff42ac 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py @@ -396,7 +396,7 @@ def test_validate_fields(self): self.handler.validate_fields(PRES_20, incorrect_pres) async def test_get_all_suites(self): - suites = await self.handler._get_all_suites(self.wallet) + suites = await self.handler._get_all_suites() assert len(suites) == 4 types = [ Ed25519Signature2018, diff --git a/aries_cloudagent/protocols/tests/test_didcomm_prefix.py b/aries_cloudagent/protocols/tests/test_didcomm_prefix.py index be0dd6b6dd..4328d539d0 100644 --- a/aries_cloudagent/protocols/tests/test_didcomm_prefix.py +++ b/aries_cloudagent/protocols/tests/test_didcomm_prefix.py @@ -16,6 +16,8 @@ def test_didcomm_prefix(self): f"{DIDCommPrefix.NEW.value}/hello" ) + # No longer possible to have the arg `False` but leaving in test + # Still want to be able to receive the OLD format, just not emit it DIDCommPrefix.set({"emit_new_didcomm_prefix": False}) assert environ.get("DIDCOMM_PREFIX") == DIDCommPrefix.OLD.value assert DIDCommPrefix.qualify_current("hello") == ( diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index ccb47e63ed..a027010937 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -1,6 +1,7 @@ """Issuer revocation registry storage handling.""" import json +import importlib import logging import uuid from functools import total_ordering @@ -13,6 +14,10 @@ from marshmallow import fields, validate from ...core.profile import Profile, ProfileSession +from ...indy.credx.issuer import ( + CATEGORY_CRED_DEF, + CATEGORY_REV_REG_DEF_PRIVATE, +) from ...indy.issuer import IndyIssuer, IndyIssuerError from ...indy.models.revocation import ( IndyRevRegDef, @@ -379,41 +384,59 @@ async def fix_ledger_entry( session, rev_reg_id=self.revoc_reg_id ) - revoked_ids = [] - for rec in recs: - if rec.state == IssuerCredRevRecord.STATE_REVOKED: - revoked_ids.append(int(rec.cred_rev_id)) - if int(rec.cred_rev_id) not in rev_reg_delta["value"]["revoked"]: - # await rec.set_state(session, IssuerCredRevRecord.STATE_ISSUED) - rec_count += 1 - - LOGGER.debug(">>> fixed entry recs count = %s", rec_count) - LOGGER.debug( - ">>> rev_reg_record.revoc_reg_entry.value: %s", - self.revoc_reg_entry.value, + revoked_ids = [] + for rec in recs: + if rec.state == IssuerCredRevRecord.STATE_REVOKED: + revoked_ids.append(int(rec.cred_rev_id)) + if int(rec.cred_rev_id) not in rev_reg_delta["value"]["revoked"]: + # await rec.set_state(session, IssuerCredRevRecord.STATE_ISSUED) + rec_count += 1 + + LOGGER.debug(">>> fixed entry recs count = %s", rec_count) + LOGGER.debug( + ">>> rev_reg_record.revoc_reg_entry.value: %s", + self.revoc_reg_entry.value, + ) + LOGGER.debug('>>> rev_reg_delta.get("value"): %s', rev_reg_delta.get("value")) + + # if we had any revocation discrepencies, check the accumulator value + if rec_count > 0: + if (self.revoc_reg_entry.value and rev_reg_delta.get("value")) and not ( + self.revoc_reg_entry.value.accum == rev_reg_delta["value"]["accum"] + ): + # self.revoc_reg_entry = rev_reg_delta["value"] + # await self.save(session) + accum_count += 1 + async with profile.session() as session: + issuer_rev_reg_record = ( + await IssuerRevRegRecord.retrieve_by_revoc_reg_id( + session, self.revoc_reg_id + ) + ) + cred_def_id = issuer_rev_reg_record.cred_def_id + _cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) + _rev_reg_def_private = await session.handle.fetch( + CATEGORY_REV_REG_DEF_PRIVATE, self.revoc_reg_id + ) + credx_module = importlib.import_module("indy_credx") + cred_defn = credx_module.CredentialDefinition.load(_cred_def.value_json) + rev_reg_defn_private = ( + credx_module.RevocationRegistryDefinitionPrivate.load( + _rev_reg_def_private.value_json + ) ) - LOGGER.debug( - '>>> rev_reg_delta.get("value"): %s', rev_reg_delta.get("value") + calculated_txn = await generate_ledger_rrrecovery_txn( + genesis_transactions, + self.revoc_reg_id, + revoked_ids, + cred_defn, + rev_reg_defn_private, ) + recovery_txn = json.loads(calculated_txn.to_json()) - # if we had any revocation discrepencies, check the accumulator value - if rec_count > 0: - if (self.revoc_reg_entry.value and rev_reg_delta.get("value")) and not ( - self.revoc_reg_entry.value.accum == rev_reg_delta["value"]["accum"] - ): - # self.revoc_reg_entry = rev_reg_delta["value"] - # await self.save(session) - accum_count += 1 - - calculated_txn = await generate_ledger_rrrecovery_txn( - genesis_transactions, - self.revoc_reg_id, - revoked_ids, - ) - recovery_txn = json.loads(calculated_txn.to_json()) - - LOGGER.debug(">>> apply_ledger_update = %s", apply_ledger_update) - if apply_ledger_update: + LOGGER.debug(">>> apply_ledger_update = %s", apply_ledger_update) + if apply_ledger_update: + async with profile.session() as session: ledger = session.inject_or(BaseLedger) if not ledger: reason = "No ledger available" @@ -426,7 +449,7 @@ async def fix_ledger_entry( self.revoc_reg_id, "CL_ACCUM", recovery_txn ) - applied_txn = ledger_response["result"] + applied_txn = ledger_response["result"] return (rev_reg_delta, recovery_txn, applied_txn) diff --git a/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py index 80d15592ea..bc292daeb4 100644 --- a/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py @@ -1,10 +1,14 @@ +import importlib import json from os.path import join +from typing import Any, Mapping, Type from asynctest import TestCase as AsyncTestCase, mock as async_mock -from ....core.in_memory import InMemoryProfile +from ....core.in_memory import InMemoryProfile, InMemoryProfileSession +from ....core.profile import Profile, ProfileSession +from ....config.injection_context import InjectionContext from ....indy.issuer import IndyIssuer, IndyIssuerError from ....indy.util import indy_client_dir from ....ledger.base import BaseLedger @@ -78,6 +82,191 @@ async def test_order(self): await rec1.save(session, reason="another record") assert rec0 < rec1 + async def test_fix_ledger_entry(self): + mock_cred_def = { + "ver": "1.0", + "id": "55GkHamhTU1ZbTbV2ab9DE:3:CL:15:tag", + "schemaId": "15", + "type": "CL", + "tag": "tag", + "value": { + "primary": { + "n": "97755077532337293488745809101840506138839796255951676757957095601046950252131007247574478925999370347259264200571203215922043340992124710162716447600735862203372184211636922189157817784130889637892277882125570534881418101997106854432924131454003284028861730869486619833238782461572414320131411215654018054873708704239239964147885524705957168226001184349267512368273047027640698617139590254809517853853130886625330234652284599369944951191468504513070325577275942459149020744004320427886430812173881305323670033871442431456536710164496981741573947141839358296893048748871620778358547174947418982842584086910766377426381", + "s": "93857461424756524831309204327541325286955427618197438652468158979904850495412765053870776608842922458588066684799662534588058064651312983375745531372709501399468055860133002734655992193122962210761579825894255323571233899698407599997585154150003119067126768230932260880730480485869205348502571475803409653237711195597513501909794351191631697504002513031303493339100149587811674358458553310268294288630892274988762794340699542957746255964836213015732087927295229891638172796974267746722775815971385993320758281434138548295910631509522670548769524207136946079991721576359187532370389806940124963337347442220291195381379", + "r": { + "attr": "47851812221002419738510348434589719299005636707459946297826216114810122000929029718256700990932773158900801032759636000731156275563407397654401429007893443548977293352519606768993036568324588287173469229957113813985349265315838049185336810444812307980611094706266979152047930521019494220669875858786312470775883694491178666122652050631237335108908878703921025524371908258656719798575775820756662770857250313841640739199537587617643616110506607528089021428997787577846819067855712454919148492951257992078447468260961148762693304228013316282905649275245604958548728173709772269908692834231109401462806738322298320014588", + "master_secret": "1456034034452917866826058280183727425788793902981406386175016190082488912409448091384258390876182506491481376203626852147024966574074505159095019883608712317385148607657338114099655296899988391276120507283428334300992065352980582993987058928311851237491145495605817715962285966152190021168736714801270287839185496030660074232737687865821410617700173791873244132463428232777257496952831001540198725563550215668732380420651897876237788070219793125826917973396212392661183288071986411608233468079014270033759863563636650489933951357555474357165922955337708555697826065745245669404874498188819137127409608428703992429087", + }, + "rctxt": "3181166664902801317779556377385630436151550717862204865421515198259158965590304479081007449054293128232193810709014084698474265818919401580417293157753663769438333622403413264724381527519123803324371803790394771682351074853790156764071298806108016312946683322202825645967662223488370365263607749486727675784672879635222504232881959377264213229748333060407839919218390751977625946072140500297691925789411870206929445018192788803161174534714033652405735420578422669164795825360682590769380466620583112381320768898271838621002245390378640966896034356391997262998321678222800285339726066703530463747958328257763842981029", + "z": "88882571993858813307170085778645601203164536169839951148860666045278872319305671063809074514017614163768224290285339562133145800179699201796197881847784025241295744277627025401945924707873790519590490294771194748427422972923806879605569348426345486261878031328212177481424474160598821738627280954513834712377303213119700398636139443890766009925681362795514758039170870017744796137650484010207378636277505131056606153648732287440383301414486959605962454376715897921966027018433464774363787319454306102402364604589381426633687528444586300536019182461458586285601075166524298872635741326726895166696312685388693673327358", + }, + "revocation": { + "g": "1 1ACDF1F880164FAE240C033DD2CC7E80130264E1A3BC81A3B2BD28E65827E1DA 1 0A4DD8DBB73B2A26CD2A576ECC0C5AB609346EDC923FFF20685EBE3B618A841A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "g_dash": "1 206CF4DECDE37A09315EE453D08E64CE6EFF4B89D6FCADFB456396AD1C57C442 1 14E36B42881BD9CA04608002810B4C9923BCC52D379A8F216FA57C6392A3BE83 1 138F620C8EEBFEAF9D1EF90152A11B9C0BDF3AED384324767E2293A4FC12F784 1 073227CD0CC0A6D5101A3CE03BDF6D29E6BACB33A090A4AE95F688F45BD54316 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + "h": "1 0B608579F0A9A830862E45AC4E97F5CB30FD118379A1C408363D92970DBE989C 1 1671922BCECA4E69B693851EA3C78CB3B2714CACAAB64DD5B5CF3C5A187A572A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h0": "1 1BCE3325E76EF9FA7566732BD17B5B82B1D0E9559B393D368A0BD09761AE2A39 1 10A1DA70F60369FBD69742B1564E98C85188312CFCCDD8AD5D11978F3FA1376D 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h1": "1 145B2DB05BFAAB9AF90401897E318B73BCEE7178D2857A551561DE0893098137 1 17A23E4161EF4392A56F13B2B0F40E0FA1DD28CDBD3FF33FDD67C5D998A10B6D 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h2": "1 14E18A1496B043246A4A860159EFCF49621606AF3E46A9854B8949E9F8C0FD97 1 1EFE5D486448A8F7A44BE358B674C0CF6FF09AAFDDDA416462943311CCA1A291 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "htilde": "1 152FBD85CD4C81796599582D2CFE37407760B348A7816ADE2C9B76077C62440D 1 23DDDD15C99111413585FD282E05095FC27803EA8DCF94570B3FD2A53658AE00 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h_cap": "1 06333FED8C082FE894D02CC04A53B78D801ABC9CE09D0C5B731FEDF82A1BBB5F 1 06FD92996796D6B81F7803C3CEA3891741552B452E3F6C735F5BFE096234442A 1 23C5C9462697381BDF16658D133E5B0888C909E0EA01F041FEDC8E3A4F84BBF6 1 0511A0E8510AAF8454EB78446C563A8719622DADFF4AE86DF07E2969D2E4E48F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + "u": "1 1566C7AC1F99896C4E0CD7BD1B77C241614CE03337201FC6BB886650D78AC20D 1 1C73D8607361E539355F39297C3D8073B6CCA2DBF8A7AC50A50BF2FF20CAD7E9 1 1BF4F8AAE80EFAE308FF9ED399F24546306623EE8BECF6A2B39D939B1FCE94DA 1 0646620C014AD0A78BF83748F165211EFF811B526859F71AD184DA462A0C9303 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + "pk": "1 16373692E1B40E44D2010B1CC7023E89E96DD9F45DB3F837D532A1299E80DB23 1 0A0DA35D2830848D4C04FADBB3E726525ADE9DC5F3364AB6286F642E904BB913 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "y": "1 1C132346B6D6999F872132568ACB6F722FF4F93E08F0F6A1433E25F0937FBCB8 1 093034020F4F2087FDAA63D2EBD9BC02A7FF88F914F5971F285CD21BC200F9BD 1 04FB2599E6FA39E12F7F4A9B1C152E753E6584755843D7B6F746904A78396650 1 2291AC4F56DC5E0FEA5C580ACC2521714A3A8F3C79D78D475AEC473FB631E131 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + }, + }, + } + mock_reg_rev_def_private = { + "value": { + "gamma": "11D1EF7D29B67A898E8BFDECEF24AD3B23A4984636F31AA84E1958A724A3579A" + } + } + + class TestProfile(InMemoryProfile): + def session(self, context: InjectionContext = None) -> "ProfileSession": + return TestProfileSession(self, context=context) + + @classmethod + def test_profile( + cls, settings: Mapping[str, Any] = None, bind: Mapping[Type, Any] = None + ) -> "TestProfile": + profile = TestProfile( + context=InjectionContext(enforce_typing=False, settings=settings), + name=InMemoryProfile.TEST_PROFILE_NAME, + ) + if bind: + for k, v in bind.items(): + if v: + profile.context.injector.bind_instance(k, v) + else: + profile.context.injector.clear_binding(k) + return profile + + @classmethod + def test_session( + cls, settings: Mapping[str, Any] = None, bind: Mapping[Type, Any] = None + ) -> "TestProfileSession": + session = TestProfileSession(cls.test_profile(), settings=settings) + session._active = True + session._init_context() + if bind: + for k, v in bind.items(): + if v: + session.context.injector.bind_instance(k, v) + else: + session.context.injector.clear_binding(k) + return session + + class TestProfileSession(InMemoryProfileSession): + def __init__( + self, + profile: Profile, + *, + context: InjectionContext = None, + settings: Mapping[str, Any] = None, + ): + super().__init__(profile=profile, context=context, settings=settings) + self.handle_counter = 0 + + @property + def handle(self): + if self.handle_counter == 0: + self.handle_counter = self.handle_counter + 1 + return async_mock.MagicMock( + fetch=async_mock.CoroutineMock( + return_value=async_mock.MagicMock( + value_json=json.dumps(mock_cred_def) + ) + ) + ) + else: + return async_mock.MagicMock( + fetch=async_mock.CoroutineMock( + return_value=async_mock.MagicMock( + value_json=json.dumps(mock_reg_rev_def_private), + ), + ) + ) + + credx_module = importlib.import_module("indy_credx") + rev_reg_delta = credx_module.RevocationRegistryDelta.load( + json.dumps( + { + "ver": "1.0", + "value": { + "accum": "1 0792BD1C8C1A529173FDF54A5B30AC90C2472956622E9F04971D36A9BF77C2C5 1 13B18B6B68AD62605C74FD61088814338EDEEB41C2195F96EC0E83B2B3D0258F 1 102ED0DDE96F6367199CE1C0B138F172BC913B65E37250581606974034F4CA20 1 1C53786D2C15190B57167CDDD2A046CAD63970B5DE43F4D492D4F46B8EEE6FF1 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000" + }, + } + ) + ) + + TEST_GENESIS_TXN = "test_genesis_txn" + rec = IssuerRevRegRecord( + issuer_did=TEST_DID, + revoc_reg_id=REV_REG_ID, + revoc_reg_def=REV_REG_DEF, + revoc_def_type="CL_ACCUM", + revoc_reg_entry=REV_REG_ENTRY, + cred_def_id=CRED_DEF_ID, + tails_public_uri=TAILS_URI, + ) + _test_rev_reg_delta = { + "ver": "1.0", + "value": {"accum": "ACCUM", "issued": [1, 2], "revoked": [3, 4]}, + } + self.ledger.get_revoc_reg_delta = async_mock.CoroutineMock( + return_value=( + _test_rev_reg_delta, + 1234567890, + ) + ) + self.ledger.send_revoc_reg_entry = async_mock.CoroutineMock( + return_value={ + "result": {"...": "..."}, + }, + ) + _test_session = TestProfile.test_session( + settings={"tails_server_base_url": "http://1.2.3.4:8088"}, + ) + _test_profile = _test_session.profile + _test_profile.context.injector.bind_instance(BaseLedger, self.ledger) + with async_mock.patch.object( + test_module.IssuerCredRevRecord, + "query_by_ids", + async_mock.CoroutineMock( + return_value=[ + test_module.IssuerCredRevRecord( + record_id=test_module.UUID4_EXAMPLE, + state=test_module.IssuerCredRevRecord.STATE_REVOKED, + cred_ex_id=test_module.UUID4_EXAMPLE, + rev_reg_id=REV_REG_ID, + cred_rev_id="1", + ) + ] + ), + ), async_mock.patch.object( + test_module.IssuerRevRegRecord, + "retrieve_by_revoc_reg_id", + async_mock.CoroutineMock(return_value=rec), + ), async_mock.patch.object( + test_module, + "generate_ledger_rrrecovery_txn", + async_mock.CoroutineMock(return_value=rev_reg_delta), + ): + assert ( + _test_rev_reg_delta, + { + "ver": "1.0", + "value": { + "accum": "1 0792BD1C8C1A529173FDF54A5B30AC90C2472956622E9F04971D36A9BF77C2C5 1 13B18B6B68AD62605C74FD61088814338EDEEB41C2195F96EC0E83B2B3D0258F 1 102ED0DDE96F6367199CE1C0B138F172BC913B65E37250581606974034F4CA20 1 1C53786D2C15190B57167CDDD2A046CAD63970B5DE43F4D492D4F46B8EEE6FF1 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000" + }, + }, + {"...": "..."}, + ) == await rec.fix_ledger_entry( + profile=_test_profile, + apply_ledger_update=True, + genesis_transactions=json.dumps(TEST_GENESIS_TXN), + ) + async def test_generate_registry_etc(self): rec = IssuerRevRegRecord( issuer_did=TEST_DID, diff --git a/aries_cloudagent/revocation/recover.py b/aries_cloudagent/revocation/recover.py index 49adcbc5ed..f2bf38267c 100644 --- a/aries_cloudagent/revocation/recover.py +++ b/aries_cloudagent/revocation/recover.py @@ -85,7 +85,9 @@ async def fetch_txns(genesis_txns, registry_id): return defn, registry, delta, revoked, tails_temp -async def generate_ledger_rrrecovery_txn(genesis_txns, registry_id, set_revoked): +async def generate_ledger_rrrecovery_txn( + genesis_txns, registry_id, set_revoked, cred_def, rev_reg_def_private +): """Generate a new ledger accum entry, based on wallet vs ledger revocation state.""" new_delta = None @@ -111,7 +113,9 @@ async def generate_ledger_rrrecovery_txn(genesis_txns, registry_id, set_revoked) LOGGER.debug("tails_temp: %s", tails_temp.name) update_registry = registry.copy() - new_delta = update_registry.update(defn, [], updates, tails_temp.name) + new_delta = update_registry.update( + cred_def, defn, rev_reg_def_private, [], updates + ) LOGGER.debug("New delta:") LOGGER.debug(new_delta.to_json()) diff --git a/aries_cloudagent/vc/ld_proofs/crypto/tests/test_wallet_key_pair.py b/aries_cloudagent/vc/ld_proofs/crypto/tests/test_wallet_key_pair.py index 580ce4254a..d1842d9ac2 100644 --- a/aries_cloudagent/vc/ld_proofs/crypto/tests/test_wallet_key_pair.py +++ b/aries_cloudagent/vc/ld_proofs/crypto/tests/test_wallet_key_pair.py @@ -2,7 +2,8 @@ from aries_cloudagent.wallet.key_type import ED25519 - +from .....core.in_memory import InMemoryProfile +from .....wallet.in_memory import InMemoryWallet from ...error import LinkedDataProofException from ..wallet_key_pair import WalletKeyPair @@ -10,10 +11,10 @@ class TestWalletKeyPair(TestCase): async def setUp(self): - self.wallet = async_mock.MagicMock() + self.profile = InMemoryProfile.test_profile() async def test_sign_x_no_public_key(self): - key_pair = WalletKeyPair(wallet=self.wallet, key_type=ED25519) + key_pair = WalletKeyPair(profile=self.profile, key_type=ED25519) with self.assertRaises(LinkedDataProofException) as context: await key_pair.sign(b"Message") @@ -22,23 +23,26 @@ async def test_sign_x_no_public_key(self): async def test_sign(self): public_key_base58 = "verkey" key_pair = WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=public_key_base58, ) signed = async_mock.MagicMock() - self.wallet.sign_message = async_mock.CoroutineMock(return_value=signed) - - singed_ret = await key_pair.sign(b"Message") + with async_mock.patch.object( + InMemoryWallet, + "sign_message", + async_mock.CoroutineMock(return_value=signed), + ) as sign_message: + singed_ret = await key_pair.sign(b"Message") - assert signed == singed_ret - self.wallet.sign_message.assert_called_once_with( - message=b"Message", from_verkey=public_key_base58 - ) + assert signed == singed_ret + sign_message.assert_called_once_with( + message=b"Message", from_verkey=public_key_base58 + ) async def test_verify_x_no_public_key(self): - key_pair = WalletKeyPair(wallet=self.wallet, key_type=ED25519) + key_pair = WalletKeyPair(profile=self.profile, key_type=ED25519) with self.assertRaises(LinkedDataProofException) as context: await key_pair.verify(b"Message", b"signature") @@ -47,24 +51,28 @@ async def test_verify_x_no_public_key(self): async def test_verify(self): public_key_base58 = "verkey" key_pair = WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=public_key_base58, ) - self.wallet.verify_message = async_mock.CoroutineMock(return_value=True) - verified = await key_pair.verify(b"Message", b"signature") - - assert verified - self.wallet.verify_message.assert_called_once_with( - message=b"Message", - signature=b"signature", - from_verkey=public_key_base58, - key_type=ED25519, - ) + with async_mock.patch.object( + InMemoryWallet, + "verify_message", + async_mock.CoroutineMock(return_value=True), + ) as verify_message: + verified = await key_pair.verify(b"Message", b"signature") + + assert verified + verify_message.assert_called_once_with( + message=b"Message", + signature=b"signature", + from_verkey=public_key_base58, + key_type=ED25519, + ) async def test_from_verification_method_x_no_public_key_base58(self): - key_pair = WalletKeyPair(wallet=self.wallet, key_type=ED25519) + key_pair = WalletKeyPair(profile=self.profile, key_type=ED25519) with self.assertRaises(LinkedDataProofException) as context: key_pair.from_verification_method({}) diff --git a/aries_cloudagent/vc/ld_proofs/crypto/wallet_key_pair.py b/aries_cloudagent/vc/ld_proofs/crypto/wallet_key_pair.py index f666b739ac..8d99eaed90 100644 --- a/aries_cloudagent/vc/ld_proofs/crypto/wallet_key_pair.py +++ b/aries_cloudagent/vc/ld_proofs/crypto/wallet_key_pair.py @@ -2,9 +2,10 @@ from typing import List, Optional, Union -from ....wallet.util import b58_to_bytes -from ....wallet.key_type import KeyType +from ....core.profile import Profile from ....wallet.base import BaseWallet +from ....wallet.key_type import KeyType +from ....wallet.util import b58_to_bytes from ..error import LinkedDataProofException @@ -17,13 +18,13 @@ class WalletKeyPair(KeyPair): def __init__( self, *, - wallet: BaseWallet, + profile: Profile, key_type: KeyType, public_key_base58: Optional[str] = None, ) -> None: """Initialize new WalletKeyPair instance.""" super().__init__() - self.wallet = wallet + self.profile = profile self.key_type = key_type self.public_key_base58 = public_key_base58 @@ -33,10 +34,12 @@ async def sign(self, message: Union[List[bytes], bytes]) -> bytes: raise LinkedDataProofException( "Unable to sign message with WalletKey: No key to sign with" ) - return await self.wallet.sign_message( - message=message, - from_verkey=self.public_key_base58, - ) + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + return await wallet.sign_message( + message=message, + from_verkey=self.public_key_base58, + ) async def verify( self, message: Union[List[bytes], bytes], signature: bytes @@ -47,12 +50,14 @@ async def verify( "Unable to verify message with key pair: No key to verify with" ) - return await self.wallet.verify_message( - message=message, - signature=signature, - from_verkey=self.public_key_base58, - key_type=self.key_type, - ) + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + return await wallet.verify_message( + message=message, + signature=signature, + from_verkey=self.public_key_base58, + key_type=self.key_type, + ) def from_verification_method(self, verification_method: dict) -> "WalletKeyPair": """Create new WalletKeyPair from public key in verification method.""" @@ -62,7 +67,7 @@ def from_verification_method(self, verification_method: dict) -> "WalletKeyPair" ) return WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=self.key_type, public_key_base58=verification_method["publicKeyBase58"], ) diff --git a/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_2020.py b/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_2020.py index ba477604e8..a5a141c174 100644 --- a/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_2020.py +++ b/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_2020.py @@ -3,9 +3,9 @@ from aries_cloudagent.wallet.key_type import BLS12381G2 +from .....core.in_memory import InMemoryProfile from .....did.did_key import DIDKey from .....wallet.in_memory import InMemoryWallet -from .....core.in_memory import InMemoryProfile from ....tests.document_loader import custom_document_loader from ....tests.data import ( TEST_LD_DOCUMENT, @@ -38,11 +38,11 @@ async def setUp(self): ).key_id self.sign_key_pair = WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=BLS12381G2, public_key_base58=self.key.verkey, ) - self.verify_key_pair = WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2) + self.verify_key_pair = WalletKeyPair(profile=self.profile, key_type=BLS12381G2) async def test_sign_ld_proofs(self): signed = await sign( diff --git a/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_proof_2020.py b/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_proof_2020.py index df2080480d..ae217d4b63 100644 --- a/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_proof_2020.py +++ b/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_proof_2020.py @@ -3,9 +3,9 @@ from aries_cloudagent.wallet.key_type import BLS12381G2 +from .....core.in_memory import InMemoryProfile from .....did.did_key import DIDKey from .....wallet.in_memory import InMemoryWallet -from .....core.in_memory import InMemoryProfile from ....tests.document_loader import custom_document_loader from ....tests.data import ( TEST_LD_DOCUMENT_SIGNED_BBS, @@ -46,7 +46,7 @@ async def setUp(self): self.key.verkey, BLS12381G2 ).key_id - self.key_pair = WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2) + self.key_pair = WalletKeyPair(profile=self.profile, key_type=BLS12381G2) async def test_derive_ld_proofs(self): derived = await derive( diff --git a/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2018.py b/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2018.py index 20e510df3e..fdae59d396 100644 --- a/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2018.py +++ b/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2018.py @@ -37,11 +37,11 @@ async def setUp(self): ).key_id self.sign_key_pair = WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.key.verkey, ) - self.verify_key_pair = WalletKeyPair(wallet=self.wallet, key_type=ED25519) + self.verify_key_pair = WalletKeyPair(profile=self.profile, key_type=ED25519) async def test_sign_ld_proofs(self): signed = await sign( diff --git a/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2020.py b/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2020.py index 165a32fc21..c527e3aa48 100644 --- a/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2020.py +++ b/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2020.py @@ -32,11 +32,11 @@ async def setUp(self): ).key_id self.sign_key_pair = WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.key.verkey, ) - self.verify_key_pair = WalletKeyPair(wallet=self.wallet, key_type=ED25519) + self.verify_key_pair = WalletKeyPair(profile=self.profile, key_type=ED25519) async def test_sign_ld_proofs(self): signed = await sign( diff --git a/aries_cloudagent/vc/ld_proofs/tests/test_ld_proofs.py b/aries_cloudagent/vc/ld_proofs/tests/test_ld_proofs.py index b8eaecc379..26f170793e 100644 --- a/aries_cloudagent/vc/ld_proofs/tests/test_ld_proofs.py +++ b/aries_cloudagent/vc/ld_proofs/tests/test_ld_proofs.py @@ -65,7 +65,7 @@ async def test_sign_Ed25519Signature2018(self): suite = Ed25519Signature2018( verification_method=self.ed25519_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.ed25519_key_info.verkey, ), @@ -87,7 +87,7 @@ async def test_sign_Ed25519Signature2020(self): suite = Ed25519Signature2020( verification_method=self.ed25519_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.ed25519_key_info.verkey, ), @@ -105,7 +105,7 @@ async def test_sign_Ed25519Signature2020(self): async def test_verify_Ed25519Signature2018(self): # Verification requires lot less input parameters suite = Ed25519Signature2018( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=ED25519), + key_pair=WalletKeyPair(profile=self.profile, key_type=ED25519), ) result = await verify( @@ -120,7 +120,7 @@ async def test_verify_Ed25519Signature2018(self): async def test_verify_Ed25519Signature2020(self): # Verification requires lot less input parameters suite = Ed25519Signature2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=ED25519), + key_pair=WalletKeyPair(profile=self.profile, key_type=ED25519), ) result = await verify( @@ -140,7 +140,7 @@ async def test_sign_BbsBlsSignature2020(self): suite = BbsBlsSignature2020( verification_method=self.bls12381g2_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=BLS12381G2, public_key_base58=self.bls12381g2_key_info.verkey, ), @@ -169,7 +169,7 @@ async def test_sign_BbsBlsSignature2020(self): async def test_verify_BbsBlsSignature2020(self): # Verification requires lot less input parameters suite = BbsBlsSignature2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2), + key_pair=WalletKeyPair(profile=self.profile, key_type=BLS12381G2), ) result = await verify( @@ -185,7 +185,7 @@ async def test_verify_BbsBlsSignature2020(self): async def test_derive_BbsBlsSignatureProof2020(self): # Verification requires lot less input parameters suite = BbsBlsSignatureProof2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2), + key_pair=WalletKeyPair(profile=self.profile, key_type=BLS12381G2), ) result = await derive( @@ -201,7 +201,7 @@ async def test_derive_BbsBlsSignatureProof2020(self): async def test_verify_BbsBlsSignatureProof2020(self): # Verification requires lot less input parameters suite = BbsBlsSignatureProof2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2), + key_pair=WalletKeyPair(profile=self.profile, key_type=BLS12381G2), ) result = await verify( diff --git a/aries_cloudagent/vc/tests/test_bbs_mattr_interop.py b/aries_cloudagent/vc/tests/test_bbs_mattr_interop.py index d9f4dde009..0fd50ccbc1 100644 --- a/aries_cloudagent/vc/tests/test_bbs_mattr_interop.py +++ b/aries_cloudagent/vc/tests/test_bbs_mattr_interop.py @@ -51,17 +51,17 @@ async def setUp(self): self.signature_issuer_suite = BbsBlsSignature2020( verification_method="did:example:489398593#test", key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=BLS12381G2, public_key_base58=public_key_base58, ), ) self.signature_suite = BbsBlsSignature2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2), + key_pair=WalletKeyPair(profile=self.profile, key_type=BLS12381G2), ) self.proof_suite = BbsBlsSignatureProof2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2) + key_pair=WalletKeyPair(profile=self.profile, key_type=BLS12381G2) ) async def test_sign_bbs_vc_mattr(self): diff --git a/aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py b/aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py index d155dadd8f..d169a1907a 100644 --- a/aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py +++ b/aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py @@ -68,7 +68,7 @@ async def test_issue_Ed25519Signature2018(self): suite = Ed25519Signature2018( verification_method=self.ed25519_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.ed25519_key_info.verkey, ), @@ -90,7 +90,7 @@ async def test_issue_Ed25519Signature2020(self): suite = Ed25519Signature2020( verification_method=self.ed25519_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.ed25519_key_info.verkey, ), @@ -133,7 +133,7 @@ async def test_derive_x_invalid_credential_structure(self): async def test_verify_Ed25519Signature2018(self): # Verification requires lot less input parameters suite = Ed25519Signature2018( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=ED25519), + key_pair=WalletKeyPair(profile=self.profile, key_type=ED25519), ) verified = await verify_credential( credential=CREDENTIAL_ISSUED, @@ -146,7 +146,7 @@ async def test_verify_Ed25519Signature2018(self): async def test_verify_Ed25519Signature2020(self): # Verification requires lot less input parameters suite = Ed25519Signature2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=ED25519), + key_pair=WalletKeyPair(profile=self.profile, key_type=ED25519), ) verified = await verify_credential( credential=CREDENTIAL_ISSUED_2020, @@ -177,7 +177,7 @@ async def test_issue_BbsBlsSignature2020(self): suite = BbsBlsSignature2020( verification_method=self.bls12381g2_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=BLS12381G2, public_key_base58=self.bls12381g2_key_info.verkey, ), @@ -201,7 +201,7 @@ async def test_issue_BbsBlsSignature2020(self): async def test_verify_BbsBlsSignature2020(self): # Verification requires lot less input parameters suite = BbsBlsSignature2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2), + key_pair=WalletKeyPair(profile=self.profile, key_type=BLS12381G2), ) result = await verify_credential( credential=CREDENTIAL_ISSUED_BBS, @@ -227,7 +227,7 @@ async def test_create_presentation_x_invalid_credential_structures(self): suite = Ed25519Signature2018( verification_method=self.ed25519_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.ed25519_key_info.verkey, ), @@ -260,7 +260,7 @@ async def test_sign_presentation_bbsbls(self): suite = BbsBlsSignature2020( verification_method=self.bls12381g2_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=BLS12381G2, public_key_base58=self.bls12381g2_key_info.verkey, ), @@ -269,7 +269,7 @@ async def test_sign_presentation_bbsbls(self): assert unsigned_presentation == PRESENTATION_UNSIGNED unsigned_presentation["@context"].append("https://w3id.org/security/bbs/v1") - presentation = await sign_presentation( + _ = await sign_presentation( presentation=unsigned_presentation, suite=suite, document_loader=custom_document_loader, @@ -278,7 +278,7 @@ async def test_sign_presentation_bbsbls(self): async def test_verify_presentation(self): suite = Ed25519Signature2018( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=ED25519), + key_pair=WalletKeyPair(profile=self.profile, key_type=ED25519), ) verification_result = await verify_presentation( presentation=PRESENTATION_SIGNED, diff --git a/aries_cloudagent/wallet/jwt.py b/aries_cloudagent/wallet/jwt.py index e66049a30a..3137ec7bfa 100644 --- a/aries_cloudagent/wallet/jwt.py +++ b/aries_cloudagent/wallet/jwt.py @@ -2,13 +2,15 @@ import json import logging -from typing import Any, Mapping, NamedTuple, Optional +from typing import Any, Mapping, Optional +from marshmallow import fields from pydid import DIDUrl, Resource, VerificationMethod from ..core.profile import Profile from ..messaging.jsonld.error import BadJWSHeaderError, InvalidVerificationMethod from ..messaging.jsonld.routes import SUPPORTED_VERIFICATION_METHOD_TYPES +from ..messaging.models.base import BaseModel, BaseModelSchema from ..resolver.did_resolver import DIDResolver from .default_verification_key_strategy import BaseVerificationKeyStrategy from .base import BaseWallet @@ -67,10 +69,11 @@ async def jwt_sign( if not did: raise ValueError("DID URL must be absolute") + if not headers.get("typ", None): + headers["typ"] = "JWT" headers = { **headers, "alg": "EdDSA", - "typ": "JWT", "kid": verification_method, } encoded_headers = dict_to_b64(headers) @@ -88,13 +91,45 @@ async def jwt_sign( return f"{encoded_headers}.{encoded_payload}.{sig}" -class JWTVerifyResult(NamedTuple): +class JWTVerifyResult(BaseModel): """Result from verify.""" - headers: Mapping[str, Any] - payload: Mapping[str, Any] - valid: bool - kid: str + class Meta: + """JWTVerifyResult metadata.""" + + schema_class = "JWTVerifyResultSchema" + + def __init__( + self, + headers: Mapping[str, Any], + payload: Mapping[str, Any], + valid: bool, + kid: str, + ): + """Initialize a JWTVerifyResult instance.""" + self.headers = headers + self.payload = payload + self.valid = valid + self.kid = kid + + +class JWTVerifyResultSchema(BaseModelSchema): + """JWTVerifyResult schema.""" + + class Meta: + """JWTVerifyResultSchema metadata.""" + + model_class = JWTVerifyResult + + headers = fields.Dict( + required=True, metadata={"description": "Headers from verified JWT."} + ) + payload = fields.Dict( + required=True, metadata={"description": "Payload from verified JWT"} + ) + valid = fields.Bool(required=True) + kid = fields.Str(required=True, metadata={"description": "kid of signer"}) + error = fields.Str(required=False, metadata={"description": "Error text"}) async def resolve_public_key_by_kid_for_verify(profile: Profile, kid: str) -> str: @@ -120,7 +155,7 @@ async def resolve_public_key_by_kid_for_verify(profile: Profile, kid: str) -> st async def jwt_verify(profile: Profile, jwt: str) -> JWTVerifyResult: """Verify a JWT and return the headers and payload.""" - encoded_headers, encoded_payload, encoded_signiture = jwt.split(".", 3) + encoded_headers, encoded_payload, encoded_signature = jwt.split(".", 3) headers = b64_to_dict(encoded_headers) if "alg" not in headers or headers["alg"] != "EdDSA" or "kid" not in headers: raise BadJWSHeaderError( @@ -129,7 +164,7 @@ async def jwt_verify(profile: Profile, jwt: str) -> JWTVerifyResult: payload = b64_to_dict(encoded_payload) verification_method = headers["kid"] - decoded_signature = b64_to_bytes(encoded_signiture, urlsafe=True) + decoded_signature = b64_to_bytes(encoded_signature, urlsafe=True) async with profile.session() as session: verkey = await resolve_public_key_by_kid_for_verify( diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 20dd8727c1..ba5e7d4bd4 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -35,7 +35,12 @@ INDY_RAW_PUBLIC_KEY_VALIDATE, JWT_EXAMPLE, JWT_VALIDATE, + SD_JWT_EXAMPLE, + SD_JWT_VALIDATE, + NON_SD_LIST_EXAMPLE, + NON_SD_LIST_VALIDATE, IndyDID, + StrOrDictField, Uri, ) from ..protocols.coordinate_mediation.v1_0.route_manager import RouteManager @@ -50,6 +55,7 @@ from ..resolver.base import ResolverError from ..storage.error import StorageError, StorageNotFoundError from ..wallet.jwt import jwt_sign, jwt_verify +from ..wallet.sd_jwt import sd_jwt_sign, sd_jwt_verify from .base import BaseWallet from .did_info import DIDInfo from .did_method import KEY, SOV, DIDMethod, DIDMethods, HolderDefinedDid @@ -171,14 +177,32 @@ class JWSCreateSchema(OpenAPISchema): ) +class SDJWSCreateSchema(JWSCreateSchema): + """Request schema to create an sd-jws with a particular DID.""" + + non_sd_list = fields.List( + fields.Str( + required=False, + validate=NON_SD_LIST_VALIDATE, + metadata={"example": NON_SD_LIST_EXAMPLE}, + ) + ) + + class JWSVerifySchema(OpenAPISchema): """Request schema to verify a jws created from a DID.""" jwt = fields.Str(validate=JWT_VALIDATE, metadata={"example": JWT_EXAMPLE}) +class SDJWSVerifySchema(OpenAPISchema): + """Request schema to verify an sd-jws created from a DID.""" + + sd_jwt = fields.Str(validate=SD_JWT_VALIDATE, metadata={"example": SD_JWT_EXAMPLE}) + + class JWSVerifyResponseSchema(OpenAPISchema): - """Response schema for verification result.""" + """Response schema for JWT verification result.""" valid = fields.Bool(required=True) error = fields.Str(required=False, metadata={"description": "Error text"}) @@ -191,6 +215,25 @@ class JWSVerifyResponseSchema(OpenAPISchema): ) +class SDJWSVerifyResponseSchema(JWSVerifyResponseSchema): + """Response schema for SD-JWT verification result.""" + + disclosures = fields.List( + fields.List(StrOrDictField()), + metadata={ + "description": "Disclosure arrays associated with the SD-JWT", + "example": [ + ["fx1iT_mETjGiC-JzRARnVg", "name", "Alice"], + [ + "n4-t3mlh8jSS6yMIT7QHnA", + "street_address", + {"_sd": ["kLZrLK7enwfqeOzJ9-Ss88YS3mhjOAEk9lr_ix2Heng"]}, + ], + ], + }, + ) + + class DIDEndpointSchema(OpenAPISchema): """Request schema to set DID endpoint; response schema to get DID endpoint.""" @@ -588,7 +631,6 @@ async def wallet_set_public_did(request: web.BaseRequest): """ context: AdminRequestContext = request["context"] - session = await context.session() outbound_handler = request["outbound_message_router"] @@ -610,9 +652,10 @@ async def wallet_set_public_did(request: web.BaseRequest): if not connection_id: raise web.HTTPBadRequest(reason="No endorser connection found") - wallet = session.inject_or(BaseWallet) - if not wallet: - raise web.HTTPForbidden(reason="No wallet available") + async with context.session() as session: + wallet = session.inject_or(BaseWallet) + if not wallet: + raise web.HTTPForbidden(reason="No wallet available") did = request.query.get("did") if not did: raise web.HTTPBadRequest(reason="Request query must include DID") @@ -634,9 +677,7 @@ async def wallet_set_public_did(request: web.BaseRequest): try: info, attrib_def = await promote_wallet_public_did( - context.profile, context, - context.session, did, write_ledger=write_ledger, connection_id=connection_id, @@ -682,9 +723,7 @@ async def wallet_set_public_did(request: web.BaseRequest): async def promote_wallet_public_did( - profile: Profile, context: AdminRequestContext, - session_fn, did: str, write_ledger: bool = False, connection_id: str = None, @@ -699,7 +738,7 @@ async def promote_wallet_public_did( # write only Indy DID write_ledger = is_indy_did and write_ledger - ledger = profile.inject_or(BaseLedger) + ledger = context.profile.inject_or(BaseLedger) if is_indy_did: if not ledger: @@ -713,30 +752,29 @@ async def promote_wallet_public_did( raise LookupError(f"DID {did} is not posted to the ledger") # check if we need to endorse - if is_author_role(profile): + if is_author_role(context.profile): # authors cannot write to the ledger write_ledger = False # author has not provided a connection id, so determine which to use if not connection_id: - connection_id = await get_endorser_connection_id(profile) + connection_id = await get_endorser_connection_id(context.profile) if not connection_id: raise web.HTTPBadRequest(reason="No endorser connection found") if not write_ledger: - try: - async with profile.session() as session: + async with context.session() as session: + try: connection_record = await ConnRecord.retrieve_by_id( session, connection_id ) - except StorageNotFoundError as err: - raise web.HTTPNotFound(reason=err.roll_up) from err - except BaseModelError as err: - raise web.HTTPBadRequest(reason=err.roll_up) from err - - async with profile.session() as session: + except StorageNotFoundError as err: + raise web.HTTPNotFound(reason=err.roll_up) from err + except BaseModelError as err: + raise web.HTTPBadRequest(reason=err.roll_up) from err endorser_info = await connection_record.metadata_get( session, "endorser_info" ) + if not endorser_info: raise web.HTTPForbidden( reason=( @@ -755,18 +793,16 @@ async def promote_wallet_public_did( did_info: DIDInfo = None attrib_def = None - async with session_fn() as session: + async with context.session() as session: wallet = session.inject_or(BaseWallet) did_info = await wallet.get_local_did(did) info = await wallet.set_public_did(did_info) - if info: - # Publish endpoint if necessary - endpoint = did_info.metadata.get("endpoint") + if info: + # Publish endpoint if necessary + endpoint = did_info.metadata.get("endpoint") - if is_indy_did and not endpoint: - async with session_fn() as session: - wallet = session.inject_or(BaseWallet) + if is_indy_did and not endpoint: endpoint = mediator_endpoint or context.settings.get("default_endpoint") attrib_def = await wallet.set_did_endpoint( info.did, @@ -777,9 +813,10 @@ async def promote_wallet_public_did( routing_keys=routing_keys, ) + if info: # Route the public DID - route_manager = profile.inject(RouteManager) - await route_manager.route_verkey(profile, info.verkey) + route_manager = context.profile.inject(RouteManager) + await route_manager.route_verkey(context.profile, info.verkey) return info, attrib_def @@ -941,6 +978,44 @@ async def wallet_jwt_sign(request: web.BaseRequest): return web.json_response(jws) +@docs( + tags=["wallet"], summary="Create a EdDSA sd-jws using did keys with a given payload" +) +@request_schema(SDJWSCreateSchema) +@response_schema(WalletModuleResponseSchema(), description="") +async def wallet_sd_jwt_sign(request: web.BaseRequest): + """Request handler for sd-jws creation using did. + + Args: + "headers": { ... }, + "payload": { ... }, + "did": "did:example:123", + "verificationMethod": "did:example:123#keys-1" + with did and verification being mutually exclusive. + "non_sd_list": [] + """ + context: AdminRequestContext = request["context"] + body = await request.json() + did = body.get("did") + verification_method = body.get("verificationMethod") + headers = body.get("headers", {}) + payload = body.get("payload", {}) + non_sd_list = body.get("non_sd_list", []) + + try: + sd_jws = await sd_jwt_sign( + context.profile, headers, payload, non_sd_list, did, verification_method + ) + except ValueError as err: + raise web.HTTPBadRequest(reason="Bad did or verification method") from err + except WalletNotFoundError as err: + raise web.HTTPNotFound(reason=err.roll_up) from err + except WalletError as err: + raise web.HTTPBadRequest(reason=err.roll_up) from err + + return web.json_response(sd_jws) + + @docs(tags=["wallet"], summary="Verify a EdDSA jws using did keys with a given JWS") @request_schema(JWSVerifySchema()) @response_schema(JWSVerifyResponseSchema(), 200, description="") @@ -970,6 +1045,32 @@ async def wallet_jwt_verify(request: web.BaseRequest): ) +@docs( + tags=["wallet"], + summary="Verify a EdDSA sd-jws using did keys with a given SD-JWS with " + "optional key binding", +) +@request_schema(SDJWSVerifySchema()) +@response_schema(SDJWSVerifyResponseSchema(), 200, description="") +async def wallet_sd_jwt_verify(request: web.BaseRequest): + """Request handler for sd-jws validation using did. + + Args: + "sd-jwt": { ... } + """ + context: AdminRequestContext = request["context"] + body = await request.json() + sd_jwt = body["sd_jwt"] + try: + result = await sd_jwt_verify(context.profile, sd_jwt) + except (BadJWSHeaderError, InvalidVerificationMethod) as err: + raise web.HTTPBadRequest(reason=err.roll_up) from err + except ResolverError as err: + raise web.HTTPNotFound(reason=err.roll_up) from err + + return web.json_response(result.serialize()) + + @docs(tags=["wallet"], summary="Query DID endpoint in wallet") @querystring_schema(DIDQueryStringSchema()) @response_schema(DIDEndpointSchema, 200, description="") @@ -1056,8 +1157,8 @@ async def on_register_nym_event(profile: Profile, event: Event): did = event.payload["did"] connection_id = event.payload.get("connection_id") try: - info, attrib_def = await promote_wallet_public_did( - profile, profile.context, profile.session, did, connection_id + _info, attrib_def = await promote_wallet_public_did( + profile.context, did, connection_id ) except Exception as err: # log the error, but continue @@ -1125,6 +1226,8 @@ async def register(app: web.Application): web.post("/wallet/set-did-endpoint", wallet_set_did_endpoint), web.post("/wallet/jwt/sign", wallet_jwt_sign), web.post("/wallet/jwt/verify", wallet_jwt_verify), + web.post("/wallet/sd-jwt/sign", wallet_sd_jwt_sign), + web.post("/wallet/sd-jwt/verify", wallet_sd_jwt_verify), web.get( "/wallet/get-did-endpoint", wallet_get_did_endpoint, allow_head=False ), diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py new file mode 100644 index 0000000000..59f3291995 --- /dev/null +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -0,0 +1,320 @@ +"""Operations supporting SD-JWT creation and verification.""" + +import re +from typing import Any, List, Mapping, Optional, Union +from marshmallow import fields +from jsonpath_ng.ext import parse as jsonpath_parse +from sd_jwt.common import SDObj +from sd_jwt.issuer import SDJWTIssuer +from sd_jwt.verifier import SDJWTVerifier + +from ..core.profile import Profile +from ..wallet.jwt import JWTVerifyResult, JWTVerifyResultSchema, jwt_sign, jwt_verify +from ..core.error import BaseError +from ..messaging.valid import StrOrDictField + + +CLAIMS_NEVER_SD = ["iss", "iat", "exp", "cnf"] + + +class SDJWTError(BaseError): + """SD-JWT Error.""" + + +class SDJWTIssuerACAPy(SDJWTIssuer): + """SDJWTIssuer class for ACA-Py implementation.""" + + def __init__( + self, + user_claims: dict, + issuer_key, + holder_key, + profile: Profile, + headers: dict, + did: Optional[str] = None, + verification_method: Optional[str] = None, + add_decoy_claims: bool = False, + serialization_format: str = "compact", + ): + """Initialize an SDJWTIssuerACAPy instance.""" + self._user_claims = user_claims + self._issuer_key = issuer_key + self._holder_key = holder_key + + self.profile = profile + self.headers = headers + self.did = did + self.verification_method = verification_method + + self._add_decoy_claims = add_decoy_claims + self._serialization_format = serialization_format + self.ii_disclosures = [] + + async def _create_signed_jws(self) -> str: + self.serialized_sd_jwt = await jwt_sign( + self.profile, + self.headers, + self.sd_jwt_payload, + self.did, + self.verification_method, + ) + + async def issue(self) -> str: + """Issue an sd-jwt.""" + self._check_for_sd_claim(self._user_claims) + self._assemble_sd_jwt_payload() + await self._create_signed_jws() + self._create_combined() + return self.sd_jwt_issuance + + +def create_json_paths(it, current_path="", path_list=None) -> List: + """Create a json path for each element of the payload.""" + if path_list is None: + path_list = [] + + if isinstance(it, dict): + for k, v in it.items(): + if not k.startswith(tuple(CLAIMS_NEVER_SD)): + new_key = f"{current_path}.{k}" if current_path else k + path_list.append(new_key) + + if isinstance(v, dict): + create_json_paths(v, new_key, path_list) + elif isinstance(v, list): + for i, e in enumerate(v): + if isinstance(e, (dict, list)): + create_json_paths(e, f"{new_key}[{i}]", path_list) + else: + path_list.append(f"{new_key}[{i}]") + elif isinstance(it, list): + for i, e in enumerate(it): + if isinstance(e, (dict, list)): + create_json_paths(e, f"{current_path}[{i}]", path_list) + else: + path_list.append(f"{current_path}[{i}]") + + return path_list + + +def sort_sd_list(sd_list) -> List: + """Sorts sd_list. + + Ensures that selectively disclosable claims deepest + in the structure are handled first. + """ + nested_claim_sort = [(len(sd.split(".")), sd) for sd in sd_list] + nested_claim_sort.sort(reverse=True) + return [sd[1] for sd in nested_claim_sort] + + +def separate_list_splices(non_sd_list) -> List: + """Separate list splices in the non_sd_list into individual indices. + + This is necessary in order to properly construct the inverse of + the claims which should not be selectively disclosable in the case + of list splices. + """ + for item in non_sd_list: + if ":" in item: + split = re.split(r"\[|\]|:", item) + for i in range(int(split[1]), int(split[2])): + non_sd_list.append(f"{split[0]}[{i}]") + non_sd_list.remove(item) + + return non_sd_list + + +def create_sd_list(payload, non_sd_list) -> List: + """Create a list of claims which will be selectively disclosable.""" + flattened_payload = create_json_paths(payload) + separated_non_sd_list = separate_list_splices(non_sd_list) + sd_list = [ + claim for claim in flattened_payload if claim not in separated_non_sd_list + ] + return sort_sd_list(sd_list) + + +async def sd_jwt_sign( + profile: Profile, + headers: Mapping[str, Any], + payload: Mapping[str, Any], + non_sd_list: List = [], + did: Optional[str] = None, + verification_method: Optional[str] = None, +) -> str: + """Sign sd-jwt. + + Use non_sd_list and json paths for payload elements to create a list of + claims that can be selectively disclosable. Use this list to wrap + selectively disclosable claims with SDObj within payload, + create SDJWTIssuerACAPy object, and call SDJWTIssuerACAPy.issue(). + """ + sd_list = create_sd_list(payload, non_sd_list) + for sd in sd_list: + jsonpath_expression = jsonpath_parse(f"$.{sd}") + matches = jsonpath_expression.find(payload) + if len(matches) < 1: + raise SDJWTError(f"Claim for {sd} not found in payload.") + else: + for match in matches: + if isinstance(match.context.value, list): + match.context.value.remove(match.value) + match.context.value.append(SDObj(match.value)) + else: + match.context.value[ + SDObj(str(match.path)) + ] = match.context.value.pop(str(match.path)) + + return await SDJWTIssuerACAPy( + user_claims=payload, + issuer_key=None, + holder_key=None, + profile=profile, + headers=headers, + did=did, + verification_method=verification_method, + ).issue() + + +class SDJWTVerifyResult(JWTVerifyResult): + """Result from verifying SD-JWT.""" + + class Meta: + """SDJWTVerifyResult metadata.""" + + schema_class = "SDJWTVerifyResultSchema" + + def __init__( + self, + headers, + payload, + valid, + kid, + disclosures, + ): + """Initialize an SDJWTVerifyResult instance.""" + super().__init__( + headers, + payload, + valid, + kid, + ) + self.disclosures = disclosures + + +class SDJWTVerifyResultSchema(JWTVerifyResultSchema): + """SDJWTVerifyResult schema.""" + + class Meta: + """SDJWTVerifyResultSchema metadata.""" + + model_class = SDJWTVerifyResult + + disclosures = fields.List( + fields.List(StrOrDictField()), + metadata={ + "description": "Disclosure arrays associated with the SD-JWT", + "example": [ + ["fx1iT_mETjGiC-JzRARnVg", "name", "Alice"], + [ + "n4-t3mlh8jSS6yMIT7QHnA", + "street_address", + {"_sd": ["kLZrLK7enwfqeOzJ9-Ss88YS3mhjOAEk9lr_ix2Heng"]}, + ], + ], + }, + ) + + +class SDJWTVerifierACAPy(SDJWTVerifier): + """SDJWTVerifier class for ACA-Py implementation.""" + + def __init__( + self, + profile: Profile, + sd_jwt_presentation: str, + expected_aud: Union[str, None] = None, + expected_nonce: Union[str, None] = None, + serialization_format: str = "compact", + ): + """Initialize an SDJWTVerifierACAPy instance.""" + self.profile = profile + self.sd_jwt_presentation = sd_jwt_presentation + self._serialization_format = serialization_format + self.expected_aud = expected_aud + self.expected_nonce = expected_nonce + + async def _verify_sd_jwt(self) -> SDJWTVerifyResult: + verified = await jwt_verify( + self.profile, + self._unverified_input_sd_jwt, + ) + return SDJWTVerifyResult( + headers=verified.headers, + payload=verified.payload, + valid=verified.valid, + kid=verified.kid, + disclosures=self._disclosures_list, + ) + + async def verify(self) -> SDJWTVerifyResult: + """Verify an sd-jwt.""" + self._parse_sd_jwt(self.sd_jwt_presentation) + self._create_hash_mappings(self._input_disclosures) + self._disclosures_list = list(self._hash_to_decoded_disclosure.values()) + + self.verified_sd_jwt = await self._verify_sd_jwt() + + if self.expected_aud or self.expected_nonce: + if not (self.expected_aud and self.expected_nonce): + raise ValueError( + "Either both expected_aud and expected_nonce must be provided " + "or both must be None" + ) + await self._verify_key_binding_jwt( + self.expected_aud, + self.expected_nonce, + ) + return self.verified_sd_jwt + + async def _verify_key_binding_jwt( + self, + expected_aud: Union[str, None] = None, + expected_nonce: Union[str, None] = None, + ): + verified_kb_jwt = await jwt_verify( + self.profile, self._unverified_input_key_binding_jwt + ) + self._holder_public_key_payload = self.verified_sd_jwt.payload.get("cnf", None) + + if not self._holder_public_key_payload: + raise ValueError("No holder public key in SD-JWT") + + holder_public_key_payload_jwk = self._holder_public_key_payload.get("jwk", None) + if not holder_public_key_payload_jwk: + raise ValueError( + "The holder_public_key_payload is malformed. " + "It doesn't contain the claim jwk: " + f"{self._holder_public_key_payload}" + ) + + if verified_kb_jwt.headers["typ"] != self.KB_JWT_TYP_HEADER: + raise ValueError("Invalid header typ") + if verified_kb_jwt.payload["aud"] != expected_aud: + raise ValueError("Invalid audience") + if verified_kb_jwt.payload["nonce"] != expected_nonce: + raise ValueError("Invalid nonce") + + +async def sd_jwt_verify( + profile: Profile, + sd_jwt_presentation: str, + expected_aud: str = None, + expected_nonce: str = None, +) -> SDJWTVerifyResult: + """Verify sd-jwt using SDJWTVerifierACAPy.verify().""" + sd_jwt_verifier = SDJWTVerifierACAPy( + profile, sd_jwt_presentation, expected_aud, expected_nonce + ) + return await sd_jwt_verifier.verify() diff --git a/aries_cloudagent/wallet/tests/conftest.py b/aries_cloudagent/wallet/tests/conftest.py new file mode 100644 index 0000000000..eed9e1aef7 --- /dev/null +++ b/aries_cloudagent/wallet/tests/conftest.py @@ -0,0 +1,66 @@ +import pytest +from aries_cloudagent.resolver.did_resolver import DIDResolver +from aries_cloudagent.resolver.tests.test_did_resolver import MockResolver +from aries_cloudagent.wallet.default_verification_key_strategy import ( + BaseVerificationKeyStrategy, + DefaultVerificationKeyStrategy, +) + +from ...core.in_memory.profile import InMemoryProfile +from ...wallet.did_method import DIDMethods +from ...wallet.in_memory import InMemoryWallet + + +@pytest.fixture() +async def profile(): + """In memory profile with injected dependencies.""" + + mock_sov = MockResolver( + ["key"], + resolved={ + "@context": "https://www.w3.org/ns/did/v1", + "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "verificationMethod": [ + { + "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "type": "Ed25519VerificationKey2018", + "controller": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "publicKeyBase58": "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx", + } + ], + "authentication": [ + "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + ], + "assertionMethod": [ + "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + ], + "capabilityDelegation": [ + "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + ], + "capabilityInvocation": [ + "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + ], + "keyAgreement": [ + { + "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6LSbkodSr6SU2trs8VUgnrnWtSm7BAPG245ggrBmSrxbv1R", + "type": "X25519KeyAgreementKey2019", + "controller": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "publicKeyBase58": "5dTvYHaNaB7mk7iA9LqCJEHG2dGZQsvoi8WGzDRtYEf", + } + ], + }, + native=True, + ) + yield InMemoryProfile.test_profile( + bind={ + DIDMethods: DIDMethods(), + BaseVerificationKeyStrategy: DefaultVerificationKeyStrategy(), + DIDResolver: DIDResolver([mock_sov]), + } + ) + + +@pytest.fixture() +async def in_memory_wallet(profile): + """In memory wallet for testing.""" + yield InMemoryWallet(profile) diff --git a/aries_cloudagent/wallet/tests/test_jwt.py b/aries_cloudagent/wallet/tests/test_jwt.py index 289c65901f..b3a2b41f32 100644 --- a/aries_cloudagent/wallet/tests/test_jwt.py +++ b/aries_cloudagent/wallet/tests/test_jwt.py @@ -1,75 +1,12 @@ import pytest -from aries_cloudagent.resolver.did_resolver import DIDResolver -from aries_cloudagent.resolver.tests.test_did_resolver import MockResolver -from aries_cloudagent.wallet.default_verification_key_strategy import ( - BaseVerificationKeyStrategy, - DefaultVerificationKeyStrategy, -) from aries_cloudagent.wallet.key_type import ED25519 -from ...core.in_memory.profile import InMemoryProfile -from ...wallet.did_method import KEY, DIDMethods -from ...wallet.in_memory import InMemoryWallet +from ...wallet.did_method import KEY from ..jwt import jwt_sign, jwt_verify, resolve_public_key_by_kid_for_verify -@pytest.fixture() -async def profile(): - """In memory profile with injected dependencies.""" - - mock_sov = MockResolver( - ["key"], - resolved={ - "@context": "https://www.w3.org/ns/did/v1", - "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "verificationMethod": [ - { - "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "type": "Ed25519VerificationKey2018", - "controller": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "publicKeyBase58": "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx", - } - ], - "authentication": [ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - ], - "assertionMethod": [ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - ], - "capabilityDelegation": [ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - ], - "capabilityInvocation": [ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - ], - "keyAgreement": [ - { - "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6LSbkodSr6SU2trs8VUgnrnWtSm7BAPG245ggrBmSrxbv1R", - "type": "X25519KeyAgreementKey2019", - "controller": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "publicKeyBase58": "5dTvYHaNaB7mk7iA9LqCJEHG2dGZQsvoi8WGzDRtYEf", - } - ], - }, - native=True, - ) - yield InMemoryProfile.test_profile( - bind={ - DIDMethods: DIDMethods(), - BaseVerificationKeyStrategy: DefaultVerificationKeyStrategy(), - DIDResolver: DIDResolver([mock_sov]), - } - ) - - -@pytest.fixture() -async def in_memory_wallet(profile): - """In memory wallet for testing.""" - yield InMemoryWallet(profile) - - class TestJWT: """Tests for JWT sign and verify using dids.""" diff --git a/aries_cloudagent/wallet/tests/test_sd_jwt.py b/aries_cloudagent/wallet/tests/test_sd_jwt.py new file mode 100644 index 0000000000..9282c87b9c --- /dev/null +++ b/aries_cloudagent/wallet/tests/test_sd_jwt.py @@ -0,0 +1,437 @@ +from base64 import urlsafe_b64decode +import json +import pytest + +from ...wallet.did_method import KEY +from ...wallet.key_type import ED25519 +from ...wallet.jwt import jwt_sign +from ..sd_jwt import SDJWTVerifyResult, sd_jwt_sign, sd_jwt_verify + + +@pytest.fixture +def create_address_payload(): + return { + "address": { + "street_address": "123 Main St", + "locality": "Anytown", + "region": "Anystate", + "country": "US", + }, + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + } + + +class TestSDJWT: + """Tests for JWT sign and verify using dids.""" + + seed = "testseed000000000000000000000001" + headers = {} + + @pytest.mark.asyncio + async def test_sign_with_did_key_and_verify(self, profile, in_memory_wallet): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + verification_method = None + payload = { + "sub": "user_42", + "given_name": "John", + "family_name": "Doe", + "email": "johndoe@example.com", + "phone_number": "+1-202-555-0101", + "phone_number_verified": True, + "address": { + "street_address": "123 Main St", + "locality": "Anytown", + "region": "Anystate", + "country": "US", + }, + "birthdate": "1940-01-01", + "updated_at": 1570000000, + "nationalities": ["US", "DE", "SA"], + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + } + non_sd_list = [ + "given_name", + "family_name", + "birthdate", + ] + signed = await sd_jwt_sign( + profile, + self.headers, + payload, + non_sd_list, + did_info.did, + verification_method, + ) + assert signed + + # Separate the jwt from the disclosures + signed_sd_jwt = signed.split("~")[0] + + # Determine which selectively disclosable attributes to reveal + revealed = ["sub", "phone_number", "phone_number_verified"] + + for disclosure in signed.split("~")[1:-1]: + # Decode the disclosures + padded = f"{disclosure}{'=' * divmod(len(disclosure),4)[1]}" + decoded = json.loads(urlsafe_b64decode(padded).decode("utf-8")) + # Add the disclosures associated with the claims to be revealed + if decoded[1] in revealed: + signed_sd_jwt = signed_sd_jwt + "~" + disclosure + + verified = await sd_jwt_verify(profile, f"{signed_sd_jwt}~") + assert verified.valid + # Validate that the non-selectively disclosable claims are visible in the payload + assert verified.payload["given_name"] == payload["given_name"] + assert verified.payload["family_name"] == payload["family_name"] + assert verified.payload["birthdate"] == payload["birthdate"] + # Validate that the revealed claims are in the disclosures + assert sorted(revealed) == sorted( + [disclosure[1] for disclosure in verified.disclosures] + ) + assert verified.payload["iss"] == payload["iss"] + assert verified.payload["iat"] == payload["iat"] + assert verified.payload["exp"] == payload["exp"] + + @pytest.mark.asyncio + async def test_flat_structure( + self, profile, in_memory_wallet, create_address_payload + ): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + verification_method = None + non_sd_list = [ + "address.street_address", + "address.locality", + "address.region", + "address.country", + ] + signed = await sd_jwt_sign( + profile, + self.headers, + create_address_payload, + non_sd_list, + did_info.did, + verification_method, + ) + assert signed + + verified = await sd_jwt_verify(profile, signed) + assert isinstance(verified, SDJWTVerifyResult) + assert verified.valid + assert verified.payload["_sd"] + assert verified.payload["_sd_alg"] + assert verified.disclosures[0][1] == "address" + assert verified.disclosures[0][2] == { + "street_address": "123 Main St", + "locality": "Anytown", + "region": "Anystate", + "country": "US", + } + + @pytest.mark.asyncio + async def test_nested_structure( + self, profile, in_memory_wallet, create_address_payload + ): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + verification_method = None + non_sd_list = ["address"] + + signed = await sd_jwt_sign( + profile, + self.headers, + create_address_payload, + non_sd_list, + did_info.did, + verification_method, + ) + assert signed + + verified = await sd_jwt_verify(profile, signed) + assert isinstance(verified, SDJWTVerifyResult) + assert verified.valid + assert len(verified.payload["address"]["_sd"]) >= 4 + assert verified.payload["_sd_alg"] + sd_claims = ["street_address", "region", "locality", "country"] + assert sorted(sd_claims) == sorted([claim[1] for claim in verified.disclosures]) + + @pytest.mark.asyncio + async def test_recursive_nested_structure( + self, profile, in_memory_wallet, create_address_payload + ): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + verification_method = None + non_sd_list = [] + + signed = await sd_jwt_sign( + profile, + self.headers, + create_address_payload, + non_sd_list, + did_info.did, + verification_method, + ) + assert signed + + verified = await sd_jwt_verify(profile, signed) + assert isinstance(verified, SDJWTVerifyResult) + assert verified.valid + assert "address" not in verified.payload + assert verified.payload["_sd"] + assert verified.payload["_sd_alg"] + sd_claims = ["street_address", "region", "locality", "country"] + for disclosure in verified.disclosures: + if disclosure[1] == "address": + assert isinstance(disclosure[2], dict) + assert len(disclosure[2]["_sd"]) >= 4 + else: + assert disclosure[1] in sd_claims + + @pytest.mark.asyncio + async def test_list_splice(self, profile, in_memory_wallet): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + payload = {"nationalities": ["US", "DE", "SA"]} + verification_method = None + non_sd_list = ["nationalities", "nationalities[1:3]"] + + signed = await sd_jwt_sign( + profile, + self.headers, + payload, + non_sd_list, + did_info.did, + verification_method, + ) + assert signed + + verified = await sd_jwt_verify(profile, signed) + assert isinstance(verified, SDJWTVerifyResult) + assert verified.valid + for nationality in verified.payload["nationalities"]: + if isinstance(nationality, dict): + assert nationality["..."] + assert len(nationality) == 1 + else: + assert nationality in payload["nationalities"] + assert verified.payload["_sd_alg"] + assert verified.disclosures[0][1] == "US" + + @pytest.mark.asyncio + async def test_sd_jwt_key_binding(self, profile, in_memory_wallet): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + verification_method = None + + payload = { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "cnf": { + "jwk": { + "kty": "EC", + "crv": "P-256", + "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", + "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ", + } + }, + } + signed = await sd_jwt_sign( + profile, + self.headers, + payload, + did=did_info.did, + verification_method=verification_method, + ) + assert signed + + # Key binding + headers_kb = {"alg": "ES256", "typ": "kb+jwt"} + payload_kb = { + "nonce": "1234567890", + "aud": "https://example.com/verifier", + "iat": 1688160483, + } + signed_kb = await jwt_sign( + profile, + headers_kb, + payload_kb, + did_info.did, + verification_method, + ) + assert signed_kb + + assert await sd_jwt_verify( + profile, + f"{signed}{signed_kb}", + expected_aud=payload_kb["aud"], + expected_nonce=payload_kb["nonce"], + ) + + test_input = [ + ( + "Either both expected_aud and expected_nonce must be provided or both must be None", + { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "cnf": { + "jwk": { + "kty": "EC", + "crv": "P-256", + "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", + "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ", + } + }, + }, + {"alg": "ES256", "typ": "kb+jwt"}, + "https://example.com/verifier", + None, + ), + ( + "No holder public key in SD-JWT", + { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + }, + {"alg": "ES256", "typ": "kb+jwt"}, + "https://example.com/verifier", + "1234567890", + ), + ( + "The holder_public_key_payload is malformed. It doesn't contain the claim jwk: ", + { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "cnf": {"y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ"}, + }, + {"alg": "ES256", "typ": "kb+jwt"}, + "https://example.com/verifier", + "1234567890", + ), + ( + "Invalid header typ", + { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "cnf": { + "jwk": { + "kty": "EC", + "crv": "P-256", + "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", + "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ", + } + }, + }, + {"alg": "ES256", "typ": "JWT"}, + "https://example.com/verifier", + "1234567890", + ), + ( + "Invalid audience", + { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "cnf": { + "jwk": { + "kty": "EC", + "crv": "P-256", + "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", + "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ", + } + }, + }, + {"alg": "ES256", "typ": "kb+jwt"}, + "invalid_aud", + "1234567890", + ), + ( + "Invalid nonce", + { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "cnf": { + "jwk": { + "kty": "EC", + "crv": "P-256", + "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", + "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ", + } + }, + }, + {"alg": "ES256", "typ": "kb+jwt"}, + "https://example.com/verifier", + "invalid_nonce", + ), + ] + + @pytest.mark.parametrize( + "error, payload, headers_kb, expected_aud, expected_nonce", test_input + ) + @pytest.mark.asyncio + async def test_sd_jwt_key_binding_errors( + self, + payload, + error, + expected_nonce, + headers_kb, + expected_aud, + profile, + in_memory_wallet, + ): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + verification_method = None + + signed = await sd_jwt_sign( + profile, + self.headers, + payload, + did=did_info.did, + verification_method=verification_method, + ) + assert signed + + # Key binding + payload_kb = { + "nonce": "1234567890", + "aud": "https://example.com/verifier", + "iat": 1688160483, + } + signed_kb = await jwt_sign( + profile, + headers_kb, + payload_kb, + did_info.did, + verification_method, + ) + assert signed_kb + + with pytest.raises( + ValueError, + match=error, + ): + await sd_jwt_verify( + profile, + f"{signed}{signed_kb}", + expected_aud=expected_aud, + expected_nonce=expected_nonce, + ) diff --git a/demo/playground/scripts/requirements.txt b/demo/playground/scripts/requirements.txt index e15d11d275..20232d456d 100644 --- a/demo/playground/scripts/requirements.txt +++ b/demo/playground/scripts/requirements.txt @@ -20,5 +20,5 @@ requests==2.31.0; python_version >= "3.7" routes==2.5.1 six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" tomli==2.0.1; python_version < "3.11" and python_version >= "3.7" -urllib3==2.0.2; python_version >= "3.7" +urllib3==2.0.6; python_version >= "3.7" yarl==1.9.2; python_version >= "3.7" diff --git a/docker/Dockerfile b/docker/Dockerfile index c294d0b12c..df4ca3e25d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -22,7 +22,7 @@ ENV HOME="/home/$user" \ PIP_NO_CACHE_DIR=off \ PYTHONUNBUFFERED=1 \ PYTHONIOENCODING=UTF-8 \ - RUST_LOG=warning \ + RUST_LOG=warn \ SHELL=/bin/bash \ SUMMARY="aries-cloudagent image" \ DESCRIPTION="aries-cloudagent provides a base image for running Hyperledger Aries agents in Docker. \ diff --git a/docker/Dockerfile.indy b/docker/Dockerfile.indy index ac3ecd20a7..66b922b2ee 100644 --- a/docker/Dockerfile.indy +++ b/docker/Dockerfile.indy @@ -93,7 +93,7 @@ ENV HOME="/home/$user" \ PIP_NO_CACHE_DIR=off \ PYTHONUNBUFFERED=1 \ PYTHONIOENCODING=UTF-8 \ - RUST_LOG=warning \ + RUST_LOG=warn \ SHELL=/bin/bash \ SUMMARY="indy-python base image" \ DESCRIPTION="aries-cloudagent provides a base image for running Hyperledger Aries agents in Docker. \ @@ -227,7 +227,7 @@ ENV HOME="/home/$user" \ PIP_NO_CACHE_DIR=off \ PYTHONUNBUFFERED=1 \ PYTHONIOENCODING=UTF-8 \ - RUST_LOG=warning \ + RUST_LOG=warn \ SHELL=/bin/bash \ SUMMARY="aries-cloudagent image" \ DESCRIPTION="aries-cloudagent provides a base image for running Hyperledger Aries agents in Docker. \ diff --git a/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md b/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md new file mode 100644 index 0000000000..ed21f04768 --- /dev/null +++ b/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md @@ -0,0 +1,194 @@ +# SD-JWT Implementation in ACA-Py + +This document describes the implementation of SD-JWTs in ACA-Py according to the [Selective Disclosure for JWTs (SD-JWT) Specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05), which defines a mechanism for selective disclosure of individual elements of a JSON object used as the payload of a JSON Web Signature structure. + +This implementation adds an important privacy-preserving feature to JWTs, since the receiver of an unencrypted JWT can view all claims within. This feature allows the holder to present only a relevant subset of the claims for a given presentation. The issuer includes plaintext claims, called disclosures, outside of the JWT. Each disclosure corresponds to a hidden claim within the JWT. When a holder prepares a presentation, they include along with the JWT only the disclosures corresponding to the claims they wish to reveal. The verifier verifies that the disclosures in fact correspond to claim values within the issuer-signed JWT. The verifier cannot view the claim values not disclosed by the holder. + +In addition, this implementation includes an optional mechanism for key binding, which is the concept of binding an SD-JWT to a holder's public key and requiring that the holder prove possession of the corresponding private key when presenting the SD-JWT. + +## Issuer Instructions + +The issuer determines which claims in an SD-JWT can be selectively disclosable. In this implementation, all claims at all levels of the JSON structure are by default selectively disclosable. If the issuer wishes for certain claims to always be visible, they can indicate which claims should not be selectively disclosable, as described below. Essential verification data such as `iss`, `iat`, `exp`, and `cnf` are always visible. + +The issuer creates a list of JSON paths for the claims that will not be selectively disclosable. Here is an example payload: +``` +{ + "birthdate": "1940-01-01", + "address": { + "street_address": "123 Main St", + "locality": "Anytown", + "region": "Anystate", + "country": "US", + }, + "nationalities": ["US", "DE", "SA"], +} + +``` + +| Attribute to access | JSON path | +|--------------|-----------| +| "birthdate" | "birthdate" | +| The country attribute within the address dictionary | "address.country" | +| The second item in the nationalities list | "nationalities[1] | +| All items in the nationalities list | "nationalities[0:2]" | + +The [specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#name-nested-data-in-sd-jwts) defines options for how the issuer can handle nested structures with respect to selective disclosability. As mentioned, all claims at all levels of the JSON structure are by default selectively disclosable. + +### [Option 1: Flat SD-JWT](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#section-5.7.1) +The issuer can decide to treat the `address` claim in the above example payload as a block that can either be disclosed completely or not at all. + +The issuer lists out all the claims inside "address" in the `non_sd_list`, but not `address` itself: +``` +non_sd_list = [ + "address.street_address", + "address.locality", + "address.region", + "address.country", +] +``` + +### [Option 2: Structured SD-JWT](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#section-5.7.2) +The issuer may instead decide to make the `address` claim contents selectively disclosable individually. + +The issuer lists only "address" in the `non_sd_list`. +``` +non_sd_list = ["address"] +``` + +### [Option 3: SD-JWT with Recursive Disclosures](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#section-5.7.3) +The issuer may also decide to make the `address` claim contents selectively disclosable recursively, i.e., the `address` claim is made selectively disclosable as well as its sub-claims. + +The issuer lists neither `address` nor the subclaims of `address` in the `non_sd_list`, leaving all with their default selective disclosability. If all claims can be selectively disclosable, the `non_sd_list` need not be defined explicitly. + + +## Walk-Through of SD-JWT Implementation + +### Signing SD-JWTs + +#### Example input to `/wallet/sd-jwt/sign` endpoint: + +``` +{ + "did": "WpVJtxKVwGQdRpQP8iwJZy", + "headers": {}, + "payload": { + "sub": "user_42", + "given_name": "John", + "family_name": "Doe", + "email": "johndoe@example.com", + "phone_number": "+1-202-555-0101", + "phone_number_verified": true, + "address": { + "street_address": "123 Main St", + "locality": "Anytown", + "region": "Anystate", + "country": "US" + }, + "birthdate": "1940-01-01", + "updated_at": 1570000000, + "nationalities": ["US", "DE", "SA"], + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000 + }, + "non_sd_list": [ + "given_name", + "family_name", + "nationalities" + ] +} + +``` +#### Output: +``` +"eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJFZERTQSIsICJraWQiOiAiZGlkOnNvdjpXcFZKdHhLVndHUWRScFFQOGl3Slp5I2tleS0xIn0.eyJfc2QiOiBbIkR0a21ha3NkZGtHRjFKeDBDY0kxdmxRTmZMcGFnQWZ1N3p4VnBGRWJXeXciLCAiSlJLb1E0QXVHaU1INWJIanNmNVV4YmJFeDh2YzFHcUtvX0l3TXE3Nl9xbyIsICJNTTh0TlVLNUstR1lWd0swX01kN0k4MzExTTgwVi13Z0hRYWZvRkoxS09JIiwgIlBaM1VDQmdadVRMMDJkV0pxSVY4elUtSWhnalJNX1NTS3dQdTk3MURmLTQiLCAiX294WGNuSW5Yai1SV3BMVHNISU5YaHFrRVAwODkwUFJjNDBISWE1NElJMCIsICJhdnRLVW5Sdnc1clV0TnZfUnAwUll1dUdkR0RzcnJPYWJfVjR1Y05RRWRvIiwgInByRXZJbzBseTVtNTVsRUpTQUdTVzMxWGdVTElOalo5ZkxiRG81U1pCX0UiXSwgImdpdmVuX25hbWUiOiAiSm9obiIsICJmYW1pbHlfbmFtZSI6ICJEb2UiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJPdU1wcEhpYzEySjYzWTBIY2Ffd1BVeDJCTGdUQVdZQjJpdXpMY3lvcU5JIn0sIHsiLi4uIjogIlIxczlaU3NYeVV0T2QyODdEYy1DTVYyMEdvREF3WUVHV3c4ZkVKd1BNMjAifSwgeyIuLi4iOiAid0lJbjdhQlNDVkFZcUF1Rks3Nmpra3FjVGFvb3YzcUhKbzU5WjdKWHpnUSJ9XSwgImlzcyI6ICJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsICJpYXQiOiAxNjgzMDAwMDAwLCAiZXhwIjogMTg4MzAwMDAwMCwgIl9zZF9hbGciOiAic2hhLTI1NiJ9.cIsuGTIPfpRs_Z49nZcn7L6NUgxQumMGQpu8K6rBtv-YRiFyySUgthQI8KZe1xKyn5Wc8zJnRcWbFki2Vzw6Cw~WyJmWURNM1FQcnZicnZ6YlN4elJsUHFnIiwgIlNBIl0~WyI0UGc2SmZ0UnRXdGFPcDNZX2tscmZRIiwgIkRFIl0~WyJBcDh1VHgxbVhlYUgxeTJRRlVjbWV3IiwgIlVTIl0~WyJ4dkRYMDBmalpmZXJpTmlQb2Q1MXFRIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ~WyJYOTlzM19MaXhCY29yX2hudFJFWmNnIiwgInN1YiIsICJ1c2VyXzQyIl0~WyIxODVTak1hM1k3QlFiWUpabVE3U0NRIiwgInBob25lX251bWJlcl92ZXJpZmllZCIsIHRydWVd~WyJRN1FGaUpvZkhLSWZGV0kxZ0Vaal93IiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ~WyJOeWtVcmJYN1BjVE1ubVRkUWVxZXl3IiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ~WyJlemJwQ2lnVlhrY205RlluVjNQMGJ3IiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0~WyJvd3ROX3I5Z040MzZKVnJFRWhQU05BIiwgInN0cmVldF9hZGRyZXNzIiwgIjEyMyBNYWluIFN0Il0~WyJLQXktZ0VaWmRiUnNHV1dNVXg5amZnIiwgInJlZ2lvbiIsICJBbnlzdGF0ZSJd~WyJPNnl0anM2SU9HMHpDQktwa0tzU1pBIiwgImxvY2FsaXR5IiwgIkFueXRvd24iXQ~WyI0Nzg5aG5GSjhFNTRsLW91RjRaN1V3IiwgImNvdW50cnkiLCAiVVMiXQ~WyIyaDR3N0FuaDFOOC15ZlpGc2FGVHRBIiwgImFkZHJlc3MiLCB7Il9zZCI6IFsiTXhKRDV5Vm9QQzFIQnhPRmVRa21TQ1E0dVJrYmNrellza1Z5RzVwMXZ5SSIsICJVYkxmVWlpdDJTOFhlX2pYbS15RHBHZXN0ZDNZOGJZczVGaVJpbVBtMHdvIiwgImhsQzJEYVBwT2t0eHZyeUFlN3U2YnBuM09IZ193Qk5heExiS3lPRDVMdkEiLCAia2NkLVJNaC1PaGFZS1FPZ2JaajhmNUppOXNLb2hyYnlhYzNSdXRqcHNNYyJdfV0~" +``` + +The `sd_jwt_sign()` method: +- Creates the list of claims that are selectively disclosable + - Uses the `non_sd_list` compared against the list of JSON paths for all claims to create the list of JSON paths for selectively disclosable claims + - Separates list splices if necessary + - Sorts the `sd_list` so that the claims deepest in the structure are handled first + - Since we will wrap the selectively disclosable claim keys, the JSON paths for nested structures do not work properly when the claim key is wrapped in an object +- Uses the JSON paths in the `sd_list` to find each selectively disclosable claim and wrap it in the `SDObj` defined by the [sd-jwt Python library](https://github.com/openwallet-foundation-labs/sd-jwt-python) and removes/replaces the original entry + - For list items, the element itself is wrapped + - For other objects, the dictionary key is wrapped +- With this modified payload, the `SDJWTIssuerACAPy.issue()` method: + - Checks if there are selectively disclosable claims at any level in the payload + - Assembles the SD-JWT payload and creates the disclosures + - Calls `SDJWTIssuerACAPy._create_signed_jws()`, which is redefined in order to use the ACA-Py `jwt_sign` method and which creates the JWT + - Combines and returns the signed JWT with its disclosures and option key binding JWT, as indicated in the [specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#name-sd-jwt-structure) + + + +### Verifying SD-JWTs + +#### Example input to `/wallet/sd-jwt/verify` endpoint: + +Using the output from the `/wallet/sd-jwt/sign` example above, we have decided to only reveal two of the selectively disclosable claims (`user` and `updated_at`) and achieved this by only including the disclosures for those claims. We have also included a key binding JWT following the disclosures. +``` +{ + "sd_jwt": "eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJFZERTQSIsICJraWQiOiAiZGlkOnNvdjpXcFZKdHhLVndHUWRScFFQOGl3Slp5I2tleS0xIn0.eyJfc2QiOiBbIkR0a21ha3NkZGtHRjFKeDBDY0kxdmxRTmZMcGFnQWZ1N3p4VnBGRWJXeXciLCAiSlJLb1E0QXVHaU1INWJIanNmNVV4YmJFeDh2YzFHcUtvX0l3TXE3Nl9xbyIsICJNTTh0TlVLNUstR1lWd0swX01kN0k4MzExTTgwVi13Z0hRYWZvRkoxS09JIiwgIlBaM1VDQmdadVRMMDJkV0pxSVY4elUtSWhnalJNX1NTS3dQdTk3MURmLTQiLCAiX294WGNuSW5Yai1SV3BMVHNISU5YaHFrRVAwODkwUFJjNDBISWE1NElJMCIsICJhdnRLVW5Sdnc1clV0TnZfUnAwUll1dUdkR0RzcnJPYWJfVjR1Y05RRWRvIiwgInByRXZJbzBseTVtNTVsRUpTQUdTVzMxWGdVTElOalo5ZkxiRG81U1pCX0UiXSwgImdpdmVuX25hbWUiOiAiSm9obiIsICJmYW1pbHlfbmFtZSI6ICJEb2UiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJPdU1wcEhpYzEySjYzWTBIY2Ffd1BVeDJCTGdUQVdZQjJpdXpMY3lvcU5JIn0sIHsiLi4uIjogIlIxczlaU3NYeVV0T2QyODdEYy1DTVYyMEdvREF3WUVHV3c4ZkVKd1BNMjAifSwgeyIuLi4iOiAid0lJbjdhQlNDVkFZcUF1Rks3Nmpra3FjVGFvb3YzcUhKbzU5WjdKWHpnUSJ9XSwgImlzcyI6ICJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsICJpYXQiOiAxNjgzMDAwMDAwLCAiZXhwIjogMTg4MzAwMDAwMCwgIl9zZF9hbGciOiAic2hhLTI1NiJ9.cIsuGTIPfpRs_Z49nZcn7L6NUgxQumMGQpu8K6rBtv-YRiFyySUgthQI8KZe1xKyn5Wc8zJnRcWbFki2Vzw6Cw~WyJ4dkRYMDBmalpmZXJpTmlQb2Q1MXFRIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ~WyJYOTlzM19MaXhCY29yX2hudFJFWmNnIiwgInN1YiIsICJ1c2VyXzQyIl0~eyJhbGciOiAiRWREU0EiLCAidHlwIjogImtiK2p3dCIsICJraWQiOiAiZGlkOnNvdjpXcFZKdHhLVndHUWRScFFQOGl3Slp5I2tleS0xIn0.eyJub25jZSI6ICIxMjM0NTY3ODkwIiwgImF1ZCI6ICJodHRwczovL2V4YW1wbGUuY29tL3ZlcmlmaWVyIiwgImlhdCI6IDE2ODgxNjA0ODN9.i55VeR7bNt7T8HWJcfj6jSLH3Q7vFk8N0t7Tb5FZHKmiHyLrg0IPAuK5uKr3_4SkjuGt1_iNl8Wr3atWBtXMDA" +} +``` + +#### Output: +Note that attributes in the `non_sd_list` (`given_name`, `family_name`, and `nationalities`), as well as essential verification data (`iss`, `iat`, `exp`) are visible directly within the payload. The disclosures include only the values for the `user` and `updated_at` claims, since those are the only selectively disclosable claims that the holder presented. The corresponding hashes for those disclosures appear in the `payload["_sd"]` list. +``` +{ + "headers": { + "typ": "JWT", + "alg": "EdDSA", + "kid": "did:sov:WpVJtxKVwGQdRpQP8iwJZy#key-1" + }, + "payload": { + "_sd": [ + "DtkmaksddkGF1Jx0CcI1vlQNfLpagAfu7zxVpFEbWyw", + "JRKoQ4AuGiMH5bHjsf5UxbbEx8vc1GqKo_IwMq76_qo", + "MM8tNUK5K-GYVwK0_Md7I8311M80V-wgHQafoFJ1KOI", + "PZ3UCBgZuTL02dWJqIV8zU-IhgjRM_SSKwPu971Df-4", + "_oxXcnInXj-RWpLTsHINXhqkEP0890PRc40HIa54II0", + "avtKUnRvw5rUtNv_Rp0RYuuGdGDsrrOab_V4ucNQEdo", + "prEvIo0ly5m55lEJSAGSW31XgULINjZ9fLbDo5SZB_E" + ], + "given_name": "John", + "family_name": "Doe", + "nationalities": [ + { + "...": "OuMppHic12J63Y0Hca_wPUx2BLgTAWYB2iuzLcyoqNI" + }, + { + "...": "R1s9ZSsXyUtOd287Dc-CMV20GoDAwYEGWw8fEJwPM20" + }, + { + "...": "wIIn7aBSCVAYqAuFK76jkkqcTaoov3qHJo59Z7JXzgQ" + } + ], + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "_sd_alg": "sha-256" + }, + "valid": true, + "kid": "did:sov:WpVJtxKVwGQdRpQP8iwJZy#key-1", + "disclosures": [ + [ + "xvDX00fjZferiNiPod51qQ", + "updated_at", + 1570000000 + ], + [ + "X99s3_LixBcor_hntREZcg", + "sub", + "user_42" + ] + ] +} +``` + +The `sd_jwt_verify()` method: +- Parses the SD-JWT presentation into its component parts: JWT, disclosures, and optional key binding + - The JWT payload is parsed from its headers and signature +- Creates a list of plaintext disclosures +- Calls `SDJWTVerifierACAPy._verify_sd_jwt`, which is redefined in order to use the ACA-Py `jwt_verify` method, and which returns the verified JWT +- If key binding is used, the key binding JWT is verified and checked against the expected audience and nonce values diff --git a/poetry.lock b/poetry.lock index 03c7d1cf76..6527209fee 100644 --- a/poetry.lock +++ b/poetry.lock @@ -138,24 +138,6 @@ files = [ [package.dependencies] aiohttp = ">=1.1" -[[package]] -name = "aioredis" -version = "2.0.1" -description = "asyncio (PEP 3156) Redis support" -optional = false -python-versions = ">=3.6" -files = [ - {file = "aioredis-2.0.1-py3-none-any.whl", hash = "sha256:9ac0d0b3b485d293b8ca1987e6de8658d7dafcca1cddfcd1d506cae8cdebfdd6"}, - {file = "aioredis-2.0.1.tar.gz", hash = "sha256:eaa51aaf993f2d71f54b70527c440437ba65340588afeb786cd87c55c89cd98e"}, -] - -[package.dependencies] -async-timeout = "*" -typing-extensions = "*" - -[package.extras] -hiredis = ["hiredis (>=1.0)"] - [[package]] name = "aiosignal" version = "1.3.1" @@ -226,46 +208,6 @@ files = [ {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] -[[package]] -name = "asyncpg" -version = "0.25.0" -description = "An asyncio PostgreSQL driver" -optional = false -python-versions = ">=3.6.0" -files = [ - {file = "asyncpg-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf5e3408a14a17d480f36ebaf0401a12ff6ae5457fdf45e4e2775c51cc9517d3"}, - {file = "asyncpg-0.25.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bc197fc4aca2fd24f60241057998124012469d2e414aed3f992579db0c88e3a"}, - {file = "asyncpg-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a70783f6ffa34cc7dd2de20a873181414a34fd35a4a208a1f1a7f9f695e4ec4"}, - {file = "asyncpg-0.25.0-cp310-cp310-win32.whl", hash = "sha256:43cde84e996a3afe75f325a68300093425c2f47d340c0fc8912765cf24a1c095"}, - {file = "asyncpg-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:56d88d7ef4341412cd9c68efba323a4519c916979ba91b95d4c08799d2ff0c09"}, - {file = "asyncpg-0.25.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a84d30e6f850bac0876990bcd207362778e2208df0bee8be8da9f1558255e634"}, - {file = "asyncpg-0.25.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:beaecc52ad39614f6ca2e48c3ca15d56e24a2c15cbfdcb764a4320cc45f02fd5"}, - {file = "asyncpg-0.25.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:6f8f5fc975246eda83da8031a14004b9197f510c41511018e7b1bedde6968e92"}, - {file = "asyncpg-0.25.0-cp36-cp36m-win32.whl", hash = "sha256:ddb4c3263a8d63dcde3d2c4ac1c25206bfeb31fa83bd70fd539e10f87739dee4"}, - {file = "asyncpg-0.25.0-cp36-cp36m-win_amd64.whl", hash = "sha256:bf6dc9b55b9113f39eaa2057337ce3f9ef7de99a053b8a16360395ce588925cd"}, - {file = "asyncpg-0.25.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:acb311722352152936e58a8ee3c5b8e791b24e84cd7d777c414ff05b3530ca68"}, - {file = "asyncpg-0.25.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0a61fb196ce4dae2f2fa26eb20a778db21bbee484d2e798cb3cc988de13bdd1b"}, - {file = "asyncpg-0.25.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2633331cbc8429030b4f20f712f8d0fbba57fa8555ee9b2f45f981b81328b256"}, - {file = "asyncpg-0.25.0-cp37-cp37m-win32.whl", hash = "sha256:863d36eba4a7caa853fd7d83fad5fd5306f050cc2fe6e54fbe10cdb30420e5e9"}, - {file = "asyncpg-0.25.0-cp37-cp37m-win_amd64.whl", hash = "sha256:fe471ccd915b739ca65e2e4dbd92a11b44a5b37f2e38f70827a1c147dafe0fa8"}, - {file = "asyncpg-0.25.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:72a1e12ea0cf7c1e02794b697e3ca967b2360eaa2ce5d4bfdd8604ec2d6b774b"}, - {file = "asyncpg-0.25.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4327f691b1bdb222df27841938b3e04c14068166b3a97491bec2cb982f49f03e"}, - {file = "asyncpg-0.25.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:739bbd7f89a2b2f6bc44cb8bf967dab12c5bc714fcbe96e68d512be45ecdf962"}, - {file = "asyncpg-0.25.0-cp38-cp38-win32.whl", hash = "sha256:18d49e2d93a7139a2fdbd113e320cc47075049997268a61bfbe0dde680c55471"}, - {file = "asyncpg-0.25.0-cp38-cp38-win_amd64.whl", hash = "sha256:191fe6341385b7fdea7dbdcf47fd6db3fd198827dcc1f2b228476d13c05a03c6"}, - {file = "asyncpg-0.25.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52fab7f1b2c29e187dd8781fce896249500cf055b63471ad66332e537e9b5f7e"}, - {file = "asyncpg-0.25.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a738f1b2876f30d710d3dc1e7858160a0afe1603ba16bf5f391f5316eb0ed855"}, - {file = "asyncpg-0.25.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4105f57ad1e8fbc8b1e535d8fcefa6ce6c71081228f08680c6dea24384ff0e"}, - {file = "asyncpg-0.25.0-cp39-cp39-win32.whl", hash = "sha256:f55918ded7b85723a5eaeb34e86e7b9280d4474be67df853ab5a7fa0cc7c6bf2"}, - {file = "asyncpg-0.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:649e2966d98cc48d0646d9a4e29abecd8b59d38d55c256d5c857f6b27b7407ac"}, - {file = "asyncpg-0.25.0.tar.gz", hash = "sha256:63f8e6a69733b285497c2855464a34de657f2cccd25aeaeeb5071872e9382540"}, -] - -[package.extras] -dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "flake8 (>=3.9.2,<3.10.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "uvloop (>=0.15.3)"] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["flake8 (>=3.9.2,<3.10.0)", "pycodestyle (>=2.7.0,<2.8.0)", "uvloop (>=0.15.3)"] - [[package]] name = "asynctest" version = "0.13.0" @@ -588,24 +530,6 @@ files = [ {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, ] -[[package]] -name = "cheroot" -version = "10.0.0" -description = "Highly-optimized, pure-python HTTP server" -optional = false -python-versions = ">=3.6" -files = [ - {file = "cheroot-10.0.0-py3-none-any.whl", hash = "sha256:8f65dd38ad3d56419cfe2d1b5e4b4e3282b1d58758ca2a336231641a80cf0717"}, - {file = "cheroot-10.0.0.tar.gz", hash = "sha256:59c4a1877fef9969b3c3c080caaaf377e2780919437853fc0d32a9df40b311f0"}, -] - -[package.dependencies] -"jaraco.functools" = "*" -more-itertools = ">=2.6" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=3.2)", "python-dateutil", "sphinx (>=1.8.2)", "sphinx-tabs (>=1.1.0)", "sphinxcontrib-apidoc (>=0.3.0)"] - [[package]] name = "click" version = "8.1.7" @@ -648,68 +572,113 @@ yaml = ["PyYAML"] [[package]] name = "coverage" -version = "7.3.0" +version = "7.3.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"}, - {file = "coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"}, - {file = "coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"}, - {file = "coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"}, - {file = "coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"}, - {file = "coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"}, - {file = "coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"}, - {file = "coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"}, - {file = "coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"}, - {file = "coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"}, - {file = "coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"}, - {file = "coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"}, - {file = "coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"}, - {file = "coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"}, + {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"}, + {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"}, + {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"}, + {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"}, + {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"}, + {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"}, + {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"}, + {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"}, + {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"}, + {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"}, + {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"}, + {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"}, ] [package.extras] toml = ["tomli"] +[[package]] +name = "cryptography" +version = "41.0.4" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, + {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, + {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, + {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + [[package]] name = "cytoolz" version = "0.12.2" @@ -840,6 +809,23 @@ files = [ {file = "deepmerge-0.3.0.tar.gz", hash = "sha256:f6fd7f1293c535fb599e197e750dbe8674503c5d2a89759b3c72a3c46746d4fd"}, ] +[[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + [[package]] name = "distlib" version = "0.3.7" @@ -955,18 +941,19 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.12.2" +version = "3.12.4" description = "A platform independent file lock." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] [[package]] name = "frozendict" @@ -1086,13 +1073,13 @@ files = [ [[package]] name = "identify" -version = "2.5.27" +version = "2.5.29" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.27-py2.py3-none-any.whl", hash = "sha256:fdb527b2dfe24602809b2201e033c2a113d7bdf716db3ca8e3243f735dcecaba"}, - {file = "identify-2.5.27.tar.gz", hash = "sha256:287b75b04a0e22d727bc9a41f0d4f3c1bcada97490fa6eabb5b28f0e9097e733"}, + {file = "identify-2.5.29-py2.py3-none-any.whl", hash = "sha256:24437fbf6f4d3fe6efd0eb9d67e24dd9106db99af5ceb27996a5f7895f24bf1b"}, + {file = "identify-2.5.29.tar.gz", hash = "sha256:d43d52b86b15918c137e3a74fff5224f60385cd0e9c38e99d07c257f02f151a5"}, ] [package.extras] @@ -1122,15 +1109,15 @@ files = [ [[package]] name = "indy-credx" -version = "1.0.0" +version = "1.0.3" description = "" optional = true python-versions = ">=3.6.3" files = [ - {file = "indy_credx-1.0.0-py3-none-macosx_10_9_universal2.whl", hash = "sha256:dd936e0fb96b73fa9f1d81faca9072ea019816fdc82505b4692750262ddc6e67"}, - {file = "indy_credx-1.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:22193fcd1a7067bffde87c8aa8b0259914e39d3ccf6cba933499a11f167a0ad0"}, - {file = "indy_credx-1.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d88438ed8c5d6c3d019a0188b9bfae26ed12942f45be02cdd979d18baaf099ee"}, - {file = "indy_credx-1.0.0-py3-none-win_amd64.whl", hash = "sha256:27101d1b3a112959c5e4edb733a005a98c43c94442486a46a7e4633824d04012"}, + {file = "indy_credx-1.0.3-py3-none-macosx_10_9_universal2.whl", hash = "sha256:91fc60edc80bad306f52640deaaa6c4e627d795f3f0df523a6ed8145b6d78a4f"}, + {file = "indy_credx-1.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5c0c1170234edf9a518f4faf89767ba53f1fcf17d2f5eccb99b01623e5b4b6ba"}, + {file = "indy_credx-1.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:474eba89ab0674f0311ff01476f6b724ca03e37e46cce7fa729da971e3943d93"}, + {file = "indy_credx-1.0.3-py3-none-win_amd64.whl", hash = "sha256:9a120e939bda9ced7f62b678842b31911f301777887de2cec34862af95968be9"}, ] [[package]] @@ -1167,25 +1154,6 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "jaraco-functools" -version = "3.9.0" -description = "Functools like those found in stdlib" -optional = false -python-versions = ">=3.8" -files = [ - {file = "jaraco.functools-3.9.0-py3-none-any.whl", hash = "sha256:df2e2b0aadd2dfcee2d7e0d7d083d5a5b68f4c8621e6915ae9819a90de65dd44"}, - {file = "jaraco.functools-3.9.0.tar.gz", hash = "sha256:8b137b0feacc17fef4bacee04c011c9e86f2341099c870a1d12d3be37b32a638"}, -] - -[package.dependencies] -more-itertools = "*" -typing-extensions = {version = "*", markers = "python_version < \"3.11\""} - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.classes", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - [[package]] name = "jinja2" version = "3.1.2" @@ -1219,6 +1187,20 @@ decorator = "*" ply = "*" six = "*" +[[package]] +name = "jwcrypto" +version = "1.5.0" +description = "Implementation of JOSE Web standards" +optional = false +python-versions = ">= 3.6" +files = [ + {file = "jwcrypto-1.5.0.tar.gz", hash = "sha256:2c1dc51cf8e38ddf324795dfe9426dee9dd46caf47f535ccbc18781fba810b8d"}, +] + +[package.dependencies] +cryptography = ">=3.4" +deprecated = "*" + [[package]] name = "lxml" version = "4.9.3" @@ -1457,89 +1439,6 @@ build = ["blurb", "twine", "wheel"] docs = ["sphinx"] test = ["pytest (<5.4)", "pytest-cov"] -[[package]] -name = "more-itertools" -version = "10.1.0" -description = "More routines for operating on iterables, beyond itertools" -optional = false -python-versions = ">=3.8" -files = [ - {file = "more-itertools-10.1.0.tar.gz", hash = "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a"}, - {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, -] - -[[package]] -name = "msgpack" -version = "1.0.5" -description = "MessagePack serializer" -optional = false -python-versions = "*" -files = [ - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, - {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, - {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, - {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, - {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, - {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, - {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, - {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, - {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, - {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, - {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, - {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, - {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, - {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, - {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, - {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, -] - [[package]] name = "multidict" version = "6.0.4" @@ -1674,13 +1573,13 @@ files = [ [[package]] name = "nest-asyncio" -version = "1.5.7" +version = "1.5.8" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" files = [ - {file = "nest_asyncio-1.5.7-py3-none-any.whl", hash = "sha256:5301c82941b550b3123a1ea772ba9a1c80bad3a182be8c1a5ae6ad3be57a9657"}, - {file = "nest_asyncio-1.5.7.tar.gz", hash = "sha256:6a80f7b98f24d9083ed24608977c09dd608d83f91cccc24c9d2cba6d10e01c10"}, + {file = "nest_asyncio-1.5.8-py3-none-any.whl", hash = "sha256:accda7a339a70599cb08f9dd09a67e0c2ef8d8d6f4c07f96ab203f2ae254e48d"}, + {file = "nest_asyncio-1.5.8.tar.gz", hash = "sha256:25aa2ca0d2a5b5531956b9e273b45cf664cae2b145101d73b86b199978d48fdb"}, ] [[package]] @@ -1740,67 +1639,65 @@ tests = ["pytest (==6.2.5)", "pytest-xdist (==2.3.0)"] [[package]] name = "pillow" -version = "10.0.0" +version = "10.0.1" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "Pillow-10.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891"}, - {file = "Pillow-10.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf"}, - {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3"}, - {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992"}, - {file = "Pillow-10.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de"}, - {file = "Pillow-10.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485"}, - {file = "Pillow-10.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629"}, - {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538"}, - {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d"}, - {file = "Pillow-10.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f"}, - {file = "Pillow-10.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37"}, - {file = "Pillow-10.0.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883"}, - {file = "Pillow-10.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff"}, - {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551"}, - {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5"}, - {file = "Pillow-10.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199"}, - {file = "Pillow-10.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca"}, - {file = "Pillow-10.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3"}, - {file = "Pillow-10.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51"}, - {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86"}, - {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7"}, - {file = "Pillow-10.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0"}, - {file = "Pillow-10.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa"}, - {file = "Pillow-10.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba"}, - {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3"}, - {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017"}, - {file = "Pillow-10.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3"}, - {file = "Pillow-10.0.0.tar.gz", hash = "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396"}, + {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, + {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"}, + {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"}, + {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"}, + {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"}, + {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"}, + {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"}, + {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"}, ] [package.extras] @@ -1890,7 +1787,7 @@ name = "prompt-toolkit" version = "2.0.10" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=2.6,<3.0.0 || >=3.3.0" +python-versions = ">=2.6,<3.0.dev0 || >=3.3.dev0" files = [ {file = "prompt_toolkit-2.0.10-py2-none-any.whl", hash = "sha256:e7f8af9e3d70f514373bf41aa51bc33af12a6db3f71461ea47fea985defb2c31"}, {file = "prompt_toolkit-2.0.10-py3-none-any.whl", hash = "sha256:46642344ce457641f28fc9d1c9ca939b63dadf8df128b86f1b9860e59c73a5e4"}, @@ -2029,13 +1926,13 @@ files = [ [[package]] name = "pydid" -version = "0.3.8" +version = "0.3.9" description = "Python library for validating, constructing, and representing DIDs and DID Documents" optional = false python-versions = ">=3.6.9,<4.0.0" files = [ - {file = "pydid-0.3.8-py3-none-any.whl", hash = "sha256:53734a20962418aeaaaef8797a6b026835bcbcfcc08afd55c12e4d90c94493bb"}, - {file = "pydid-0.3.8.tar.gz", hash = "sha256:1eec0d9b45df2bcb1a62499bfbd52245fb77207dff5ef34029fb13559a6ac3d3"}, + {file = "pydid-0.3.9-py3-none-any.whl", hash = "sha256:5e4ce9825f2c17500e7f3228c463c8a1a9f1591432ba05b8295c582ebffcb3d8"}, + {file = "pydid-0.3.9.tar.gz", hash = "sha256:1ab44576b5711386af43936ea79ebd5202edea76a2003fab590ba21b6e6a335d"}, ] [package.dependencies] @@ -2123,13 +2020,13 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pytest" -version = "7.4.1" +version = "7.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.1-py3-none-any.whl", hash = "sha256:460c9a59b14e27c602eb5ece2e47bec99dc5fc5f6513cf924a7d03a578991b1f"}, - {file = "pytest-7.4.1.tar.gz", hash = "sha256:2f2301e797521b23e4d2585a0a3d7b5e50fdddaaf7e7d6773ea26ddb17c213ab"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] @@ -2415,21 +2312,40 @@ files = [ {file = "ruff-0.0.285.tar.gz", hash = "sha256:45866048d1dcdcc80855998cb26c4b2b05881f9e043d2e3bfe1aa36d9a2e8f28"}, ] +[[package]] +name = "sd-jwt" +version = "0.9.1" +description = "The reference implementation of the IETF SD-JWT specification." +optional = false +python-versions = "^3.8" +files = [] +develop = false + +[package.dependencies] +jwcrypto = ">=1.3.1" +pyyaml = ">=5.4" + +[package.source] +type = "git" +url = "https://github.com/openwallet-foundation-labs/sd-jwt-python.git" +reference = "HEAD" +resolved_reference = "0d857bf9c4971d27f1e716814ba8b55d323caaaf" + [[package]] name = "setuptools" -version = "68.1.2" +version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, - {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "simplejson" @@ -2687,13 +2603,13 @@ files = [ [[package]] name = "urllib3" -version = "2.0.4" +version = "2.0.6" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, - {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, + {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, + {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, ] [package.extras] @@ -2726,13 +2642,13 @@ files = [ [[package]] name = "virtualenv" -version = "20.24.4" +version = "20.24.5" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.4-py3-none-any.whl", hash = "sha256:29c70bb9b88510f6414ac3e55c8b413a1f96239b6b789ca123437d5e892190cb"}, - {file = "virtualenv-20.24.4.tar.gz", hash = "sha256:772b05bfda7ed3b8ecd16021ca9716273ad9f4467c801f27e83ac73430246dca"}, + {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, + {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, ] [package.dependencies] @@ -2755,19 +2671,6 @@ files = [ {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, ] -[[package]] -name = "web-py" -version = "0.62" -description = "web.py: makes web apps" -optional = false -python-versions = ">=3.5" -files = [ - {file = "web.py-0.62.tar.gz", hash = "sha256:5ce684caa240654cae5950da8b4b7bc178812031e08f990518d072bd44ab525e"}, -] - -[package.dependencies] -cheroot = "*" - [[package]] name = "webargs" version = "5.5.3" @@ -2791,6 +2694,90 @@ frameworks = ["Django (>=1.11.16)", "Flask (>=0.12.2)", "aiohttp (>=3.0.0)", "bo lint = ["flake8 (==3.7.8)", "flake8-bugbear (==19.8.0)", "mypy (==0.730)", "pre-commit (>=1.17,<2.0)"] tests = ["Django (>=1.11.16)", "Flask (>=0.12.2)", "aiohttp (>=3.0.0)", "bottle (>=0.12.13)", "falcon (>=1.4.0,<2.0)", "mock", "pyramid (>=1.9.1)", "pytest", "pytest-aiohttp (>=0.3.0)", "tornado (>=4.5.2)", "webapp2 (>=3.0.0b1)", "webtest (==2.0.33)", "webtest-aiohttp (==2.0.0)"] +[[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +] + [[package]] name = "yarl" version = "1.9.2" @@ -2886,4 +2873,4 @@ indy = ["python3-indy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "3572d33f01ad13410e0ae03d925d0ab238b66e7c888ccfb29501f72f9bec66e7" +content-hash = "a40e054d44dbd674cae5f6730be7a4fc1d22f4a33284c2eb8195d811326d62f0" diff --git a/pyproject.toml b/pyproject.toml index a634cc0ba7..4a70aec4bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ python = "^3.9" aiohttp="~3.8.1" aiohttp-apispec="~2.2.1" aiohttp-cors="~0.7.0" -aioredis="~2.0.0" apispec="~3.3.0" async-timeout="~4.0.2" base58="~2.1.0" @@ -30,7 +29,6 @@ jsonpath_ng="1.5.2" Markdown="~3.1.1" markupsafe="2.0.1" marshmallow="~3.20.1" -msgpack="~1.0" multiformats="~0.2.1" nest_asyncio="~1.5.5" packaging="~23.1" @@ -48,9 +46,7 @@ qrcode = {version = ">=6.1,<7.0", extras = ["pil"]} requests="~2.31.0" rlp="1.2.0" unflatten="~0.1" -asyncpg = ">=0.25.0,<0.26.0" -web-py = ">=0.62,<1.0" -pygments = ">=2.10,<3.0" +sd_jwt = {git = "https://github.com/openwallet-foundation-labs/sd-jwt-python.git"} # askar aries-askar= { version = "~0.2.5", optional = true }