diff --git a/aries_cloudagent/anoncreds/revocation.py b/aries_cloudagent/anoncreds/revocation.py index ebe2923887..84a03ef866 100644 --- a/aries_cloudagent/anoncreds/revocation.py +++ b/aries_cloudagent/anoncreds/revocation.py @@ -8,7 +8,7 @@ import os import time from pathlib import Path -from typing import List, NamedTuple, Optional, Sequence, Tuple +from typing import List, NamedTuple, Optional, Sequence, Tuple, Union from urllib.parse import urlparse import base58 @@ -19,6 +19,7 @@ RevocationRegistryDefinition, RevocationRegistryDefinitionPrivate, RevocationStatusList, + W3cCredential, ) from aries_askar.error import AskarError from requests import RequestException, Session @@ -717,6 +718,7 @@ async def upload_tails_file(self, rev_reg_def: RevRegDef): backoff=-0.5, max_attempts=5, # heuristic: respect HTTP timeout ) + if not upload_success: raise AnonCredsRevocationError( f"Tails file for rev reg for {rev_reg_def.cred_def_id} " @@ -892,6 +894,34 @@ async def get_or_create_active_registry(self, cred_def_id: str) -> RevRegDefResu # Credential Operations + async def create_credential_w3c( + self, + w3c_credential_offer: dict, + w3c_credential_request: dict, + w3c_credential_values: dict, + *, + retries: int = 5, + ) -> Tuple[str, str, str]: + """Create a w3c_credential. + + Args: + w3c_credential_offer: Credential Offer to create w3c_credential for + w3c_credential_request: Credential request to create w3c_credential for + w3c_credential_values: Values to go in w3c_credential + retries: number of times to retry w3c_credential creation + + Returns: + A tuple of created w3c_credential and revocation id + + """ + return await self._create_credential_helper( + w3c_credential_offer, + w3c_credential_request, + w3c_credential_values, + W3cCredential, + retries=retries, + ) + async def _create_credential( self, credential_definition_id: str, @@ -899,9 +929,26 @@ async def _create_credential( credential_offer: dict, credential_request: dict, credential_values: dict, + credential_type: Union[Credential, W3cCredential], rev_reg_def_id: Optional[str] = None, tails_file_path: Optional[str] = None, ) -> Tuple[str, str]: + """Create a credential. + + Args: + credential_definition_id: The credential definition ID + schema_attributes: The schema attributes + credential_offer: The credential offer + credential_request: The credential request + credential_values: The credential values + credential_type: The credential type + rev_reg_def_id: The revocation registry definition ID + tails_file_path: The tails file path + + Returns: + A tuple of created credential and revocation ID + + """ try: async with self.profile.session() as session: cred_def = await session.handle.fetch( @@ -1004,14 +1051,13 @@ async def _create_credential( try: credential = await asyncio.get_event_loop().run_in_executor( None, - lambda: Credential.create( - cred_def.raw_value, - cred_def_private.raw_value, - credential_offer, - credential_request, - raw_values, - None, - revoc, + lambda: credential_type.create( + cred_def=cred_def.raw_value, + cred_def_private=cred_def_private.raw_value, + cred_offer=credential_offer, + cred_request=credential_request, + attr_raw_values=raw_values, + revocation_config=revoc, ), ) except AnoncredsError as err: @@ -1039,6 +1085,36 @@ async def create_credential( Returns: A tuple of created credential and revocation id + """ + return await self._create_credential_helper( + credential_offer, + credential_request, + credential_values, + Credential, + retries=retries, + ) + + async def _create_credential_helper( + self, + credential_offer: dict, + credential_request: dict, + credential_values: dict, + credential_type: Union[Credential, W3cCredential], + *, + retries: int = 5, + ) -> Tuple[str, str, str]: + """Create a credential. + + Args: + credential_offer: Credential Offer to create credential for + credential_request: Credential request to create credential for + credential_values: Values to go in credential + credential_type: Credential or W3cCredential + retries: number of times to retry credential creation + + Returns: + A tuple of created credential and revocation id + """ issuer = AnonCredsIssuer(self.profile) anoncreds_registry = self.profile.inject(AnonCredsRegistry) @@ -1081,6 +1157,7 @@ async def create_credential( credential_offer, credential_request, credential_values, + credential_type, rev_reg_def_id, tails_file_path, ) diff --git a/aries_cloudagent/anoncreds/tests/test_revocation.py b/aries_cloudagent/anoncreds/tests/test_revocation.py index 26a75d57c2..f4a3a12ad6 100644 --- a/aries_cloudagent/anoncreds/tests/test_revocation.py +++ b/aries_cloudagent/anoncreds/tests/test_revocation.py @@ -11,6 +11,9 @@ RevocationRegistryDefinitionPrivate, RevocationStatusList, Schema, + # AnoncredsError, + # W3cCredential, + # CredentialRevocationConfig, ) from aries_askar import AskarError, AskarErrorCode from requests import RequestException, Session @@ -1024,6 +1027,7 @@ async def test_create_credential_private_no_rev_reg_or_tails( "attr1": "value1", "attr2": "value2", }, + credential_type=Credential, ) assert mock_create.called @@ -1038,6 +1042,7 @@ async def test_create_credential_private_no_rev_reg_or_tails( credential_offer={}, credential_request={}, credential_values={}, + credential_type=Credential, ) # missing cred def or cred def private @@ -1049,6 +1054,7 @@ async def test_create_credential_private_no_rev_reg_or_tails( credential_offer={}, credential_request={}, credential_values={}, + credential_type=Credential, ) mock_handle.fetch = mock.CoroutineMock(side_effect=[MockEntry(), None]) with self.assertRaises(test_module.AnonCredsRevocationError): @@ -1058,6 +1064,7 @@ async def test_create_credential_private_no_rev_reg_or_tails( credential_offer={}, credential_request={}, credential_values={}, + credential_type=Credential, ) @mock.patch.object(InMemoryProfileSession, "handle") @@ -1086,6 +1093,7 @@ async def call_test_func(): }, rev_reg_def_id="test-rev-reg-def-id", tails_file_path="tails-file-path", + credential_type=Credential, ) # missing rev list @@ -1380,3 +1388,83 @@ async def test_clear_pending_revocations_with_non_anoncreds_session(self): await self.revocation.clear_pending_revocations( self.profile.session(), rev_reg_def_id="test-rev-reg-id" ) + + @mock.patch.object( + AnonCredsIssuer, "cred_def_supports_revocation", return_value=True + ) + async def test_create_credential_w3c(self, mock_supports_revocation): + self.profile.inject = mock.Mock( + return_value=mock.MagicMock( + get_schema=mock.CoroutineMock( + return_value=GetSchemaResult( + schema_id="CsQY9MGeD3CQP4EyuVFo5m:2:MYCO Biomarker:0.0.3", + schema=AnonCredsSchema( + issuer_id="CsQY9MGeD3CQP4EyuVFo5m", + name="MYCO Biomarker:0.0.3", + version="1.0", + attr_names=["attr1", "attr2"], + ), + schema_metadata={}, + resolution_metadata={}, + ) + ) + ) + ) + self.revocation.get_or_create_active_registry = mock.CoroutineMock( + return_value=RevRegDefResult( + job_id="test-job-id", + revocation_registry_definition_state=RevRegDefState( + state=RevRegDefState.STATE_FINISHED, + revocation_registry_definition_id="active-reg-reg", + revocation_registry_definition=rev_reg_def, + ), + registration_metadata={}, + revocation_registry_definition_metadata={}, + ) + ) + + # Test private funtion seperately - very large + self.revocation._create_credential = mock.CoroutineMock( + return_value=({"cred": "cred"}, 98) + ) + + result = await self.revocation.create_credential_w3c( + w3c_credential_offer={ + "schema_id": "CsQY9MGeD3CQP4EyuVFo5m:2:MYCO Biomarker:0.0.3", + "cred_def_id": "CsQY9MGeD3CQP4EyuVFo5m:3:CL:14951:MYCO_Biomarker", + "key_correctness_proof": {}, + "nonce": "nonce", + }, + w3c_credential_request={}, + w3c_credential_values={}, + ) + + assert isinstance(result, tuple) + assert mock_supports_revocation.call_count == 1 + + @pytest.mark.asyncio + @mock.patch.object(InMemoryProfileSession, "handle") + async def test_create_credential_w3c_keyerror(self, mock_handle): + mock_handle.fetch = mock.CoroutineMock(side_effect=[MockEntry(), MockEntry()]) + with pytest.raises(test_module.AnonCredsRevocationError) as excinfo: + await self.revocation._create_credential( + credential_definition_id="test-cred-def-id", + schema_attributes=["attr1", "attr2"], + credential_offer={ + "schema_id": "CsQY9MGeD3CQP4EyuVFo5m:2:MYCO Biomarker:0.0.3", + "cred_def_id": "CsQY9MGeD3CQP4EyuVFo5m:3:CL:14951:MYCO_Biomarker", + "key_correctness_proof": {}, + "nonce": "nonce", + }, + credential_request={}, + credential_values={ + "X": "value1", + "Y": "value2", + }, + credential_type=Credential, + ) + + assert str(excinfo.value) == ( + "Provided credential values are missing a value " + "for the schema attribute 'attr1'" + ) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py index e2a688db96..100b44530e 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py @@ -7,7 +7,7 @@ import json import logging from typing import Mapping, Tuple - +from anoncreds import W3cCredential from ...models.cred_ex_record import V20CredExRecord from ...models.detail.indy import ( V20CredExRecordIndy, @@ -460,15 +460,29 @@ async def issue_credential( async with ledger: schema_id = await ledger.credential_definition_id2schema_id(cred_def_id) + cred_def = await ledger.get_credential_definition(cred_def_id) + revocable = cred_def["value"].get("revocation") legacy_offer = await self._prepare_legacy_offer(cred_offer, schema_id) legacy_request = await self._prepare_legacy_request(cred_request, cred_def_id) - issuer = AnonCredsIssuer(self.profile) - - credential = await issuer.create_credential_w3c( - legacy_offer, legacy_request, cred_values - ) + cred_rev_id = None + rev_reg_def_id = None + credential = None + if revocable: + issuer = AnonCredsRevocation(self.profile) + ( + credential, + cred_rev_id, + rev_reg_def_id, + ) = await issuer.create_credential_w3c( + legacy_offer, legacy_request, cred_values + ) + else: + issuer = AnonCredsIssuer(self.profile) + credential = await issuer.create_credential_w3c( + legacy_offer, legacy_request, cred_values + ) vcdi_credential = { "credential": json.loads(credential), @@ -476,9 +490,6 @@ async def issue_credential( result = self.get_format_data(CRED_20_ISSUE, vcdi_credential) - cred_rev_id = None - rev_reg_def_id = None - async with self._profile.transaction() as txn: detail_record = V20CredExRecordIndy( cred_ex_id=cred_ex_record.cred_ex_id, @@ -554,10 +565,18 @@ async def store_credential( cred_def_result = await anoncreds_registry.get_credential_definition( self.profile, cred["proof"][0]["verificationMethod"] ) - if cred["proof"][0].get("rev_reg_id"): + # TODO: remove loading of W3cCredential and use the credential directly + try: + cred_w3c = W3cCredential.load(cred) + rev_reg_id = cred_w3c.rev_reg_id + rev_reg_index = cred_w3c.rev_reg_index + except AnonCredsHolderError as e: + LOGGER.error(f"Error receiving credential: {e.error_code} - {e.message}") + raise e + if rev_reg_id: rev_reg_def_result = ( await anoncreds_registry.get_revocation_registry_definition( - self.profile, cred["proof"][0]["rev_reg_id"] + self.profile, rev_reg_id ) ) rev_reg_def = rev_reg_def_result.revocation_registry @@ -588,8 +607,8 @@ async def store_credential( ) detail_record.cred_id_stored = cred_id_stored - detail_record.rev_reg_id = cred["proof"][0].get("rev_reg_id", None) - detail_record.cred_rev_id = cred["proof"][0].get("cred_rev_id", None) + detail_record.rev_reg_id = rev_reg_id + detail_record.cred_rev_id = rev_reg_index async with self.profile.session() as session: # Store detail record, emit event diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py index 04d3c552ef..02a99804b2 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py @@ -2,8 +2,25 @@ from time import time import json +from aries_cloudagent.anoncreds.models.anoncreds_cred_def import ( + CredDef, + GetCredDefResult, +) +from aries_cloudagent.anoncreds.models.anoncreds_revocation import ( + GetRevRegDefResult, + RevRegDef, +) +from aries_cloudagent.anoncreds.registry import AnonCredsRegistry +from aries_cloudagent.askar.profile_anon import AskarAnoncredsProfile +from aries_cloudagent.protocols.issue_credential.v2_0.messages.cred_issue import ( + V20CredIssue, +) +from aries_cloudagent.protocols.issue_credential.v2_0.models.cred_ex_record import ( + V20CredExRecord, +) +from aries_cloudagent.wallet.did_info import DIDInfo import pytest -from .......anoncreds.holder import AnonCredsHolder +from .......anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from .......messaging.credential_definitions.util import ( CRED_DEF_SENT_RECORD_TYPE, ) @@ -176,7 +193,8 @@ "data_model_version": "2.0", "binding_proof": { "anoncreds_link_secret": { - "entropy": "M7PyEDW7WfLDA8UH4BPhVN", + "prover_did": f"did:sov:{TEST_DID}", + "entropy": f"did:sov:{TEST_DID}", "cred_def_id": CRED_DEF_ID, "blinded_ms": { "u": "10047077609650450290609991930929594521921208780899757965398360086992099381832995073955506958821655372681970112562804577530208651675996528617262693958751195285371230790988741041496869140904046414320278189103736789305088489636024127715978439300785989247215275867951013255925809735479471883338351299180591011255281885961242995409072561940244771612447316409017677474822482928698183528232263803799926211692640155689629903898365777273000566450465466723659861801656618726777274689021162957914736922404694190070274236964163273886807208820068271673047750886130307545831668836096290655823576388755329367886670574352063509727295", @@ -234,7 +252,9 @@ async def asyncSetUp(self): self.profile = self.session.profile self.context = self.profile.context setattr(self.profile, "session", mock.MagicMock(return_value=self.session)) - + self.session.wallet.get_public_did = mock.CoroutineMock( + return_value=DIDInfo(TEST_DID, None, None, None, True) + ) # Ledger Ledger = mock.MagicMock() self.ledger = Ledger() @@ -381,7 +401,6 @@ async def test_receive_proposal(self): # Not much to assert. Receive proposal doesn't do anything await self.handler.receive_proposal(cred_ex_record, cred_proposal_message) - @pytest.mark.skip(reason="Anoncreds-break") async def test_create_offer(self): schema_id_parts = SCHEMA_ID.split(":") @@ -423,26 +442,36 @@ async def test_create_offer(self): ) await self.session.storage.add_record(cred_def_record) - self.issuer.create_credential = mock.CoroutineMock( - return_value=json.dumps(VCDI_OFFER) - ) + with mock.patch.object( + test_module, "AnonCredsIssuer", return_value=self.issuer + ) as mock_issuer: + self.issuer.create_credential_offer = mock.CoroutineMock( + return_value=json.dumps( + VCDI_OFFER["binding_method"]["anoncreds_link_secret"] + ) + ) - (cred_format, attachment) = await self.handler.create_offer(cred_proposal) + self.issuer.match_created_credential_definitions = mock.CoroutineMock( + return_value=CRED_DEF_ID + ) - self.issuer.create_credential.assert_called_once_with(CRED_DEF_ID) + (cred_format, attachment) = await self.handler.create_offer(cred_proposal) - # assert identifier match - assert cred_format.attach_id == self.handler.format.api == attachment.ident + self.issuer.create_credential_offer.assert_called_once_with(CRED_DEF_ID) - # assert content of attachment is proposal data - assert attachment.content == VCDI_OFFER + assert cred_format.attach_id == self.handler.format.api == attachment.ident - # assert data is encoded as base64 - assert attachment.data.base64 + assert ( + attachment.content["binding_method"]["anoncreds_link_secret"] + == VCDI_OFFER["binding_method"]["anoncreds_link_secret"] + ) + + # Assert data is encoded as base64 + assert attachment.data.base64 + # TODO: fix this get_public_did return None in the sc + # (cred_format, attachment) = await self.handler.create_offer(cred_proposal) - self.issuer.create_credential_offer.reset_mock() - (cred_format, attachment) = await self.handler.create_offer(cred_proposal) - self.issuer.create_credential_offer.assert_not_called() + # self.issuer.create_credential_offer.assert_not_called() @pytest.mark.skip(reason="Anoncreds-break") async def test_receive_offer(self): @@ -452,9 +481,25 @@ async def test_receive_offer(self): # Not much to assert. Receive offer doesn't do anything await self.handler.receive_offer(cred_ex_record, cred_offer_message) - @pytest.mark.skip(reason="Anoncreds-break") async def test_create_request(self): + # Define your mock credential definition + mock_credential_definition_result = GetCredDefResult( + credential_definition=CredDef( + issuer_id=TEST_DID, schema_id=SCHEMA_ID, type="CL", tag="tag1", value={} + ), + credential_definition_id=CRED_DEF_ID, + resolution_metadata={}, + credential_definition_metadata={}, + ) + mock_creds_registry = mock.MagicMock() + mock_creds_registry.get_credential_definition = mock.AsyncMock( + return_value=mock_credential_definition_result + ) + + # Inject the MagicMock into the context + self.context.injector.bind_instance(AnonCredsRegistry, mock_creds_registry) + holder_did = "did" cred_offer = V20CredOffer( @@ -466,62 +511,67 @@ async def test_create_request(self): ], ) ], - # TODO here offers_attach=[AttachDecorator.data_base64(VCDI_OFFER, ident="0")], ) - cred_ex_record = V20CredExRecordIndy( + cred_ex_record = V20CredExRecord( cred_ex_id="dummy-id", - state=V20CredExRecordIndy.STATE_OFFER_RECEIVED, + state=V20CredExRecord.STATE_OFFER_RECEIVED, cred_offer=cred_offer.serialize(), ) - cred_def = {"cred": "def"} - self.ledger.get_credential_definition = mock.CoroutineMock( - return_value=cred_def - ) - cred_req_meta = {} self.holder.create_credential_request = mock.CoroutineMock( return_value=(json.dumps(VCDI_CRED_REQ), json.dumps(cred_req_meta)) ) - (cred_format, attachment) = await self.handler.create_request( - cred_ex_record, {"holder_did": holder_did} - ) - - self.holder.create_credential_request.assert_called_once_with( - VCDI_OFFER, cred_def, holder_did - ) - - # assert identifier match - assert cred_format.attach_id == self.handler.format.api == attachment.ident + self.profile = mock.MagicMock(AskarAnoncredsProfile) + self.context.injector.bind_instance(AskarAnoncredsProfile, self.profile) + with mock.patch.object( + AnonCredsHolder, "create_credential_request", mock.CoroutineMock() + ) as mock_create: - # assert content of attachment is proposal data - assert attachment.content == VCDI_CRED_REQ + mock_create.return_value = ( + json.dumps(VCDI_CRED_REQ["binding_proof"]["anoncreds_link_secret"]), + json.dumps(cred_req_meta), + ) + (cred_format, attachment) = await self.handler.create_request( + cred_ex_record, {"holder_did": holder_did} + ) - # assert data is encoded as base64 - assert attachment.data.base64 + legacy_offer = await self.handler._prepare_legacy_offer( + VCDI_OFFER, SCHEMA_ID + ) + mock_create.assert_called_once_with( + legacy_offer, + mock_credential_definition_result.credential_definition, + holder_did, + ) + assert cred_format.attach_id == self.handler.format.api == attachment.ident - # cover case with cache (change ID to prevent already exists error) - cred_ex_record._id = "dummy-id2" - await self.handler.create_request(cred_ex_record, {"holder_did": holder_did}) + del VCDI_CRED_REQ["binding_proof"]["anoncreds_link_secret"]["prover_did"] + assert attachment.content == VCDI_CRED_REQ + assert attachment.data.base64 - # cover case with no cache in injection context - self.context.injector.clear_binding(BaseCache) - cred_ex_record._id = "dummy-id3" - self.context.injector.bind_instance( - BaseMultitenantManager, - mock.MagicMock(MultitenantManager, autospec=True), - ) - with mock.patch.object( - IndyLedgerRequestsExecutor, - "get_ledger_for_identifier", - mock.CoroutineMock(return_value=(None, self.ledger)), - ): + cred_ex_record._id = "dummy-id2" await self.handler.create_request( cred_ex_record, {"holder_did": holder_did} ) + self.context.injector.clear_binding(BaseCache) + cred_ex_record._id = "dummy-id3" + self.context.injector.bind_instance( + BaseMultitenantManager, + mock.MagicMock(MultitenantManager, autospec=True), + ) + with mock.patch.object( + IndyLedgerRequestsExecutor, + "get_ledger_for_identifier", + mock.CoroutineMock(return_value=(None, self.ledger)), + ): + await self.handler.create_request( + cred_ex_record, {"holder_did": holder_did} + ) + @pytest.mark.skip(reason="Anoncreds-break") async def test_receive_request(self): cred_ex_record = mock.MagicMock() @@ -530,7 +580,6 @@ async def test_receive_request(self): # Not much to assert. Receive request doesn't do anything await self.handler.receive_request(cred_ex_record, cred_request_message) - @pytest.mark.skip(reason="Anoncreds-break") async def test_issue_credential_revocable(self): attr_values = { "legalName": "value", @@ -552,7 +601,6 @@ async def test_issue_credential_revocable(self): ], ) ], - # TODO here offers_attach=[AttachDecorator.data_base64(VCDI_OFFER, ident="0")], ) cred_request = V20CredRequest( @@ -564,59 +612,52 @@ async def test_issue_credential_revocable(self): ], ) ], - # TODO here requests_attach=[AttachDecorator.data_base64(VCDI_CRED_REQ, ident="0")], ) - cred_ex_record = V20CredExRecordIndy( + cred_ex_record = V20CredExRecord( cred_ex_id="dummy-cxid", cred_offer=cred_offer.serialize(), cred_request=cred_request.serialize(), - initiator=V20CredExRecordIndy.INITIATOR_SELF, - role=V20CredExRecordIndy.ROLE_ISSUER, - state=V20CredExRecordIndy.STATE_REQUEST_RECEIVED, + initiator=V20CredExRecord.INITIATOR_SELF, + role=V20CredExRecord.ROLE_ISSUER, + state=V20CredExRecord.STATE_REQUEST_RECEIVED, ) cred_rev_id = "1000" - self.issuer.create_credential = mock.CoroutineMock( - return_value=(json.dumps(VCDI_CRED), cred_rev_id) - ) - - with mock.patch.object(test_module, "IndyRevocation", autospec=True) as revoc: - revoc.return_value.get_or_create_active_registry = mock.CoroutineMock( - return_value=( - mock.MagicMock( # active_rev_reg_rec - revoc_reg_id=REV_REG_ID, - ), - mock.MagicMock( # rev_reg - tails_local_path="dummy-path", - get_or_fetch_local_tails_path=(mock.CoroutineMock()), - max_creds=10, - ), - ) + dummy_registry = "dummy-registry" + expected_credential = json.dumps(VCDI_CRED) + + # Mock AnonCredsRevocation and its method create_credential_w3c + with mock.patch.object( + test_module, "AnonCredsRevocation", autospec=True + ) as MockAnonCredsRevocation: + mock_issuer = MockAnonCredsRevocation.return_value + mock_issuer.create_credential_w3c = mock.CoroutineMock( + return_value=(expected_credential, cred_rev_id, dummy_registry) ) + # Call the method under test (cred_format, attachment) = await self.handler.issue_credential( cred_ex_record, retries=1 ) - - self.issuer.create_credential.assert_called_once_with( - SCHEMA, - VCDI_OFFER, - VCDI_CRED_REQ, + legacy_offer = await self.handler._prepare_legacy_offer( + VCDI_OFFER, SCHEMA_ID + ) + legacy_request = await self.handler._prepare_legacy_request( + VCDI_CRED_REQ, CRED_DEF_ID + ) + # Verify the mocked method was called with the expected parameters + mock_issuer.create_credential_w3c.assert_called_once_with( + legacy_offer, + legacy_request, attr_values, - REV_REG_ID, - "dummy-path", ) - # assert identifier match + # Assert the results are as expected assert cred_format.attach_id == self.handler.format.api == attachment.ident - - # assert content of attachment is proposal data - assert attachment.content == VCDI_CRED - - # assert data is encoded as base64 assert attachment.data.base64 + assert attachment.content == {"credential": VCDI_CRED} @pytest.mark.skip(reason="Anoncreds-break") async def test_issue_credential_non_revocable(self): @@ -702,3 +743,153 @@ async def test_issue_credential_non_revocable(self): # assert data is encoded as base64 assert attachment.data.base64 + + @pytest.mark.asyncio + async def test_match_sent_cred_def_id_error(self): + tag_query = {"tag": "test_tag"} + + with self.assertRaises(V20CredFormatError) as context: + await self.handler._match_sent_cred_def_id(tag_query) + assert "Issuer has no operable cred def for proposal spec " in str( + context.exception + ) + + @pytest.mark.asyncio + async def test_store_credential(self): + attr_values = { + "legalName": "value", + "jurisdictionId": "value", + "incorporationDate": "value", + } + cred_preview = V20CredPreview( + attributes=[ + V20CredAttrSpec(name=k, value=v) for (k, v) in attr_values.items() + ] + ) + cred_offer = V20CredOffer( + credential_preview=cred_preview, + formats=[ + V20CredFormat( + attach_id="0", + format_=ATTACHMENT_FORMAT[CRED_20_OFFER][ + V20CredFormat.Format.VC_DI.api + ], + ) + ], + offers_attach=[AttachDecorator.data_base64(VCDI_OFFER, ident="0")], + ) + cred_request = V20CredRequest( + formats=[ + V20CredFormat( + attach_id="0", + format_=ATTACHMENT_FORMAT[CRED_20_REQUEST][ + V20CredFormat.Format.VC_DI.api + ], + ) + ], + requests_attach=[AttachDecorator.data_base64(VCDI_CRED_REQ, ident="0")], + ) + cred_issue = V20CredIssue( + formats=[ + V20CredFormat( + attach_id="0", + format_=ATTACHMENT_FORMAT[CRED_20_ISSUE][ + V20CredFormat.Format.VC_DI.api + ], + ) + ], + credentials_attach=[AttachDecorator.data_base64(VCDI_CRED, ident="0")], + ) + cred_ex_record = V20CredExRecord( + cred_ex_id="dummy-cxid", + cred_offer=cred_offer.serialize(), + cred_request=cred_request.serialize(), + cred_issue=cred_issue.serialize(), + initiator=V20CredExRecord.INITIATOR_SELF, + role=V20CredExRecord.ROLE_ISSUER, + state=V20CredExRecord.STATE_REQUEST_RECEIVED, + ) + cred_id = "dummy-cred-id" + + # Define your mock credential definition + mock_credential_definition_result = GetCredDefResult( + credential_definition=CredDef( + issuer_id=TEST_DID, schema_id=SCHEMA_ID, type="CL", tag="tag1", value={} + ), + credential_definition_id=CRED_DEF_ID, + resolution_metadata={}, + credential_definition_metadata={}, + ) + mock_creds_registry = mock.AsyncMock() + mock_creds_registry.get_credential_definition = mock.AsyncMock( + return_value=mock_credential_definition_result + ) + + revocation_registry = RevRegDef( + cred_def_id=CRED_DEF_ID, + issuer_id=TEST_DID, + tag="tag1", + type="CL_ACCUM", + value={}, + ) + + mock_creds_registry.get_revocation_registry_definition = mock.AsyncMock( + return_value=GetRevRegDefResult( + revocation_registry=revocation_registry, + revocation_registry_id="rr-id", + resolution_metadata={}, + revocation_registry_metadata={}, + ) + ) + # Inject the MagicMock into the context + self.context.injector.bind_instance(AnonCredsRegistry, mock_creds_registry) + self.profile = mock.AsyncMock(AskarAnoncredsProfile) + self.context.injector.bind_instance(AskarAnoncredsProfile, self.profile) + with mock.patch.object( + test_module.AnonCredsRevocation, + "get_or_fetch_local_tails_path", + mock.CoroutineMock(), + ) as mock_get_or_fetch_local_tails_path: + with self.assertRaises(V20CredFormatError) as context: + await self.handler.store_credential(cred_ex_record, cred_id) + assert ( + "No credential exchange didcomm/ detail record found for cred ex id dummy-cxid" + in str(context.exception) + ) + + record = V20CredExRecordIndy( + cred_ex_indy_id="dummy-cxid", + rev_reg_id="rr-id", + cred_ex_id="dummy-cxid", + cred_id_stored=cred_id, + cred_request_metadata="dummy-metadata", + cred_rev_id="0", + ) + + record.save = mock.CoroutineMock() + self.handler.get_detail_record = mock.AsyncMock(return_value=record) + with mock.patch.object( + AnonCredsHolder, + "store_credential_w3c", + mock.AsyncMock(), + ) as mock_store_credential: + # Error case: no cred ex record found + + await self.handler.store_credential(cred_ex_record, cred_id) + + mock_store_credential.assert_called_once_with( + mock_credential_definition_result.credential_definition.serialize(), + VCDI_CRED["credential"], + record.cred_request_metadata, + None, + credential_id=cred_id, + rev_reg_def=revocation_registry.serialize(), + ) + + with mock.patch.object( + AnonCredsHolder, + "store_credential_w3c", + mock.AsyncMock(side_effect=AnonCredsHolderError), + ) as mock_store_credential: + with self.assertRaises(AnonCredsHolderError) as context: + await self.handler.store_credential(cred_ex_record, cred_id) diff --git a/demo/features/0453-issue-credential.feature b/demo/features/0453-issue-credential.feature index fbe1c49c07..a6736295b7 100644 --- a/demo/features/0453-issue-credential.feature +++ b/demo/features/0453-issue-credential.feature @@ -64,9 +64,10 @@ Feature: RFC 0453 Aries agent issue credential @WalletType_Askar_AnonCreds @SwitchCredTypeTest Examples: - | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | New_Cred_Type | - | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | vc_di | - | --public-did --wallet-type askar-anoncreds --cred-type vc_di | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | indy | + | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | New_Cred_Type | + | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | vc_di | + | --public-did --wallet-type askar-anoncreds --cred-type vc_di | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | indy | + | --public-did --wallet-type askar-anoncreds --cred-type vc_di --revocation | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | indy | @T003-RFC0453 Scenario Outline: Holder accepts a deleted credential offer diff --git a/demo/features/0454-present-proof.feature b/demo/features/0454-present-proof.feature index f40552e003..bdbc2155c4 100644 --- a/demo/features/0454-present-proof.feature +++ b/demo/features/0454-present-proof.feature @@ -303,3 +303,42 @@ Feature: RFC 0454 Aries agent present proof Examples: | issuer1 | Acme1_capabilities | Bob_cap | Schema_name_1 | Credential_data_1 | Proof_request | | Acme1 | --revocation --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | DL_age_over_19_v2 | + + @T003-RFC0454.5 + Scenario Outline: Present Proof for a vc_di-issued credential using "legacy" indy proof and the proof validates + Given we have "2" agents + | name | role | capabilities | extra | + | Acme | issuer | | | + | Bob | holder | | | + And "Acme" and "Bob" have an existing connection + And "Acme" is ready to issue a credential for + When "Acme" offers a credential with data + When "Bob" has the credential issued + When "Acme" sets the credential type to + When "Acme" sends a request with explicit revocation status for proof presentation to "Bob" + Then "Acme" has the proof verified + + @WalletType_Askar_AnonCreds @SwitchCredTypeTest + Examples: + | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | New_Cred_Type | Proof_request | + | --public-did --wallet-type askar-anoncreds --cred-type vc_di --revocation | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | | | indy | DL_age_over_19_v2 | + + @T003-RFC0454.6 + Scenario Outline: Present Proof for a vc_di-issued credential using "legacy" indy proof and credential is revoked and the proof fails + Given we have "2" agents + | name | role | capabilities | extra | + | Acme | issuer | | | + | Bob | holder | | | + And "Acme" and "Bob" have an existing connection + And "Acme" is ready to issue a credential for + When "Acme" offers a credential with data + When "Bob" has the credential issued + When "Acme" sets the credential type to + And "Acme" revokes the credential + When "Acme" sends a request for proof presentation to "Bob" + Then "Acme" has the proof verification fail + + @WalletType_Askar_AnonCreds @SwitchCredTypeTest + Examples: + | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | New_Cred_Type | Proof_request | + | --public-did --wallet-type askar-anoncreds --cred-type vc_di --revocation | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | | | indy | DL_age_over_19_v2 | diff --git a/demo/requirements.txt b/demo/requirements.txt index e6013b0a46..87111d6930 100644 --- a/demo/requirements.txt +++ b/demo/requirements.txt @@ -1,5 +1,5 @@ asyncpg~=0.29.0 -prompt_toolkit~=2.0.9 +prompt_toolkit~=2.0.10 web.py~=0.62 pygments~=2.18 qrcode[pil]~=7.4 diff --git a/poetry.lock b/poetry.lock index 58a42f0c3b..ea4e2bfaec 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -877,13 +877,13 @@ files = [ [[package]] name = "docutils" -version = "0.21.2" +version = "0.17.1" description = "Docutils -- Python Documentation Utilities" optional = false -python-versions = ">=3.9" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2"}, - {file = "docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f"}, + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, ] [[package]] @@ -924,15 +924,18 @@ test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] name = "eth-typing" -version = "4.2.3" +version = "4.3.0" description = "eth-typing: Common type annotations for ethereum python packages" optional = false python-versions = "<4,>=3.8" files = [ - {file = "eth_typing-4.2.3-py3-none-any.whl", hash = "sha256:b2df49fa89d2e85f2cc3fb1c903b0cd183d524f7a045e3db8cc720cf41adcd3d"}, - {file = "eth_typing-4.2.3.tar.gz", hash = "sha256:8ee3ae7d4136d14fcb955c34f9dbef8e52170984d4dc68c0ab0d61621eab29d8"}, + {file = "eth_typing-4.3.0-py3-none-any.whl", hash = "sha256:718f8ef8180ac1a15e476f072e4522e7bd4429bdabc71499e3ca79e2219d775c"}, + {file = "eth_typing-4.3.0.tar.gz", hash = "sha256:3f4eface387eefa68761b23743baab8d413d609b4201c4086c64a006be5dbf53"}, ] +[package.dependencies] +typing-extensions = ">=4.0.0" + [package.extras] dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] @@ -1027,6 +1030,8 @@ files = [ {file = "frozendict-2.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d13b4310db337f4d2103867c5a05090b22bc4d50ca842093779ef541ea9c9eea"}, {file = "frozendict-2.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:b3b967d5065872e27b06f785a80c0ed0a45d1f7c9b85223da05358e734d858ca"}, {file = "frozendict-2.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:4ae8d05c8d0b6134bfb6bfb369d5fa0c4df21eabb5ca7f645af95fdc6689678e"}, + {file = "frozendict-2.4.4-py311-none-any.whl", hash = "sha256:705efca8d74d3facbb6ace80ab3afdd28eb8a237bfb4063ed89996b024bc443d"}, + {file = "frozendict-2.4.4-py312-none-any.whl", hash = "sha256:d9647563e76adb05b7cde2172403123380871360a114f546b4ae1704510801e5"}, {file = "frozendict-2.4.4.tar.gz", hash = "sha256:3f7c031b26e4ee6a3f786ceb5e3abf1181c4ade92dce1f847da26ea2c96008c7"}, ] @@ -1346,9 +1351,13 @@ files = [ {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, @@ -2283,6 +2292,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2290,8 +2300,16 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2308,6 +2326,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2315,6 +2334,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2463,20 +2483,20 @@ files = [ [[package]] name = "sphinx" -version = "1.8.4" +version = "1.8.6" description = "Python documentation generator" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "Sphinx-1.8.4-py2.py3-none-any.whl", hash = "sha256:b53904fa7cb4b06a39409a492b949193a1b68cc7241a1a8ce9974f86f0d24287"}, - {file = "Sphinx-1.8.4.tar.gz", hash = "sha256:c1c00fc4f6e8b101a0d037065043460dffc2d507257f2f11acaed71fd2b0c83c"}, + {file = "Sphinx-1.8.6-py2.py3-none-any.whl", hash = "sha256:5973adbb19a5de30e15ab394ec8bc05700317fa83f122c349dd01804d983720f"}, + {file = "Sphinx-1.8.6.tar.gz", hash = "sha256:e096b1b369dbb0fcb95a31ba8c9e1ae98c588e601f08eada032248e1696de4b1"}, ] [package.dependencies] alabaster = ">=0.7,<0.8" babel = ">=1.3,<2.0 || >2.0" colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.11" +docutils = ">=0.11,<0.18" imagesize = "*" Jinja2 = ">=2.3" packaging = "*" @@ -2493,20 +2513,36 @@ websupport = ["sqlalchemy (>=0.9)", "whoosh (>=2.0)"] [[package]] name = "sphinx-rtd-theme" -version = "0.5.1" +version = "1.3.0" description = "Read the Docs theme for Sphinx" optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, - {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, + {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, + {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, ] [package.dependencies] -sphinx = "*" +docutils = "<0.19" +sphinx = ">=1.6,<8" +sphinxcontrib-jquery = ">=4,<5" [package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +optional = false +python-versions = ">=2.7" +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" [[package]] name = "sphinxcontrib-serializinghtml" @@ -2566,13 +2602,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.12.1" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, - {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -2886,4 +2922,4 @@ didcommv2 = ["didcomm-messaging"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "0bd0967bd50be33c5cebc8a1485d59056d1f004a2d48459106a592d1b2185d68" +content-hash = "2e5b69e190232b87e6ccabd3433c34044e61aca7ed047c8ef6f148437f17252e" diff --git a/pyproject.toml b/pyproject.toml index 68f52d655f..15b30f42b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ nest_asyncio="~1.6.0" packaging="~23.1" portalocker="~2.8.2" prompt_toolkit=">=2.0.9,<2.1.0" -pydid="^0.5.0" +pydid="^0.5.1" pyjwt="~2.8.0" pyld="^2.0.4" pynacl="~1.5.0" @@ -70,7 +70,7 @@ ruff = "0.4.4" # Sync with version in .pre-commit-config.yaml black = "24.4.2" -sphinx="1.8.4" +sphinx="1.8.6" sphinx-rtd-theme=">=0.4.3" ptvsd="4.3.2" @@ -79,7 +79,7 @@ pydevd="1.5.1" pydevd-pycharm="~193.6015.39" # testing -pytest = "^8.2.0" +pytest = "^8.2.2" pytest-asyncio = "^0.23.6" pytest-cov = "^5.0.0" pytest-ruff = "^0.3.2"