From 0cb514ff1fe1e7a6cdeccb1f2c5161a61a9dff05 Mon Sep 17 00:00:00 2001 From: Micah Peltier Date: Mon, 22 Aug 2022 12:07:28 -0600 Subject: [PATCH 01/33] feat: remove public/multi mutual exclusion Signed-off-by: Micah Peltier --- .../protocols/out_of_band/v1_0/manager.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index 4bdfcc39a6..2670d4bf89 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -133,10 +133,10 @@ async def create_invitation( "Cannot store metadata without handshake protocols" ) if public: - if multi_use: - raise OutOfBandManagerError( - "Cannot create public invitation with multi_use" - ) + # if multi_use: + # raise OutOfBandManagerError( + # "Cannot create public invitation with multi_use" + # ) if metadata: raise OutOfBandManagerError( "Cannot store metadata on public invitations" @@ -235,9 +235,15 @@ async def create_invitation( # Only create connection record if hanshake_protocols is defined if handshake_protocols: + invitation_mode = ( + ConnRecord.INVITATION_MODE_MULTI + if multi_use + else ConnRecord.INVITATION_MODE_ONCE + ) conn_rec = ConnRecord( # create connection record invitation_key=public_did.verkey, invitation_msg_id=invi_msg._id, + invitation_mode=invitation_mode, their_role=ConnRecord.Role.REQUESTER.rfc23, state=ConnRecord.State.INVITATION.rfc23, accept=ConnRecord.ACCEPT_AUTO From 56adc18ca26eadbcc701df501411fd1baed08aa8 Mon Sep 17 00:00:00 2001 From: Micah Peltier Date: Mon, 29 Aug 2022 11:24:45 -0600 Subject: [PATCH 02/33] feat: remove commented code Signed-off-by: Micah Peltier --- aries_cloudagent/protocols/out_of_band/v1_0/manager.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index 2670d4bf89..1fa0756740 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -133,10 +133,6 @@ async def create_invitation( "Cannot store metadata without handshake protocols" ) if public: - # if multi_use: - # raise OutOfBandManagerError( - # "Cannot create public invitation with multi_use" - # ) if metadata: raise OutOfBandManagerError( "Cannot store metadata on public invitations" From 9cdb75ed2f738c66193bdd903bff33ec10b71b3c Mon Sep 17 00:00:00 2001 From: Micah Peltier Date: Wed, 12 Oct 2022 11:49:52 -0600 Subject: [PATCH 03/33] feat: add flag for unsolicited requests Signed-off-by: Micah Peltier --- aries_cloudagent/config/argparse.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/config/argparse.py b/aries_cloudagent/config/argparse.py index c3cc337f6b..90b2abb2a0 100644 --- a/aries_cloudagent/config/argparse.py +++ b/aries_cloudagent/config/argparse.py @@ -1014,7 +1014,17 @@ def add_arguments(self, parser: ArgumentParser): action="store_true", env_var="ACAPY_PUBLIC_INVITES", help=( - "Send invitations out, and receive connection requests, " + "Send invitations out using the public DID for the agent, " + "and receive connection requests solicited by invitations " + "which use the public DID. Default: false." + ), + ) + parser.add_argument( + "--requests-through-public-did", + action="store_true", + env_var="ACAPY_REQUESTS_THROUGH_PUBLIC_DID", + help=( + "Allow agent to receive unsolicited connection requests, " "using the public DID for the agent. Default: false." ), ) @@ -1109,6 +1119,11 @@ def get_settings(self, args: Namespace) -> dict: settings["monitor_forward"] = args.monitor_forward if args.public_invites: settings["public_invites"] = True + if args.requests_through_public_did: + if not args.public_invites: + raise ArgsParseError("--public-invites is required to use " + "--requests-through-public-did") + settings["requests_through_public_did"] = True if args.timing: settings["timing.enabled"] = True if args.timing_log: From ff3edd851012c3774f622fdb4997c05c83936897 Mon Sep 17 00:00:00 2001 From: Micah Peltier Date: Wed, 12 Oct 2022 11:57:56 -0600 Subject: [PATCH 04/33] feat: allow metadata on public invites Signed-off-by: Micah Peltier --- aries_cloudagent/protocols/out_of_band/v1_0/manager.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index 1fa0756740..29ba806f5c 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -132,11 +132,6 @@ async def create_invitation( raise OutOfBandManagerError( "Cannot store metadata without handshake protocols" ) - if public: - if metadata: - raise OutOfBandManagerError( - "Cannot store metadata on public invitations" - ) if attachments and multi_use: raise OutOfBandManagerError( From 91ba45ba7cb7ef8d3025751cf6289948a0c6b1e4 Mon Sep 17 00:00:00 2001 From: Micah Peltier Date: Wed, 12 Oct 2022 12:05:01 -0600 Subject: [PATCH 05/33] feat: check for new flag Signed-off-by: Micah Peltier --- aries_cloudagent/protocols/didexchange/v1_0/manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index 587e6b56b5..523d307362 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -483,6 +483,9 @@ async def receive_request( ) else: # request is against implicit invitation on public DID + if not self.profile.settings.get("requests_through_public_did"): + raise DIDXManagerError("Unsolicited connection requests to " + "public DID is not enabled") async with self.profile.session() as session: wallet = session.inject(BaseWallet) my_info = await wallet.create_local_did( From 55e52d62b39a34f40c11ed16a19b7f9d1d7b2556 Mon Sep 17 00:00:00 2001 From: Micah Peltier Date: Wed, 12 Oct 2022 12:06:48 -0600 Subject: [PATCH 06/33] test: implicit invites Signed-off-by: Micah Peltier --- .../didexchange/v1_0/tests/test_manager.py | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py index 131d87670e..a59c52c10a 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py @@ -971,6 +971,149 @@ async def test_receive_request_public_did_no_auto_accept(self): messages = self.responder.messages assert not messages + async def test_receive_request_implicit_public_did_not_enabled(self): + async with self.profile.session() as session: + mock_request = async_mock.MagicMock( + did=TestConfig.test_did, + did_doc_attach=async_mock.MagicMock( + data=async_mock.MagicMock( + verify=async_mock.CoroutineMock(return_value=True), + signed=async_mock.MagicMock( + decode=async_mock.MagicMock(return_value="dummy-did-doc") + ), + ) + ), + _thread=async_mock.MagicMock(pthid="did:sov:publicdid0000000000000"), + ) + mediation_record = MediationRecord( + role=MediationRecord.ROLE_CLIENT, + state=MediationRecord.STATE_GRANTED, + connection_id=self.test_mediator_conn_id, + routing_keys=self.test_mediator_routing_keys, + endpoint=self.test_mediator_endpoint, + ) + await mediation_record.save(session) + + await session.wallet.create_local_did( + method=DIDMethod.SOV, + key_type=KeyType.ED25519, + seed=None, + did=TestConfig.test_did, + ) + + self.profile.context.update_settings({"public_invites": True}) + + with async_mock.patch.object( + test_module, "ConnRecord", async_mock.MagicMock() + ) as mock_conn_rec_cls, async_mock.patch.object( + test_module, "DIDDoc", autospec=True + ) as mock_did_doc, async_mock.patch.object( + test_module, "DIDPosture", autospec=True + ) as mock_did_posture, async_mock.patch.object( + self.manager, + "verify_diddoc", + async_mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did))): + mock_did_posture.get = async_mock.MagicMock( + return_value=test_module.DIDPosture.PUBLIC + ) + mock_conn_rec_cls.retrieve_by_invitation_key = async_mock.CoroutineMock( + side_effect=StorageNotFoundError() + ) + mock_conn_rec_cls.retrieve_by_invitation_msg_id = ( + async_mock.CoroutineMock(return_value=None) + ) + + with self.assertRaises(DIDXManagerError) as context: + await self.manager.receive_request( + request=mock_request, + recipient_did=TestConfig.test_did, + my_endpoint=None, + alias=None, + auto_accept_implicit=None, + ) + assert "Unsolicited connection requests" in str(context.exception) + + async def test_receive_request_implicit_public_did(self): + async with self.profile.session() as session: + mock_request = async_mock.MagicMock( + did=TestConfig.test_did, + did_doc_attach=async_mock.MagicMock( + data=async_mock.MagicMock( + verify=async_mock.CoroutineMock(return_value=True), + signed=async_mock.MagicMock( + decode=async_mock.MagicMock(return_value="dummy-did-doc") + ), + ) + ), + _thread=async_mock.MagicMock(pthid="did:sov:publicdid0000000000000"), + ) + mediation_record = MediationRecord( + role=MediationRecord.ROLE_CLIENT, + state=MediationRecord.STATE_GRANTED, + connection_id=self.test_mediator_conn_id, + routing_keys=self.test_mediator_routing_keys, + endpoint=self.test_mediator_endpoint, + ) + await mediation_record.save(session) + + await session.wallet.create_local_did( + method=DIDMethod.SOV, + key_type=KeyType.ED25519, + seed=None, + did=TestConfig.test_did, + ) + + self.profile.context.update_settings({"public_invites": True}) + self.profile.context.update_settings({"requests_through_public_did": True}) + ACCEPT_AUTO = ConnRecord.ACCEPT_AUTO + STATE_REQUEST = ConnRecord.State.REQUEST + + with async_mock.patch.object( + test_module, "ConnRecord", async_mock.MagicMock() + ) as mock_conn_rec_cls, async_mock.patch.object( + test_module, "DIDDoc", autospec=True + ) as mock_did_doc, async_mock.patch.object( + test_module, "DIDPosture", autospec=True + ) as mock_did_posture, async_mock.patch.object( + self.manager, + "verify_diddoc", + async_mock.CoroutineMock(return_value=DIDDoc(TestConfig.test_did))): + mock_did_posture.get = async_mock.MagicMock( + return_value=test_module.DIDPosture.PUBLIC + ) + mock_conn_rec_cls.retrieve_by_invitation_key = async_mock.CoroutineMock( + side_effect=StorageNotFoundError() + ) + mock_conn_rec_cls.retrieve_by_invitation_msg_id = ( + async_mock.CoroutineMock(return_value=None) + ) + + mock_conn_record = async_mock.MagicMock( + accept=ACCEPT_AUTO, + my_did=None, + state=STATE_REQUEST.rfc23, + attach_request=async_mock.CoroutineMock(), + retrieve_request=async_mock.CoroutineMock(), + metadata_get_all=async_mock.CoroutineMock(return_value={}), + metadata_get=async_mock.CoroutineMock(return_value=True), + save=async_mock.CoroutineMock(), + ) + + mock_conn_rec_cls.return_value = mock_conn_record + + conn_rec = await self.manager.receive_request( + request=mock_request, + recipient_did=TestConfig.test_did, + recipient_verkey=None, + my_endpoint=None, + alias=None, + auto_accept_implicit=None, + ) + assert conn_rec + self.oob_mock.clean_finished_oob_record.assert_called_once_with( + self.profile, mock_request + ) + async def test_receive_request_peer_did(self): async with self.profile.session() as session: mock_request = async_mock.MagicMock( From 6ecd8e6f87eec05cab5d0de99ba4070e4133f5c1 Mon Sep 17 00:00:00 2001 From: Micah Peltier Date: Wed, 12 Oct 2022 12:12:13 -0600 Subject: [PATCH 07/33] feat: allow public did + multi-use conn invites Signed-off-by: Micah Peltier --- .../protocols/connections/v1_0/manager.py | 146 ++++++++++-------- 1 file changed, 78 insertions(+), 68 deletions(-) diff --git a/aries_cloudagent/protocols/connections/v1_0/manager.py b/aries_cloudagent/protocols/connections/v1_0/manager.py index d937b7ecb2..6f5f07a861 100644 --- a/aries_cloudagent/protocols/connections/v1_0/manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/manager.py @@ -127,10 +127,42 @@ async def create_invitation( or_default=True, ) image_url = self.profile.context.settings.get("image_url") + invitation = None + connection = None + + invitation_mode = ConnRecord.INVITATION_MODE_ONCE + if multi_use: + invitation_mode = ConnRecord.INVITATION_MODE_MULTI if not my_label: my_label = self.profile.settings.get("default_label") + accept = ( + ConnRecord.ACCEPT_AUTO + if ( + auto_accept + or ( + auto_accept is None + and self.profile.settings.get("debug.auto_accept_requests") + ) + ) + else ConnRecord.ACCEPT_MANUAL + ) + + if recipient_keys: + # TODO: register recipient keys for relay + # TODO: check that recipient keys are in wallet + invitation_key = recipient_keys[0] # TODO first key appropriate? + else: + # Create and store new invitation key + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + invitation_signing_key = await wallet.create_signing_key( + key_type=KeyType.ED25519 + ) + invitation_key = invitation_signing_key.verkey + recipient_keys = [invitation_key] + if public: if not self.profile.settings.get("public_invites"): raise ConnectionManagerError("Public invitations are not enabled") @@ -143,89 +175,64 @@ async def create_invitation( "Cannot create public invitation with no public DID" ) - if multi_use: - raise ConnectionManagerError( - "Cannot use public and multi_use at the same time" - ) - - if metadata: - raise ConnectionManagerError( - "Cannot use public and set metadata at the same time" - ) - # FIXME - allow ledger instance to format public DID with prefix? invitation = ConnectionInvitation( label=my_label, did=f"did:sov:{public_did.did}", image_url=image_url ) + connection = ConnRecord( # create connection record + invitation_key=public_did.verkey, + invitation_msg_id=invitation._id, + invitation_mode=invitation_mode, + their_role=ConnRecord.Role.REQUESTER.rfc23, + state=ConnRecord.State.INVITATION.rfc23, + accept=accept, + alias=alias, + connection_protocol=CONN_PROTO, + ) + + async with self.profile.session() as session: + await connection.save(session, reason="Created new invitation") + # Add mapping for multitenant relaying. # Mediation of public keys is not supported yet await self._route_manager.route_public_did(self.profile, public_did.verkey) - return None, invitation - - invitation_mode = ConnRecord.INVITATION_MODE_ONCE - if multi_use: - invitation_mode = ConnRecord.INVITATION_MODE_MULTI - - if recipient_keys: - # TODO: register recipient keys for relay - # TODO: check that recipient keys are in wallet - invitation_key = recipient_keys[0] # TODO first key appropriate? else: - # Create and store new invitation key + # Create connection record + connection = ConnRecord( + invitation_key=invitation_key, # TODO: determine correct key to use + their_role=ConnRecord.Role.REQUESTER.rfc160, + state=ConnRecord.State.INVITATION.rfc160, + accept=accept, + invitation_mode=invitation_mode, + alias=alias, + connection_protocol=CONN_PROTO, + ) async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - invitation_signing_key = await wallet.create_signing_key( - key_type=KeyType.ED25519 - ) - invitation_key = invitation_signing_key.verkey - recipient_keys = [invitation_key] + await connection.save(session, reason="Created new invitation") - accept = ( - ConnRecord.ACCEPT_AUTO - if ( - auto_accept - or ( - auto_accept is None - and self.profile.settings.get("debug.auto_accept_requests") - ) + await self._route_manager.route_invitation( + self.profile, connection, mediation_record + ) + routing_keys, my_endpoint = await self._route_manager.routing_info( + self.profile, + my_endpoint or cast(str, self.profile.settings.get("default_endpoint")), + mediation_record, ) - else ConnRecord.ACCEPT_MANUAL - ) - - # Create connection record - connection = ConnRecord( - invitation_key=invitation_key, # TODO: determine correct key to use - their_role=ConnRecord.Role.REQUESTER.rfc160, - state=ConnRecord.State.INVITATION.rfc160, - accept=accept, - invitation_mode=invitation_mode, - alias=alias, - connection_protocol=CONN_PROTO, - ) - async with self.profile.session() as session: - await connection.save(session, reason="Created new invitation") - await self._route_manager.route_invitation( - self.profile, connection, mediation_record - ) - routing_keys, my_endpoint = await self._route_manager.routing_info( - self.profile, - my_endpoint or cast(str, self.profile.settings.get("default_endpoint")), - mediation_record, - ) + # Create connection invitation message + # Note: Need to split this into two stages + # to support inbound routing of invites + # Would want to reuse create_did_document and convert the result + invitation = ConnectionInvitation( + label=my_label, + recipient_keys=recipient_keys, + routing_keys=routing_keys, + endpoint=my_endpoint, + image_url=image_url, + ) - # Create connection invitation message - # Note: Need to split this into two stages to support inbound routing of invites - # Would want to reuse create_did_document and convert the result - invitation = ConnectionInvitation( - label=my_label, - recipient_keys=recipient_keys, - routing_keys=routing_keys, - endpoint=my_endpoint, - image_url=image_url, - ) async with self.profile.session() as session: await connection.attach_invitation(session, invitation) @@ -531,6 +538,9 @@ async def receive_request( their_role=ConnRecord.Role.REQUESTER.rfc160, ) if not connection: + if not self.profile.settings.get("requests_through_public_did"): + raise ConnectionManagerError("Unsolicited connection requests to " + "public DID is not enabled") connection = ConnRecord() connection.invitation_key = connection_key connection.my_did = my_info.did From e640a4e61957b225315396e09c1c5a4a1fbdf280 Mon Sep 17 00:00:00 2001 From: Micah Peltier Date: Wed, 12 Oct 2022 12:13:19 -0600 Subject: [PATCH 08/33] tests: check new flags, conn manager changes Signed-off-by: Micah Peltier --- .../connections/v1_0/tests/test_manager.py | 113 +++++++++++++----- 1 file changed, 80 insertions(+), 33 deletions(-) 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 d7c3836236..15d6e18454 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -35,6 +35,7 @@ from ....discovery.v2_0.manager import V20DiscoveryMgr from ..manager import ConnectionManager, ConnectionManagerError +from .. import manager as test_module from ..messages.connection_invitation import ConnectionInvitation from ..messages.connection_request import ConnectionRequest from ..messages.connection_response import ConnectionResponse @@ -112,21 +113,6 @@ async def setUp(self): self.manager = ConnectionManager(self.profile) assert self.manager.profile - async def test_create_invitation_public_and_multi_use_fails(self): - self.context.update_settings({"public_invites": True}) - with async_mock.patch.object( - InMemoryWallet, "get_public_did", autospec=True - ) as mock_wallet_get_public_did: - mock_wallet_get_public_did.return_value = DIDInfo( - self.test_did, - self.test_verkey, - None, - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - with self.assertRaises(ConnectionManagerError): - await self.manager.create_invitation(public=True, multi_use=True) - async def test_create_invitation_non_multi_use_invitation_fails_on_reuse(self): connect_record, connect_invite = await self.manager.create_invitation() @@ -174,7 +160,7 @@ async def test_create_invitation_public(self): public=True, my_endpoint="testendpoint" ) - assert connect_record is None + assert connect_record assert connect_invite.did.endswith(self.test_did) self.route_manager.route_public_did.assert_called_once_with( self.profile, self.test_verkey @@ -266,23 +252,6 @@ async def test_create_invitation_metadata_assigned(self): assert await record.metadata_get_all(session) == {"hello": "world"} - async def test_create_invitation_public_and_metadata_fails(self): - self.context.update_settings({"public_invites": True}) - with async_mock.patch.object( - InMemoryWallet, "get_public_did", autospec=True - ) as mock_wallet_get_public_did: - mock_wallet_get_public_did.return_value = DIDInfo( - self.test_did, - self.test_verkey, - None, - method=DIDMethod.SOV, - key_type=KeyType.ED25519, - ) - with self.assertRaises(ConnectionManagerError): - await self.manager.create_invitation( - public=True, metadata={"hello": "world"} - ) - async def test_create_invitation_multi_use_metadata_transfers_to_connection(self): async with self.profile.session() as session: connect_record, _ = await self.manager.create_invitation( @@ -643,7 +612,84 @@ async def test_receive_request_public_did_oob_invite(self): self.profile, mock_request ) + async def test_receive_request_public_did_unsolicited_fails(self): + async with self.profile.session() as session: + mock_request = async_mock.MagicMock() + mock_request.connection = async_mock.MagicMock() + mock_request.connection.did = self.test_did + mock_request.connection.did_doc = async_mock.MagicMock() + mock_request.connection.did_doc.did = self.test_did + + receipt = MessageReceipt( + recipient_did=self.test_did, recipient_did_public=True + ) + await session.wallet.create_local_did( + method=DIDMethod.SOV, + key_type=KeyType.ED25519, + seed=None, + did=self.test_did, + ) + + self.context.update_settings({"public_invites": True}) + with self.assertRaises( + ConnectionManagerError + ), async_mock.patch.object( + ConnRecord, "connection_id", autospec=True + ), async_mock.patch.object( + ConnRecord, "save", autospec=True + ) as mock_conn_rec_save, async_mock.patch.object( + ConnRecord, "attach_request", autospec=True + ) as mock_conn_attach_request, async_mock.patch.object( + ConnRecord, "retrieve_by_id", autospec=True + ) as mock_conn_retrieve_by_id, async_mock.patch.object( + ConnRecord, "retrieve_request", autospec=True + ), async_mock.patch.object( + ConnRecord, "retrieve_by_invitation_msg_id", async_mock.CoroutineMock() + ) as mock_conn_retrieve_by_invitation_msg_id: + mock_conn_retrieve_by_invitation_msg_id.return_value = None + conn_rec = await self.manager.receive_request(mock_request, receipt) + async def test_receive_request_public_did_conn_invite(self): + async with self.profile.session() as session: + mock_request = async_mock.MagicMock() + mock_request.connection = async_mock.MagicMock() + mock_request.connection.did = self.test_did + mock_request.connection.did_doc = async_mock.MagicMock() + mock_request.connection.did_doc.did = self.test_did + + receipt = MessageReceipt( + recipient_did=self.test_did, recipient_did_public=True + ) + await session.wallet.create_local_did( + method=DIDMethod.SOV, + key_type=KeyType.ED25519, + seed=None, + did=self.test_did, + ) + + mock_connection_record = async_mock.MagicMock() + mock_connection_record.save = async_mock.CoroutineMock() + mock_connection_record.attach_request = async_mock.CoroutineMock() + + + self.context.update_settings({"public_invites": True}) + with async_mock.patch.object( + ConnRecord, "connection_id", autospec=True + ), async_mock.patch.object( + ConnRecord, "save", autospec=True + ) as mock_conn_rec_save, async_mock.patch.object( + ConnRecord, "attach_request", autospec=True + ) as mock_conn_attach_request, async_mock.patch.object( + ConnRecord, "retrieve_by_id", autospec=True + ) as mock_conn_retrieve_by_id, async_mock.patch.object( + ConnRecord, "retrieve_request", autospec=True + ), async_mock.patch.object( + ConnRecord, "retrieve_by_invitation_msg_id", async_mock.CoroutineMock(return_value=mock_connection_record) + ) as mock_conn_retrieve_by_invitation_msg_id: + conn_rec = await self.manager.receive_request(mock_request, receipt) + assert conn_rec + + async def test_receive_request_public_did_unsolicited(self): async with self.profile.session() as session: mock_request = async_mock.MagicMock() mock_request.connection = async_mock.MagicMock() @@ -662,6 +708,7 @@ async def test_receive_request_public_did_conn_invite(self): ) self.context.update_settings({"public_invites": True}) + self.context.update_settings({"requests_through_public_did": True}) with async_mock.patch.object( ConnRecord, "connection_id", autospec=True ), async_mock.patch.object( From ad44b028053c38eae85ba774c0f2def33eda178f Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 14 Oct 2022 16:30:40 -0400 Subject: [PATCH 09/33] fix: public did mediator routing keys as did keys Squash of multiple commits: fix: revert changes to manager fix: don't use dereference_as fix: correct typo in method fix: remove stray print statement Signed-off-by: Daniel Bluhm --- aries_cloudagent/connections/base_manager.py | 8 +++-- .../protocols/out_of_band/v1_0/manager.py | 1 + aries_cloudagent/resolver/default/indy.py | 17 ++++++++-- aries_cloudagent/resolver/did_resolver.py | 31 +++++++++++-------- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/aries_cloudagent/connections/base_manager.py b/aries_cloudagent/connections/base_manager.py index 4308705c31..53172b9966 100644 --- a/aries_cloudagent/connections/base_manager.py +++ b/aries_cloudagent/connections/base_manager.py @@ -269,16 +269,18 @@ async def resolve_invitation( endpoint = first_didcomm_service.service_endpoint recipient_keys: List[VerificationMethod] = [ - doc.dereference(url) for url in first_didcomm_service.recipient_keys + await resolver.dereference(self._profile, url, document=doc) + for url in first_didcomm_service.recipient_keys ] routing_keys: List[VerificationMethod] = [ - doc.dereference(url) for url in first_didcomm_service.routing_keys + await resolver.dereference(self._profile, url, document=doc) + for url in first_didcomm_service.routing_keys ] for key in [*recipient_keys, *routing_keys]: if not isinstance(key, self.SUPPORTED_KEY_TYPES): raise BaseConnectionManagerError( - f"Key type {key.type} is not supported" + f"Key type {type(key).__name__} is not supported" ) return ( diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index 7f160ed63c..3c9e300b95 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -222,6 +222,7 @@ async def create_invitation( async with self.profile.session() as session: wallet = session.inject(BaseWallet) public_did = await wallet.get_public_did() + if not public_did: raise OutOfBandManagerError( "Cannot create public invitation with no public DID" diff --git a/aries_cloudagent/resolver/default/indy.py b/aries_cloudagent/resolver/default/indy.py index ad34b90487..7f86025ed2 100644 --- a/aries_cloudagent/resolver/default/indy.py +++ b/aries_cloudagent/resolver/default/indy.py @@ -11,6 +11,7 @@ from ...config.injection_context import InjectionContext from ...core.profile import Profile +from ...did.did_key import DIDKey from ...ledger.endpoint_type import EndpointType from ...ledger.error import LedgerError from ...ledger.multiple_ledger.ledger_requests_executor import ( @@ -19,7 +20,7 @@ ) from ...messaging.valid import IndyDID from ...multitenant.base import BaseMultitenantManager - +from ...wallet.key_type import KeyType from ..base import BaseDIDResolver, DIDNotFound, ResolverError, ResolverType LOGGER = logging.getLogger(__name__) @@ -29,6 +30,16 @@ class NoIndyLedger(ResolverError): """Raised when there is no Indy ledger instance configured.""" +def _routing_keys_as_did_key_urls(routing_keys: Sequence[str]) -> Sequence[str]: + """Convert raw base58 keys to did:key values.""" + return [ + DIDKey.from_public_key_b58(routing_key, KeyType.ED25519).key_id + if not routing_key.startswith("did:key:") + else routing_key + for routing_key in routing_keys + ] + + class IndyDIDResolver(BaseDIDResolver): """Indy DID Resolver.""" @@ -101,7 +112,7 @@ def add_services( type_=self.SERVICE_TYPE_DID_COMMUNICATION, service_endpoint=endpoint, priority=1, - routing_keys=routing_keys, + routing_keys=_routing_keys_as_did_key_urls(routing_keys), recipient_keys=[recipient_key.id], accept=( service_accept if service_accept else ["didcomm/aip2;env=rfc19"] @@ -114,7 +125,7 @@ def add_services( type_=self.SERVICE_TYPE_DIDCOMM, service_endpoint=endpoint, recipient_keys=[recipient_key.id], - routing_keys=routing_keys, + routing_keys=_routing_keys_as_did_key_urls(routing_keys), # CHECKME # accept=(service_accept if service_accept else ["didcomm/v2"]), accept=["didcomm/v2"], diff --git a/aries_cloudagent/resolver/did_resolver.py b/aries_cloudagent/resolver/did_resolver.py index a2bdf48cbc..b523b59607 100644 --- a/aries_cloudagent/resolver/did_resolver.py +++ b/aries_cloudagent/resolver/did_resolver.py @@ -8,10 +8,11 @@ from datetime import datetime from itertools import chain import logging -from typing import Optional, List, Sequence, Tuple, Text, Type, TypeVar, Union +from typing import List, Optional, Sequence, Text, Tuple, Union -from pydid import DID, DIDError, DIDUrl, Resource, NonconformantDocument -from pydid.doc.doc import IDNotFoundError +from pydid import DID, DIDError, DIDUrl, Resource +import pydid +from pydid.doc.doc import BaseDIDDocument, IDNotFoundError from ..core.profile import Profile from .base import ( @@ -26,9 +27,6 @@ LOGGER = logging.getLogger(__name__) -ResourceType = TypeVar("ResourceType", bound=Resource) - - class DIDResolver: """did resolver singleton.""" @@ -115,8 +113,12 @@ async def _match_did_to_resolver( return resolvers async def dereference( - self, profile: Profile, did_url: str, *, cls: Type[ResourceType] = Resource - ) -> ResourceType: + self, + profile: Profile, + did_url: str, + *, + document: Optional[BaseDIDDocument] = None, + ) -> Resource: """Dereference a DID URL to its corresponding DID Doc object.""" # TODO Use cached DID Docs when possible try: @@ -128,12 +130,15 @@ async def dereference( "Failed to parse DID URL from {}".format(did_url) ) from err - doc_dict = await self.resolve(profile, parsed.did) - # Use non-conformant doc as the "least common denominator" + if document and parsed.did != document.id: + document = None + + if not document: + doc_dict = await self.resolve(profile, parsed.did) + document = pydid.deserialize_document(doc_dict) + try: - return NonconformantDocument.deserialize(doc_dict).dereference_as( - cls, parsed - ) + return document.dereference(parsed) except IDNotFoundError as error: raise ResolverError( "Failed to dereference DID URL: {}".format(error) From 42cf9d578235a4765004786deee1b8d03ec05f77 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 20 Oct 2022 12:06:41 -0400 Subject: [PATCH 10/33] fix: remove cls from resolver.dereference() call Signed-off-by: Char Howland --- aries_cloudagent/messaging/jsonld/routes.py | 2 -- .../messaging/jsonld/tests/test_routes.py | 26 +++++++++---------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/aries_cloudagent/messaging/jsonld/routes.py b/aries_cloudagent/messaging/jsonld/routes.py index f0ff1b8c29..eb3b7b48c2 100644 --- a/aries_cloudagent/messaging/jsonld/routes.py +++ b/aries_cloudagent/messaging/jsonld/routes.py @@ -5,7 +5,6 @@ from marshmallow import INCLUDE, Schema, fields from pydid.verification_method import ( Ed25519VerificationKey2018, - KnownVerificationMethods, ) from ...admin.request_context import AdminRequestContext @@ -148,7 +147,6 @@ async def verify(request: web.BaseRequest): vmethod = await resolver.dereference( profile, doc["proof"]["verificationMethod"], - cls=KnownVerificationMethods, ) if not isinstance(vmethod, SUPPORTED_VERIFICATION_METHOD_TYPES): diff --git a/aries_cloudagent/messaging/jsonld/tests/test_routes.py b/aries_cloudagent/messaging/jsonld/tests/test_routes.py index 129ac26515..1302749fc1 100644 --- a/aries_cloudagent/messaging/jsonld/tests/test_routes.py +++ b/aries_cloudagent/messaging/jsonld/tests/test_routes.py @@ -234,22 +234,22 @@ async def test_verify_bad_ver_meth_deref_req_error( assert "error" in mock_response.call_args[0][0] -@pytest.mark.asyncio -async def test_verify_bad_ver_meth_not_ver_meth( - mock_resolver, mock_verify_request, mock_response, request_body -): - request_body["doc"]["proof"][ - "verificationMethod" - ] = "did:example:1234abcd#did-communication" - await test_module.verify(mock_verify_request(request_body)) - assert "error" in mock_response.call_args[0][0] - - +@pytest.mark.parametrize( + "vmethod", + [ + "did:example:1234abcd#key-2", + "did:example:1234abcd#did-communication", + ], +) @pytest.mark.asyncio async def test_verify_bad_vmethod_unsupported( - mock_resolver, mock_verify_request, mock_response, request_body + mock_resolver, + mock_verify_request, + mock_response, + request_body, + vmethod, ): - request_body["doc"]["proof"]["verificationMethod"] = "did:example:1234abcd#key-2" + request_body["doc"]["proof"]["verificationMethod"] = vmethod with pytest.raises(web.HTTPBadRequest): await test_module.verify(mock_verify_request(request_body)) From 2bf13c73230d5be0edc4633cf28a65b63e483424 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 20 Oct 2022 12:07:41 -0400 Subject: [PATCH 11/33] fix: mock dereference Signed-off-by: Char Howland --- .../protocols/connections/v1_0/tests/test_manager.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 80efb456bf..e3631478c1 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -2068,6 +2068,9 @@ async def test_fetch_connection_targets_conn_invitation_did_resolver(self): return_value=self.test_endpoint ) self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) + self.resolver.dereference = async_mock.CoroutineMock( + return_value = did_doc.verification_method[0] + ) self.context.injector.bind_instance(DIDResolver, self.resolver) local_did = await session.wallet.create_local_did( @@ -2137,6 +2140,9 @@ async def test_fetch_connection_targets_conn_invitation_btcr_resolver(self): return_value=self.test_endpoint ) self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) + self.resolver.dereference = async_mock.CoroutineMock( + return_value = did_doc.verification_method[0] + ) self.context.injector.bind_instance(DIDResolver, self.resolver) local_did = await session.wallet.create_local_did( method=DIDMethod.SOV, @@ -2288,6 +2294,9 @@ async def test_fetch_connection_targets_conn_invitation_unsupported_key_type(sel return_value=self.test_endpoint ) self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) + self.resolver.dereference = async_mock.CoroutineMock( + return_value = did_doc.verification_method[0] + ) self.context.injector.bind_instance(DIDResolver, self.resolver) local_did = await session.wallet.create_local_did( method=DIDMethod.SOV, @@ -2357,6 +2366,9 @@ async def test_fetch_connection_targets_oob_invitation_svc_did_resolver(self): self.resolver = async_mock.MagicMock() self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) + self.resolver.dereference = async_mock.CoroutineMock( + return_value = did_doc.verification_method[0] + ) self.context.injector.bind_instance(DIDResolver, self.resolver) local_did = await session.wallet.create_local_did( From 206ccda1ea0fdcc5fb47e79880da43397205408d Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 20 Oct 2022 12:09:17 -0400 Subject: [PATCH 12/33] feat: convert did key to did key url Signed-off-by: Char Howland --- aries_cloudagent/resolver/default/indy.py | 26 +++++++++++++++++------ 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/aries_cloudagent/resolver/default/indy.py b/aries_cloudagent/resolver/default/indy.py index 7f86025ed2..2bdef2cf8d 100644 --- a/aries_cloudagent/resolver/default/indy.py +++ b/aries_cloudagent/resolver/default/indy.py @@ -31,13 +31,25 @@ class NoIndyLedger(ResolverError): def _routing_keys_as_did_key_urls(routing_keys: Sequence[str]) -> Sequence[str]: - """Convert raw base58 keys to did:key values.""" - return [ - DIDKey.from_public_key_b58(routing_key, KeyType.ED25519).key_id - if not routing_key.startswith("did:key:") - else routing_key - for routing_key in routing_keys - ] + """Convert raw base58 keys to did:key values. + + If a did:key is passed in, convert to a did:key URL. + """ + + did_key_urls = [] + for routing_key in routing_keys: + if not routing_key.startswith("did:key:"): + did_key_urls.append( + DIDKey.from_public_key_b58(routing_key, KeyType.ED25519).key_id + ) + else: + if "#" not in routing_key: + did_key_urls.append( + f"{routing_key}#{DIDKey.from_did(routing_key).fingerprint}" + ) + else: + return routing_keys + return did_key_urls class IndyDIDResolver(BaseDIDResolver): From 795c58f9089a2eb6d7f353e2d4d5170a121f232f Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 20 Oct 2022 12:10:29 -0400 Subject: [PATCH 13/33] test: _routing_keys_as_did_key_urls Signed-off-by: Char Howland --- .../resolver/default/tests/test_indy.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/resolver/default/tests/test_indy.py b/aries_cloudagent/resolver/default/tests/test_indy.py index c2c53a3e92..6287fb5352 100644 --- a/aries_cloudagent/resolver/default/tests/test_indy.py +++ b/aries_cloudagent/resolver/default/tests/test_indy.py @@ -17,7 +17,7 @@ from ....multitenant.manager import MultitenantManager from ...base import DIDNotFound, ResolverError -from ..indy import IndyDIDResolver +from ..indy import IndyDIDResolver, _routing_keys_as_did_key_urls # pylint: disable=W0621 TEST_DID0 = "did:sov:WgWxqztrNooG92RXvxSTWv" @@ -127,7 +127,7 @@ async def test_supports_updated_did_sov_rules( """Test that new attrib structure is supported.""" example = { "endpoint": "https://example.com/endpoint", - "routingKeys": ["a-routing-key"], + "routingKeys": ["HQhjaj4mcaS3Xci27a9QhnBrNpS91VNFUU4TDrtMxa9j"], "types": ["DIDComm", "did-communication", "endpoint"], "profile": "https://example.com", "linked_domains": "https://example.com", @@ -177,3 +177,18 @@ async def test_supports_updated_did_sov_rules_no_endpoint_url( ) def test_process_endpoint_types(self, resolver: IndyDIDResolver, types, result): assert resolver.process_endpoint_types(types) == result + + @pytest.mark.parametrize( + "keys", + [ + ["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], + ["did:key:z6MkgzZFYHiH9RhyMmkoyvNvVwnvgLxkVrJbureLx9HXsuKA"], + [ + "did:key:z6MkgzZFYHiH9RhyMmkoyvNvVwnvgLxkVrJbureLx9HXsuKA#z6MkgzZFYHiH9RhyMmkoyvNvVwnvgLxkVrJbureLx9HXsuKA" + ], + ], + ) + def test_routing_keys_as_did_key_urls(self, keys): + for key in _routing_keys_as_did_key_urls(keys): + assert key.startswith("did:key:") + assert "#" in key From 1f1c19c40c546b81288ff08e67d52d55b8268b8a Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 20 Oct 2022 12:11:30 -0400 Subject: [PATCH 14/33] test: resolver.dereference() with diddoc passed in Signed-off-by: Char Howland --- .../resolver/tests/test_did_resolver.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/resolver/tests/test_did_resolver.py b/aries_cloudagent/resolver/tests/test_did_resolver.py index 2bcbb4ed42..58c459b307 100644 --- a/aries_cloudagent/resolver/tests/test_did_resolver.py +++ b/aries_cloudagent/resolver/tests/test_did_resolver.py @@ -7,7 +7,7 @@ import pytest from asynctest import mock as async_mock -from pydid import DID, DIDDocument, VerificationMethod +from pydid import DID, DIDDocument, VerificationMethod, BasicDIDDocument from ..base import ( BaseDIDResolver, @@ -153,6 +153,17 @@ async def test_dereference(resolver, profile): assert expected == actual.serialize() +@pytest.mark.asyncio +async def test_dereference_diddoc(resolver, profile): + url = "did:example:1234abcd#4" + doc = BasicDIDDocument( + id="did:example:z6Mkmpe2DyE4NsDiAb58d75hpi1BjqbH6wYMschUkjWDEEuR" + ) + result = await resolver.dereference(profile, url, document=doc) + assert isinstance(result, VerificationMethod) + assert result.id == url + + @pytest.mark.asyncio async def test_dereference_x(resolver, profile): url = "non-did" From d669be25e9ddd4e7941d201895326670aeb5a211 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 20 Oct 2022 15:06:48 -0400 Subject: [PATCH 15/33] style: fix formatting Signed-off-by: Daniel Bluhm --- .../protocols/connections/v1_0/tests/test_manager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 e3631478c1..285d3e9ff8 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -2069,7 +2069,7 @@ async def test_fetch_connection_targets_conn_invitation_did_resolver(self): ) self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) self.resolver.dereference = async_mock.CoroutineMock( - return_value = did_doc.verification_method[0] + return_value=did_doc.verification_method[0] ) self.context.injector.bind_instance(DIDResolver, self.resolver) @@ -2141,7 +2141,7 @@ async def test_fetch_connection_targets_conn_invitation_btcr_resolver(self): ) self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) self.resolver.dereference = async_mock.CoroutineMock( - return_value = did_doc.verification_method[0] + return_value=did_doc.verification_method[0] ) self.context.injector.bind_instance(DIDResolver, self.resolver) local_did = await session.wallet.create_local_did( @@ -2295,7 +2295,7 @@ async def test_fetch_connection_targets_conn_invitation_unsupported_key_type(sel ) self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) self.resolver.dereference = async_mock.CoroutineMock( - return_value = did_doc.verification_method[0] + return_value=did_doc.verification_method[0] ) self.context.injector.bind_instance(DIDResolver, self.resolver) local_did = await session.wallet.create_local_did( @@ -2367,7 +2367,7 @@ async def test_fetch_connection_targets_oob_invitation_svc_did_resolver(self): self.resolver = async_mock.MagicMock() self.resolver.resolve = async_mock.CoroutineMock(return_value=did_doc) self.resolver.dereference = async_mock.CoroutineMock( - return_value = did_doc.verification_method[0] + return_value=did_doc.verification_method[0] ) self.context.injector.bind_instance(DIDResolver, self.resolver) From 997d263c564e921b0f985f2f53703a2caf7d6585 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 27 Oct 2022 11:12:11 -0600 Subject: [PATCH 16/33] fix: account for changes to KeyType Signed-off-by: Char Howland --- aries_cloudagent/resolver/default/indy.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/resolver/default/indy.py b/aries_cloudagent/resolver/default/indy.py index 2bdef2cf8d..8c98778d57 100644 --- a/aries_cloudagent/resolver/default/indy.py +++ b/aries_cloudagent/resolver/default/indy.py @@ -20,7 +20,7 @@ ) from ...messaging.valid import IndyDID from ...multitenant.base import BaseMultitenantManager -from ...wallet.key_type import KeyType +from ...wallet.key_type import ED25519 from ..base import BaseDIDResolver, DIDNotFound, ResolverError, ResolverType LOGGER = logging.getLogger(__name__) @@ -39,9 +39,10 @@ def _routing_keys_as_did_key_urls(routing_keys: Sequence[str]) -> Sequence[str]: did_key_urls = [] for routing_key in routing_keys: if not routing_key.startswith("did:key:"): - did_key_urls.append( - DIDKey.from_public_key_b58(routing_key, KeyType.ED25519).key_id - ) + did_key_urls.append(DIDKey.from_public_key_b58(routing_key, ED25519).key_id) + # NOTE: using hardcoded prefix ED25519 because py_multicodec library + # is outdated. Update after this PR gets released: + # https://github.com/multiformats/py-multicodec/pull/14 else: if "#" not in routing_key: did_key_urls.append( From 87905a86319466e086a506124a732076294d4a05 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 27 Oct 2022 11:29:48 -0600 Subject: [PATCH 17/33] fix: remove note Signed-off-by: Char Howland --- aries_cloudagent/resolver/default/indy.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/aries_cloudagent/resolver/default/indy.py b/aries_cloudagent/resolver/default/indy.py index 8c98778d57..ce18f75c67 100644 --- a/aries_cloudagent/resolver/default/indy.py +++ b/aries_cloudagent/resolver/default/indy.py @@ -40,9 +40,6 @@ def _routing_keys_as_did_key_urls(routing_keys: Sequence[str]) -> Sequence[str]: for routing_key in routing_keys: if not routing_key.startswith("did:key:"): did_key_urls.append(DIDKey.from_public_key_b58(routing_key, ED25519).key_id) - # NOTE: using hardcoded prefix ED25519 because py_multicodec library - # is outdated. Update after this PR gets released: - # https://github.com/multiformats/py-multicodec/pull/14 else: if "#" not in routing_key: did_key_urls.append( From 93cbd2a22ccb5ca9b34735ef8744f4ab6a7bd756 Mon Sep 17 00:00:00 2001 From: Micah Peltier Date: Mon, 28 Nov 2022 11:55:48 -0700 Subject: [PATCH 18/33] fix: save metadata on OOB invitation with public DID Signed-off-by: Micah Peltier --- aries_cloudagent/protocols/out_of_band/v1_0/manager.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index 30e51e1e99..7737891d3a 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -258,6 +258,12 @@ async def create_invitation( async with self.profile.session() as session: await conn_rec.save(session, reason="Created new invitation") await conn_rec.attach_invitation(session, invi_msg) + + await conn_rec.attach_invitation(session, invi_msg) + + if metadata: + for key, value in metadata.items(): + await conn_rec.metadata_set(session, key, value) else: our_service = ServiceDecorator( recipient_keys=[our_recipient_key], From e9d07cf65120ff125f798cd27d457a73e9638631 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 5 Dec 2022 18:40:33 -0700 Subject: [PATCH 19/33] fix: black code formatter Signed-off-by: Char Howland --- aries_cloudagent/protocols/issue_credential/v1_0/routes.py | 6 ++++-- aries_cloudagent/protocols/issue_credential/v2_0/routes.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py index 90853c598b..3cba79cb88 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py @@ -382,8 +382,10 @@ async def credential_exchange_retrieve(request: web.BaseRequest): @docs( tags=["issue-credential v1.0"], - summary=("Create a credential record without " - "sending (generally for use with Out-Of-Band)"), + summary=( + "Create a credential record without " + "sending (generally for use with Out-Of-Band)" + ), ) @request_schema(V10CredentialCreateSchema()) @response_schema(V10CredentialExchangeSchema(), 200, description="") diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py index f35745f7ab..e3a066d339 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py @@ -521,8 +521,10 @@ async def credential_exchange_retrieve(request: web.BaseRequest): @docs( tags=["issue-credential v2.0"], - summary=("Create a credential record without " - "sending (generally for use with Out-Of-Band)"), + summary=( + "Create a credential record without " + "sending (generally for use with Out-Of-Band)" + ), ) @request_schema(V20IssueCredSchemaCore()) @response_schema(V20CredExRecordSchema(), 200, description="") From 8d370c1fd0d544b846d8fb9193f3a0ad0bcb906b Mon Sep 17 00:00:00 2001 From: Micah Peltier Date: Wed, 14 Dec 2022 10:36:19 -0700 Subject: [PATCH 20/33] fix: DIDMethod refs in tests Signed-off-by: Micah Peltier --- .../protocols/connections/v1_0/tests/test_manager.py | 8 ++++---- .../protocols/didexchange/v1_0/tests/test_manager.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) 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 6b0b6171f3..9d651c7f87 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -658,8 +658,8 @@ async def test_receive_request_public_did_conn_invite(self): recipient_did=self.test_did, recipient_did_public=True ) await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, + method=SOV, + key_type=ED25519, seed=None, did=self.test_did, ) @@ -699,8 +699,8 @@ async def test_receive_request_public_did_unsolicited(self): recipient_did=self.test_did, recipient_did_public=True ) await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, + method=SOV, + key_type=ED25519, seed=None, did=self.test_did, ) diff --git a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py index f912290daa..05641b86a4 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py @@ -988,8 +988,8 @@ async def test_receive_request_implicit_public_did_not_enabled(self): await mediation_record.save(session) await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, + method=SOV, + key_type=ED25519, seed=None, did=TestConfig.test_did, ) @@ -1051,8 +1051,8 @@ async def test_receive_request_implicit_public_did(self): await mediation_record.save(session) await session.wallet.create_local_did( - method=DIDMethod.SOV, - key_type=KeyType.ED25519, + method=SOV, + key_type=ED25519, seed=None, did=TestConfig.test_did, ) From eb204c67c8853d467fb84079469c0b5f6a182358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dobros=C5=82aw=20=C5=BBybort?= Date: Thu, 15 Dec 2022 13:09:11 +0100 Subject: [PATCH 21/33] Add missing --mediator-connections-invite cmd arg info to docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dobrosław Żybort --- Mediation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Mediation.md b/Mediation.md index 425996e16e..c301a39e77 100644 --- a/Mediation.md +++ b/Mediation.md @@ -15,6 +15,7 @@ * `--open-mediation` - Instructs mediators to automatically grant all incoming mediation requests. * `--mediator-invitation` - Receive invitation, send mediation request and set as default mediator. +* `--mediator-connections-invite` - Connect to mediator through a connection invitation. If not specified, connect using an OOB invitation. * `--default-mediator-id` - Set pre-existing mediator as default mediator. * `--clear-default-mediator` - Clear the stored default mediator. @@ -72,4 +73,4 @@ See [Aries RFC 0211: Coordinate Mediation Protocol](https://github.com/hyperledg ## Using a Mediator After establishing a connection with a mediator also having mediation granted, you can use that mediator id for future did_comm connections. - When creating, receiving or accepting a invitation intended to be Mediated, you provide `mediation_id` with the desired mediator id. if using a single mediator for all future connections, You can set a default mediation id. If no mediation_id is provided the default mediation id will be used instead. \ No newline at end of file + When creating, receiving or accepting a invitation intended to be Mediated, you provide `mediation_id` with the desired mediator id. if using a single mediator for all future connections, You can set a default mediation id. If no mediation_id is provided the default mediation id will be used instead. From 0e1f06fe63cb14bb99848041d819ea447c01b4dd Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 22 Dec 2022 14:54:41 -0800 Subject: [PATCH 22/33] do not reject invitation with unknown handshake protocol(s) Signed-off-by: Andrew Whitehead --- .../out_of_band/v1_0/messages/invitation.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py b/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py index e461201975..e51da83d60 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py @@ -221,9 +221,6 @@ class Meta: fields.Str( description="Handshake protocol", example=DIDCommPrefix.qualify_current(HSProto.RFC23.name), - validate=lambda hsp: ( - DIDCommPrefix.unqualify(hsp) in [p.name for p in HSProto] - ), ), required=False, ) @@ -275,14 +272,11 @@ def validate_fields(self, data, **kwargs): ValidationError: If any of the fields do not validate """ handshake_protocols = data.get("handshake_protocols") - requests_attach = data.get("requests_attach") - if not ( - (handshake_protocols and len(handshake_protocols) > 0) - or (requests_attach and len(requests_attach) > 0) - ): + requests_attach = data.get("requests~attach") + if not handshake_protocols and not requests_attach: raise ValidationError( "Model must include non-empty " - "handshake_protocols or requests_attach or both" + "handshake_protocols or requests~attach or both" ) # services = data.get("services") From 50312e56279a82c4dcf22c29f5d8d3e8ca29daf5 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 22 Dec 2022 15:12:30 -0800 Subject: [PATCH 23/33] fix service entry validation Signed-off-by: Andrew Whitehead --- aries_cloudagent/messaging/valid.py | 49 ++++++------------- .../v1_0/models/connection_detail.py | 2 +- .../out_of_band/v1_0/messages/invitation.py | 12 +++-- .../v1_0/messages/tests/test_invitation.py | 5 +- 4 files changed, 26 insertions(+), 42 deletions(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 6253c6b304..5af2a2d7be 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -23,60 +23,41 @@ class StrOrDictField(Field): """URI or Dict field for Marshmallow.""" - def _serialize(self, value, attr, obj, **kwargs): - return value - def _deserialize(self, value, attr, data, **kwargs): - if isinstance(value, (str, dict)): - return value - else: + if not isinstance(value, (str, dict)): raise ValidationError("Field should be str or dict") + return super()._deserialize(value, attr, data, **kwargs) class StrOrNumberField(Field): """String or Number field for Marshmallow.""" - def _serialize(self, value, attr, obj, **kwargs): - return value - def _deserialize(self, value, attr, data, **kwargs): - if isinstance(value, (str, float, int)): - return value - else: + if not isinstance(value, (str, float, int)): raise ValidationError("Field should be str or int or float") + return super()._deserialize(value, attr, data, **kwargs) class DictOrDictListField(Field): """Dict or Dict List field for Marshmallow.""" - def _serialize(self, value, attr, obj, **kwargs): - return value - def _deserialize(self, value, attr, data, **kwargs): - # dict - if isinstance(value, dict): - return value - # list of dicts - elif isinstance(value, list) and all(isinstance(item, dict) for item in value): - return value - else: - raise ValidationError("Field should be dict or list of dicts") + if not isinstance(value, dict): + if not isinstance(value, list) or not all( + isinstance(item, dict) for item in value + ): + raise ValidationError("Field should be dict or list of dicts") + return super()._deserialize(value, attr, data, **kwargs) class UriOrDictField(StrOrDictField): """URI or Dict field for Marshmallow.""" - def __init__(self, *args, **kwargs): - """Initialize new UriOrDictField instance.""" - super().__init__(*args, **kwargs) - - # Insert validation into self.validators so that multiple errors can be stored. - self.validators.insert(0, self._uri_validator) - - def _uri_validator(self, value): - # Check if URI when + def _deserialize(self, value, attr, data, **kwargs): if isinstance(value, str): - return Uri()(value) + # Check regex + Uri()(value) + return super()._deserialize(value, attr, data, **kwargs) class IntEpoch(Range): @@ -775,7 +756,7 @@ def __call__(self, value): except ValidationError: raise ValidationError( f"credential subject id {value[0]} must be URI" - ) + ) from None return value diff --git a/aries_cloudagent/protocols/connections/v1_0/models/connection_detail.py b/aries_cloudagent/protocols/connections/v1_0/models/connection_detail.py index cdbac51cd7..96a64abed1 100644 --- a/aries_cloudagent/protocols/connections/v1_0/models/connection_detail.py +++ b/aries_cloudagent/protocols/connections/v1_0/models/connection_detail.py @@ -23,7 +23,7 @@ def _serialize(self, value, attr, obj, **kwargs): """ return value.serialize() - def _deserialize(self, value, attr, data, **kwargs): + def _deserialize(self, value, attr=None, data=None, **kwargs): """ Deserialize a value into a DIDDoc. diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py b/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py index e51da83d60..be3c0b80a8 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/messages/invitation.py @@ -96,13 +96,17 @@ def _serialize(self, value, attr, obj, **kwargs): def _deserialize(self, value, attr, data, **kwargs): if isinstance(value, dict): return Service.deserialize(value) + elif isinstance(value, Service): + return value elif isinstance(value, str): - if bool(DIDValidation.PATTERN.match(value)): - return value - else: + if not DIDValidation.PATTERN.match(value): raise ValidationError( "Service item must be a valid decentralized identifier (DID)" ) + return value + raise ValidationError( + "Service item must be a valid decentralized identifier (DID) or object" + ) class InvitationMessage(AgentMessage): @@ -272,7 +276,7 @@ def validate_fields(self, data, **kwargs): ValidationError: If any of the fields do not validate """ handshake_protocols = data.get("handshake_protocols") - requests_attach = data.get("requests~attach") + requests_attach = data.get("requests_attach") if not handshake_protocols and not requests_attach: raise ValidationError( "Model must include non-empty " diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py b/aries_cloudagent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py index 801b17680e..004c7d5ca5 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/messages/tests/test_invitation.py @@ -139,9 +139,8 @@ def test_invalid_invi_wrong_type_services(self): "services": [123], } - invi_schema = InvitationMessageSchema() - with pytest.raises(test_module.ValidationError): - invi_schema.validate_fields(obj_x) + errs = InvitationMessageSchema().validate(obj_x) + assert errs and "services" in errs def test_assign_msg_type_version_to_model_inst(self): test_msg = InvitationMessage() From 224b93eac7d19982bce82536370474fcf798fbd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Humbert?= Date: Thu, 22 Dec 2022 16:25:19 +0100 Subject: [PATCH 24/33] feat: enable creation of DIDs for all registered methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To leverage the recent addition of the `DIDMethods` registry: * Relax condition on did format for the `POST /wallet/did` endpoint * Validate new DIDs parameters using `DIDMethods` in Askar and InMemory profiles validate new DIDs parameters Signed-off-by: Clément Humbert --- aries_cloudagent/core/tests/test_conductor.py | 3 +- .../ledger/tests/test_indy_vdr.py | 2 +- .../messaging/jsonld/tests/test_routes.py | 3 +- .../connections/v1_0/tests/test_manager.py | 3 +- .../tests/test_mediation_request_handler.py | 2 + .../v1_0/tests/test_mediation_manager.py | 5 +- .../v1_0/tests/test_routes.py | 2 + .../handlers/tests/test_complete_handler.py | 2 + .../handlers/tests/test_invitation_handler.py | 2 + .../handlers/tests/test_request_handler.py | 3 +- .../handlers/tests/test_response_handler.py | 3 +- .../v1_0/messages/tests/test_request.py | 10 ++- .../v1_0/messages/tests/test_response.py | 12 ++- .../didexchange/v1_0/tests/test_manager.py | 3 +- .../v1_0/tests/test_manager.py | 3 +- .../dif/tests/test_pres_exch_handler.py | 4 +- .../transport/tests/test_pack_format.py | 3 +- aries_cloudagent/wallet/askar.py | 31 +++---- aries_cloudagent/wallet/did_method.py | 35 +++++++- .../wallet/did_parameters_validation.py | 65 +++++++++++++++ aries_cloudagent/wallet/in_memory.py | 26 ++---- aries_cloudagent/wallet/routes.py | 26 ++++-- .../tests/test_did_parameters_validation.py | 83 +++++++++++++++++++ .../wallet/tests/test_in_memory_wallet.py | 1 + .../wallet/tests/test_indy_wallet.py | 5 +- aries_cloudagent/wallet/tests/test_routes.py | 46 +++++++++- 26 files changed, 312 insertions(+), 71 deletions(-) create mode 100644 aries_cloudagent/wallet/did_parameters_validation.py create mode 100644 aries_cloudagent/wallet/tests/test_did_parameters_validation.py diff --git a/aries_cloudagent/core/tests/test_conductor.py b/aries_cloudagent/core/tests/test_conductor.py index c105a66307..d37f10161e 100644 --- a/aries_cloudagent/core/tests/test_conductor.py +++ b/aries_cloudagent/core/tests/test_conductor.py @@ -36,7 +36,7 @@ from ...utils.stats import Collector from ...version import __version__ from ...wallet.base import BaseWallet -from ...wallet.did_method import SOV +from ...wallet.did_method import SOV, DIDMethods from ...wallet.key_type import ED25519 from .. import conductor as test_module @@ -87,6 +87,7 @@ async def build_context(self) -> InjectionContext: context.injector.bind_instance(ProfileManager, InMemoryProfileManager()) context.injector.bind_instance(ProtocolRegistry, ProtocolRegistry()) context.injector.bind_instance(BaseWireFormat, self.wire_format) + context.injector.bind_instance(DIDMethods, DIDMethods()) context.injector.bind_instance(DIDResolver, DIDResolver([])) context.injector.bind_instance(EventBus, MockEventBus()) return context diff --git a/aries_cloudagent/ledger/tests/test_indy_vdr.py b/aries_cloudagent/ledger/tests/test_indy_vdr.py index 5f820aeeab..6a575a5737 100644 --- a/aries_cloudagent/ledger/tests/test_indy_vdr.py +++ b/aries_cloudagent/ledger/tests/test_indy_vdr.py @@ -28,7 +28,7 @@ @pytest.fixture() def ledger(): - profile = InMemoryProfile.test_profile() + profile = InMemoryProfile.test_profile(bind={DIDMethods: DIDMethods()}) ledger = IndyVdrLedger(IndyVdrLedgerPool("test-ledger"), profile) async def open(): diff --git a/aries_cloudagent/messaging/jsonld/tests/test_routes.py b/aries_cloudagent/messaging/jsonld/tests/test_routes.py index ebddf2b9f3..cc9d35c724 100644 --- a/aries_cloudagent/messaging/jsonld/tests/test_routes.py +++ b/aries_cloudagent/messaging/jsonld/tests/test_routes.py @@ -14,7 +14,7 @@ from ....resolver.did_resolver import DIDResolver from ....vc.ld_proofs.document_loader import DocumentLoader from ....wallet.base import BaseWallet -from ....wallet.did_method import SOV +from ....wallet.did_method import SOV, DIDMethods from ....wallet.error import WalletError from ....wallet.key_type import ED25519 from ..error import ( @@ -274,6 +274,7 @@ async def setUp(self): self.context.profile.context.injector.bind_instance( DocumentLoader, custom_document_loader ) + self.context.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) self.did_info = await (await self.context.session()).wallet.create_local_did( SOV, ED25519 ) 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 be52b0aefb..c906d12a44 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -23,7 +23,7 @@ from .....storage.error import StorageNotFoundError from .....transport.inbound.receipt import MessageReceipt from .....wallet.base import DIDInfo -from .....wallet.did_method import SOV +from .....wallet.did_method import SOV, DIDMethods from .....wallet.error import WalletNotFoundError from .....wallet.in_memory import InMemoryWallet from .....wallet.key_type import ED25519 @@ -93,6 +93,7 @@ async def setUp(self): BaseCache: InMemoryCache(), OobMessageProcessor: self.oob_mock, RouteManager: self.route_manager, + DIDMethods: DIDMethods(), }, ) self.context = self.profile.context diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_request_handler.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_request_handler.py index 61d2b4d449..d3c342ec07 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_request_handler.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_request_handler.py @@ -13,6 +13,7 @@ from ...models.mediation_record import MediationRecord from ..mediation_request_handler import MediationRequestHandler +from ......wallet.did_method import DIDMethods TEST_CONN_ID = "conn-id" TEST_VERKEY = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx" @@ -24,6 +25,7 @@ class TestMediationRequestHandler(AsyncTestCase): async def setUp(self): """setup dependencies of messaging""" self.context = RequestContext.test_context() + self.context.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) self.session = await self.context.session() self.context.message = MediationRequest() self.context.connection_ready = True 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 97bc2f80f7..7e93eaa841 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 @@ -26,6 +26,7 @@ from ..messages.mediate_grant import MediationGrant from ..messages.mediate_request import MediationRequest from ..models.mediation_record import MediationRecord +from .....wallet.did_method import DIDMethods TEST_CONN_ID = "conn-id" TEST_THREAD_ID = "thread-id" @@ -42,7 +43,9 @@ def profile() -> Iterable[Profile]: """Fixture for profile used in tests.""" # pylint: disable=W0621 - yield InMemoryProfile.test_profile(bind={EventBus: MockEventBus()}) + yield InMemoryProfile.test_profile( + bind={EventBus: MockEventBus(), DIDMethods: DIDMethods()} + ) @pytest.fixture 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 4cbd017a45..ababf0ea16 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 @@ -6,11 +6,13 @@ from .....storage.error import StorageError, StorageNotFoundError from ..models.mediation_record import MediationRecord from ..route_manager import RouteManager +from .....wallet.did_method import DIDMethods class TestCoordinateMediationRoutes(AsyncTestCase): def setUp(self): self.profile = InMemoryProfile.test_profile() + self.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) self.context = AdminRequestContext.test_context(profile=self.profile) self.outbound_message_router = async_mock.CoroutineMock() self.request_dict = { diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_complete_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_complete_handler.py index bbeba7c27f..19372e515c 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_complete_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_complete_handler.py @@ -10,11 +10,13 @@ from ...messages.problem_report_reason import ProblemReportReason from .. import complete_handler as test_module +from ......wallet.did_method import DIDMethods @pytest.fixture() def request_context() -> RequestContext: ctx = RequestContext.test_context() + ctx.injector.bind_instance(DIDMethods, DIDMethods()) ctx.message_receipt = MessageReceipt() yield ctx diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_invitation_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_invitation_handler.py index 95a728310c..279d905863 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_invitation_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_invitation_handler.py @@ -9,11 +9,13 @@ from ...handlers.invitation_handler import InvitationHandler from ...messages.problem_report_reason import ProblemReportReason +from ......wallet.did_method import DIDMethods @pytest.fixture() def request_context() -> RequestContext: ctx = RequestContext.test_context() + ctx.injector.bind_instance(DIDMethods, DIDMethods()) ctx.message_receipt = MessageReceipt() yield ctx diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py index 8f8c4381b1..6c7a5892b8 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_request_handler.py @@ -9,7 +9,7 @@ Service, ) from ......core.in_memory import InMemoryProfile -from ......wallet.did_method import SOV +from ......wallet.did_method import SOV, DIDMethods from ......wallet.key_type import ED25519 from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.request_context import RequestContext @@ -76,6 +76,7 @@ async def setUp(self): "debug.auto_accept_requests_public": True, } ) + self.session.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) self.conn_rec = conn_record.ConnRecord( my_did="55GkHamhTU1ZbTbV2ab9DE", diff --git a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_response_handler.py b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_response_handler.py index ff9750ca14..ba81955e36 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_response_handler.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/handlers/tests/test_response_handler.py @@ -13,7 +13,7 @@ from ......messaging.request_context import RequestContext from ......messaging.responder import MockResponder from ......transport.inbound.receipt import MessageReceipt -from ......wallet.did_method import SOV +from ......wallet.did_method import SOV, DIDMethods from ......wallet.key_type import ED25519 from .....problem_report.v1_0.message import ProblemReport @@ -63,6 +63,7 @@ async def setUp(self): self.ctx = RequestContext.test_context() self.ctx.message_receipt = MessageReceipt() + self.ctx.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) wallet = (await self.ctx.session()).wallet self.did_info = await wallet.create_local_did( method=SOV, diff --git a/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_request.py b/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_request.py index 37569858e6..dc4c8b189e 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_request.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_request.py @@ -5,7 +5,7 @@ from ......connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service from ......core.in_memory import InMemoryProfile from ......messaging.decorators.attach_decorator import AttachDecorator -from ......wallet.did_method import SOV +from ......wallet.did_method import SOV, DIDMethods from ......wallet.key_type import ED25519 from .....didcomm_prefix import DIDCommPrefix from ...message_types import DIDX_REQUEST @@ -49,7 +49,9 @@ def make_did_doc(self): class TestDIDXRequest(AsyncTestCase, TestConfig): async def setUp(self): - self.wallet = InMemoryProfile.test_session().wallet + self.session = InMemoryProfile.test_session() + self.session.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + self.wallet = self.session.wallet self.did_info = await self.wallet.create_local_did( method=SOV, key_type=ED25519, @@ -106,7 +108,9 @@ class TestDIDXRequestSchema(AsyncTestCase, TestConfig): """Test request schema.""" async def setUp(self): - self.wallet = InMemoryProfile.test_session().wallet + self.session = InMemoryProfile.test_session() + self.session.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + self.wallet = self.session.wallet self.did_info = await self.wallet.create_local_did( method=SOV, key_type=ED25519, diff --git a/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_response.py b/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_response.py index 59657ef2dc..3be60ca51e 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_response.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/messages/tests/test_response.py @@ -5,7 +5,7 @@ from ......connections.models.diddoc import DIDDoc, PublicKey, PublicKeyType, Service from ......core.in_memory import InMemoryProfile from ......messaging.decorators.attach_decorator import AttachDecorator -from ......wallet.did_method import SOV +from ......wallet.did_method import SOV, DIDMethods from ......wallet.key_type import ED25519 from .....didcomm_prefix import DIDCommPrefix from ...message_types import DIDX_RESPONSE @@ -48,7 +48,10 @@ def make_did_doc(self): class TestDIDXResponse(AsyncTestCase, TestConfig): async def setUp(self): - self.wallet = InMemoryProfile.test_session().wallet + self.session = InMemoryProfile.test_session() + self.session.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + self.wallet = self.session.wallet + self.did_info = await self.wallet.create_local_did( method=SOV, key_type=ED25519, @@ -102,7 +105,10 @@ class TestDIDXResponseSchema(AsyncTestCase, TestConfig): """Test response schema.""" async def setUp(self): - self.wallet = InMemoryProfile.test_session().wallet + self.session = InMemoryProfile.test_session() + self.session.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) + self.wallet = self.session.wallet + self.did_info = await self.wallet.create_local_did( method=SOV, key_type=ED25519, diff --git a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py index 18f87d6b30..69de49fcfd 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py @@ -24,7 +24,7 @@ from .....storage.error import StorageNotFoundError from .....transport.inbound.receipt import MessageReceipt from .....wallet.did_info import DIDInfo -from .....wallet.did_method import SOV +from .....wallet.did_method import SOV, DIDMethods from .....wallet.error import WalletError from .....wallet.in_memory import InMemoryWallet from .....wallet.key_type import ED25519 @@ -102,6 +102,7 @@ async def setUp(self): BaseCache: InMemoryCache(), OobMessageProcessor: self.oob_mock, RouteManager: self.route_manager, + DIDMethods: DIDMethods(), }, ) self.context = self.profile.context diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py index 1ce20b7ca5..f4b2719237 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/tests/test_manager.py @@ -12,7 +12,7 @@ from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError from .....wallet.base import BaseWallet -from .....wallet.did_method import SOV +from .....wallet.did_method import SOV, DIDMethods from .....wallet.key_type import ED25519 from ..manager import TransactionManager, TransactionManagerError from ..models.transaction_record import TransactionRecord @@ -112,6 +112,7 @@ async def setUp(self): self.profile = self.context.profile injector = self.profile.context.injector injector.bind_instance(BaseLedger, self.ledger) + injector.bind_instance(DIDMethods, DIDMethods()) async with self.profile.session() as session: self.wallet: BaseWallet = session.inject_or(BaseWallet) diff --git a/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch_handler.py index 72525e2fe8..87597359cc 100644 --- a/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch_handler.py @@ -15,7 +15,7 @@ from .....storage.vc_holder.vc_record import VCRecord from .....wallet.base import BaseWallet, DIDInfo from .....wallet.crypto import KeyType -from .....wallet.did_method import SOV, KEY +from .....wallet.did_method import SOV, KEY, DIDMethods from .....wallet.error import WalletNotFoundError from .....vc.ld_proofs import ( BbsBlsSignature2020, @@ -69,7 +69,7 @@ def event_loop(request): @pytest.fixture(scope="class") def profile(): - profile = InMemoryProfile.test_profile() + profile = InMemoryProfile.test_profile(bind={DIDMethods: DIDMethods()}) context = profile.context context.injector.bind_instance(DIDResolver, DIDResolver([])) context.injector.bind_instance(DocumentLoader, custom_document_loader) diff --git a/aries_cloudagent/transport/tests/test_pack_format.py b/aries_cloudagent/transport/tests/test_pack_format.py index 02a7712e1c..764e4a54bf 100644 --- a/aries_cloudagent/transport/tests/test_pack_format.py +++ b/aries_cloudagent/transport/tests/test_pack_format.py @@ -8,7 +8,7 @@ from ...protocols.didcomm_prefix import DIDCommPrefix from ...protocols.routing.v1_0.message_types import FORWARD from ...wallet.base import BaseWallet -from ...wallet.did_method import SOV +from ...wallet.did_method import SOV, DIDMethods from ...wallet.error import WalletError from ...wallet.key_type import ED25519 from .. import pack_format as test_module @@ -33,6 +33,7 @@ class TestPackWireFormat(AsyncTestCase): def setUp(self): self.session = InMemoryProfile.test_session() + self.session.profile.context.injector.bind_instance(DIDMethods, DIDMethods()) self.wallet = self.session.inject(BaseWallet) async def test_errors(self): diff --git a/aries_cloudagent/wallet/askar.py b/aries_cloudagent/wallet/askar.py index 5d84df9f26..2a0093d276 100644 --- a/aries_cloudagent/wallet/askar.py +++ b/aries_cloudagent/wallet/askar.py @@ -15,9 +15,9 @@ SeedMethod, ) +from .did_parameters_validation import DIDParametersValidation from ..askar.didcomm.v1 import pack_message, unpack_message from ..askar.profile import AskarProfileSession -from ..did.did_key import DIDKey from ..ledger.base import BaseLedger from ..ledger.endpoint_type import EndpointType from ..ledger.error import LedgerConfigError @@ -30,7 +30,7 @@ validate_seed, verify_signed_message, ) -from .did_method import SOV, KEY, DIDMethod, DIDMethods +from .did_method import SOV, DIDMethod, DIDMethods from .error import WalletError, WalletDuplicateError, WalletNotFoundError from .key_type import BLS12381G2, ED25519, KeyType, KeyTypes from .util import b58_to_bytes, bytes_to_b58 @@ -171,29 +171,23 @@ async def create_local_did( WalletError: If there is another backend error """ - - # validate key_type - if not method.supports_key_type(key_type): - raise WalletError( - f"Invalid key type {key_type.key_type}" - f" for DID method {method.method_name}" - ) - - if method == KEY and did: - raise WalletError("Not allowed to set DID for DID method 'key'") + did_validation = DIDParametersValidation( + self._session.context.inject(DIDMethods) + ) + did_validation.validate_key_type(method, key_type) if not metadata: metadata = {} - if method not in [SOV, KEY]: - raise WalletError( - f"Unsupported DID method for askar storage: {method.method_name}" - ) try: keypair = _create_keypair(key_type, seed) verkey_bytes = keypair.get_public_bytes() verkey = bytes_to_b58(verkey_bytes) + did = did_validation.validate_or_derive_did( + method, key_type, verkey_bytes, did + ) + try: await self._session.handle.insert_key( verkey, keypair, metadata=json.dumps(metadata) @@ -205,11 +199,6 @@ async def create_local_did( else: raise WalletError("Error inserting key") from err - if method == KEY: - did = DIDKey.from_public_key(verkey_bytes, key_type).did - elif not did: - did = bytes_to_b58(verkey_bytes[:16]) - item = await self._session.handle.fetch(CATEGORY_DID, did, for_update=True) if item: did_info = item.value_json diff --git a/aries_cloudagent/wallet/did_method.py b/aries_cloudagent/wallet/did_method.py index af971ea019..cbe1361b39 100644 --- a/aries_cloudagent/wallet/did_method.py +++ b/aries_cloudagent/wallet/did_method.py @@ -1,19 +1,35 @@ """did method.py contains registry for did methods.""" +from enum import Enum from typing import Dict, List, Mapping, Optional from .error import BaseError from .key_type import BLS12381G2, ED25519, KeyType +class HolderDefinedDid(Enum): + """Define if a holder can specify its own did for a given method.""" + + NO = "no" # holder CANNOT provide a DID + ALLOWED = "allowed" # holder CAN provide a DID + REQUIRED = "required" # holder MUST provide a DID + + class DIDMethod: """Class to represent a did method.""" - def __init__(self, name: str, key_types: List[KeyType], rotation: bool = False): + def __init__( + self, + name: str, + key_types: List[KeyType], + rotation: bool = False, + holder_defined_did: HolderDefinedDid = HolderDefinedDid.NO, + ): """Construct did method class.""" self._method_name: str = name self._supported_key_types: List[KeyType] = key_types self._supports_rotation: bool = rotation + self._holder_defined_did: HolderDefinedDid = holder_defined_did @property def method_name(self): @@ -34,8 +50,21 @@ def supports_key_type(self, key_type: KeyType) -> bool: """Check whether the current method supports the key type.""" return key_type in self.supported_key_types + def holder_defined_did(self) -> HolderDefinedDid: + """Return the did derivation policy. -SOV = DIDMethod(name="sov", key_types=[ED25519], rotation=True) + eg: did:key DIDs are derived from the verkey -> HolderDefinedDid.NO + eg: did:web DIDs cannot be derived from key material -> HolderDefinedDid.REQUIRED + """ + return self._holder_defined_did + + +SOV = DIDMethod( + name="sov", + key_types=[ED25519], + rotation=True, + holder_defined_did=HolderDefinedDid.ALLOWED, +) KEY = DIDMethod( name="key", key_types=[ED25519, BLS12381G2], @@ -55,7 +84,7 @@ def __init__(self) -> None: def registered(self, method: str) -> bool: """Check for a supported method.""" - return method in list(self._registry.items()) + return method in self._registry.keys() def register(self, method: DIDMethod): """Register a new did method.""" diff --git a/aries_cloudagent/wallet/did_parameters_validation.py b/aries_cloudagent/wallet/did_parameters_validation.py new file mode 100644 index 0000000000..04572c77bf --- /dev/null +++ b/aries_cloudagent/wallet/did_parameters_validation.py @@ -0,0 +1,65 @@ +"""Tooling to validate DID creation parameters.""" + +from typing import Optional + +from aries_cloudagent.did.did_key import DIDKey +from aries_cloudagent.wallet.did_method import ( + DIDMethods, + DIDMethod, + HolderDefinedDid, + KEY, + SOV, +) +from aries_cloudagent.wallet.error import WalletError +from aries_cloudagent.wallet.key_type import KeyType +from aries_cloudagent.wallet.util import bytes_to_b58 + + +class DIDParametersValidation: + """A utility class to check compatibility of provided DID creation parameters.""" + + def __init__(self, did_methods: DIDMethods): + """:param did_methods: DID method registry relevant for the validation.""" + self.did_methods = did_methods + + @staticmethod + def validate_key_type(method: DIDMethod, key_type: KeyType): + """Validate compatibility of the DID method with the desired key type.""" + # validate key_type + if not method.supports_key_type(key_type): + raise WalletError( + f"Invalid key type {key_type.key_type}" + f" for DID method {method.method_name}" + ) + + def validate_or_derive_did( + self, + method: DIDMethod, + key_type: KeyType, + verkey: bytes, + did: Optional[str], + ) -> str: + """ + Validate compatibility of the provided did (if any) with the given DID method. + + If no DID was provided, automatically derive one for methods that support it. + """ + if method.holder_defined_did() == HolderDefinedDid.NO and did: + raise WalletError( + f"Not allowed to set DID for DID method '{method.method_name}'" + ) + elif method.holder_defined_did() == HolderDefinedDid.REQUIRED and not did: + raise WalletError(f"Providing a DID is required {method.method_name}") + elif not self.did_methods.registered(method.method_name): + raise WalletError( + f"Unsupported DID method for current storage: {method.method_name}" + ) + + # We need some did method specific handling. If more did methods + # are added it is probably better create a did method specific handler + elif method == KEY: + return DIDKey.from_public_key(verkey, key_type).did + elif method == SOV: + return bytes_to_b58(verkey[:16]) if not did else did + + return did diff --git a/aries_cloudagent/wallet/in_memory.py b/aries_cloudagent/wallet/in_memory.py index f9bb03a37f..5023e8d6c0 100644 --- a/aries_cloudagent/wallet/in_memory.py +++ b/aries_cloudagent/wallet/in_memory.py @@ -3,8 +3,8 @@ import asyncio from typing import List, Sequence, Tuple, Union +from .did_parameters_validation import DIDParametersValidation from ..core.in_memory import InMemoryProfile -from ..did.did_key import DIDKey from .base import BaseWallet from .crypto import ( @@ -17,7 +17,7 @@ ) from .did_info import KeyInfo, DIDInfo from .did_posture import DIDPosture -from .did_method import SOV, KEY, DIDMethod, DIDMethods +from .did_method import SOV, DIDMethod, DIDMethods from .error import WalletError, WalletDuplicateError, WalletNotFoundError from .key_type import KeyType from .util import b58_to_bytes, bytes_to_b58, random_seed @@ -212,27 +212,15 @@ async def create_local_did( """ seed = validate_seed(seed) or random_seed() - # validate key_type - if not method.supports_key_type(key_type): - raise WalletError( - f"Invalid key type {key_type.key_type} for method {method.method_name}" - ) + did_methods: DIDMethods = self.profile.context.inject(DIDMethods) + did_validation = DIDParametersValidation(did_methods) + + did_validation.validate_key_type(method, key_type) verkey, secret = create_keypair(key_type, seed) verkey_enc = bytes_to_b58(verkey) - # We need some did method specific handling. If more did methods - # are added it is probably better create a did method specific handler - if method == KEY: - if did: - raise WalletError("Not allowed to set DID for DID method 'key'") - - did = DIDKey.from_public_key(verkey, key_type).did - elif method == SOV: - if not did: - did = bytes_to_b58(verkey[:16]) - else: - raise WalletError(f"Unsupported DID method: {method.method_name}") + did = did_validation.validate_or_derive_did(method, key_type, verkey, did) if ( did in self.profile.local_dids diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 1c038957dc..1fb4998934 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -25,6 +25,7 @@ INDY_DID, INDY_OR_KEY_DID, INDY_RAW_PUBLIC_KEY, + GENERIC_DID, ) from ..protocols.coordinate_mediation.v1_0.route_manager import RouteManager from ..protocols.endorse_transaction.v1_0.manager import ( @@ -38,7 +39,7 @@ from ..storage.error import StorageError, StorageNotFoundError from .base import BaseWallet from .did_info import DIDInfo -from .did_method import SOV, KEY, DIDMethod, DIDMethods +from .did_method import SOV, KEY, DIDMethod, DIDMethods, HolderDefinedDid from .did_posture import DIDPosture from .error import WalletError, WalletNotFoundError from .key_type import BLS12381G2, ED25519, KeyTypes @@ -119,7 +120,7 @@ class DIDEndpointSchema(OpenAPISchema): class DIDListQueryStringSchema(OpenAPISchema): """Parameters and validators for DID list request query string.""" - did = fields.Str(description="DID of interest", required=False, **INDY_OR_KEY_DID) + did = fields.Str(description="DID of interest", required=False, **GENERIC_DID) verkey = fields.Str( description="Verification key of interest", required=False, @@ -163,6 +164,8 @@ class DIDCreateOptionsSchema(OpenAPISchema): validate=validate.OneOf([ED25519.key_type, BLS12381G2.key_type]), ) + did = fields.Str(required=False, **GENERIC_DID) + class DIDCreateSchema(OpenAPISchema): """Parameters and validators for create DID endpoint.""" @@ -171,7 +174,6 @@ class DIDCreateSchema(OpenAPISchema): required=False, default=SOV.method_name, example=SOV.method_name, - validate=validate.OneOf([KEY.method_name, SOV.method_name]), ) options = fields.Nested( @@ -374,14 +376,26 @@ async def wallet_create_did(request: web.BaseRequest): f" support key type {key_type.key_type}" ) ) + + did = body.get("did") + if method.holder_defined_did() == HolderDefinedDid.NO and did: + raise web.HTTPForbidden( + reason=( + f"method {method.method_name} does not" + f" support user-defined DIDs" + ) + ) + elif method.holder_defined_did() == HolderDefinedDid.REQUIRED and not did: + raise web.HTTPBadRequest( + reason=f"method {method.method_name} requires a user-defined DIDs" + ) + wallet = session.inject_or(BaseWallet) if not wallet: raise web.HTTPForbidden(reason="No wallet available") try: info = await wallet.create_local_did( - method=method, - key_type=key_type, - seed=seed, + method=method, key_type=key_type, seed=seed, did=did ) except WalletError as err: diff --git a/aries_cloudagent/wallet/tests/test_did_parameters_validation.py b/aries_cloudagent/wallet/tests/test_did_parameters_validation.py new file mode 100644 index 0000000000..73565f827f --- /dev/null +++ b/aries_cloudagent/wallet/tests/test_did_parameters_validation.py @@ -0,0 +1,83 @@ +import pytest + +from aries_cloudagent.wallet.did_method import DIDMethods, DIDMethod, HolderDefinedDid +from aries_cloudagent.wallet.did_parameters_validation import DIDParametersValidation +from aries_cloudagent.wallet.error import WalletError +from aries_cloudagent.wallet.key_type import ED25519, BLS12381G1 + + +@pytest.fixture +def did_methods_registry(): + return DIDMethods() + + +def test_validate_key_type_uses_didmethod_when_validating_key_type( + did_methods_registry, +): + # given + ed_method = DIDMethod("ed-method", [ED25519]) + did_methods_registry.register(ed_method) + did_validation = DIDParametersValidation(did_methods_registry) + + # when - then + assert did_validation.validate_key_type(ed_method, ED25519) is None + with pytest.raises(WalletError): + did_validation.validate_key_type(ed_method, BLS12381G1) + + +def test_validate_key_type_raises_exception_when_validating_unknown_did_method( + did_methods_registry, +): + # given + unknown_method = DIDMethod("unknown", []) + did_validation = DIDParametersValidation(did_methods_registry) + + # when - then + with pytest.raises(WalletError): + did_validation.validate_key_type(unknown_method, ED25519) + + +def test_set_did_raises_error_when_did_is_provided_and_method_doesnt_allow( + did_methods_registry, +): + # given + ed_method = DIDMethod( + "derived-did", [ED25519], holder_defined_did=HolderDefinedDid.NO + ) + did_methods_registry.register(ed_method) + did_validation = DIDParametersValidation(did_methods_registry) + + # when - then + with pytest.raises(WalletError): + did_validation.validate_or_derive_did( + ed_method, ED25519, b"verkey", "did:edward:self-defined" + ) + + +def test_validate_or_derive_did_raises_error_when_no_did_is_provided_and_method_requires_one( + did_methods_registry, +): + # given + ed_method = DIDMethod( + "self-defined-did", [ED25519], holder_defined_did=HolderDefinedDid.REQUIRED + ) + did_methods_registry.register(ed_method) + did_validation = DIDParametersValidation(did_methods_registry) + + # when - then + with pytest.raises(WalletError): + did_validation.validate_or_derive_did(ed_method, ED25519, b"verkey", did=None) + + +def test_validate_or_derive_did_raises_exception_when_validating_unknown_did_method( + did_methods_registry, +): + # given + unknown_method = DIDMethod("unknown", []) + did_validation = DIDParametersValidation(did_methods_registry) + + # when - then + with pytest.raises(WalletError): + did_validation.validate_or_derive_did( + unknown_method, ED25519, b"verkey", did=None + ) diff --git a/aries_cloudagent/wallet/tests/test_in_memory_wallet.py b/aries_cloudagent/wallet/tests/test_in_memory_wallet.py index ecb3c97263..e9d81ffa27 100644 --- a/aries_cloudagent/wallet/tests/test_in_memory_wallet.py +++ b/aries_cloudagent/wallet/tests/test_in_memory_wallet.py @@ -13,6 +13,7 @@ @pytest.fixture() async def wallet(): profile = InMemoryProfile.test_profile() + profile.context.injector.bind_instance(DIDMethods, DIDMethods()) wallet = InMemoryWallet(profile) yield wallet diff --git a/aries_cloudagent/wallet/tests/test_indy_wallet.py b/aries_cloudagent/wallet/tests/test_indy_wallet.py index 8dfe821e58..47ae72cdec 100644 --- a/aries_cloudagent/wallet/tests/test_indy_wallet.py +++ b/aries_cloudagent/wallet/tests/test_indy_wallet.py @@ -17,7 +17,7 @@ from ...indy.sdk.wallet_setup import IndyWalletConfig from ...ledger.endpoint_type import EndpointType from ...ledger.indy import IndySdkLedgerPool -from ...wallet.did_method import SOV +from ...wallet.did_method import SOV, DIDMethods from ...wallet.key_type import ED25519 from .. import indy as test_module from ..base import BaseWallet @@ -28,7 +28,7 @@ @pytest.fixture() async def in_memory_wallet(): - profile = InMemoryProfile.test_profile() + profile = InMemoryProfile.test_profile(bind={DIDMethods: DIDMethods()}) wallet = InMemoryWallet(profile) yield wallet @@ -38,6 +38,7 @@ async def wallet(): key = await IndySdkWallet.generate_wallet_key() context = InjectionContext() context.injector.bind_instance(IndySdkLedgerPool, IndySdkLedgerPool("name")) + context.injector.bind_instance(DIDMethods, DIDMethods()) with async_mock.patch.object(IndySdkProfile, "_make_finalizer"): profile = cast( IndySdkProfile, diff --git a/aries_cloudagent/wallet/tests/test_routes.py b/aries_cloudagent/wallet/tests/test_routes.py index 75abc4d483..f04e6431ef 100644 --- a/aries_cloudagent/wallet/tests/test_routes.py +++ b/aries_cloudagent/wallet/tests/test_routes.py @@ -1,4 +1,5 @@ import mock as async_mock +import pytest from aiohttp.web import HTTPForbidden from async_case import IsolatedAsyncioTestCase @@ -6,7 +7,7 @@ from ...core.in_memory import InMemoryProfile from ...ledger.base import BaseLedger from ...protocols.coordinate_mediation.v1_0.route_manager import RouteManager -from ...wallet.did_method import SOV, DIDMethods +from ...wallet.did_method import SOV, DIDMethods, DIDMethod, HolderDefinedDid from ...wallet.key_type import ED25519, KeyTypes from .. import routes as test_module from ..base import BaseWallet @@ -38,7 +39,8 @@ def setUp(self): self.test_verkey = "verkey" self.test_posted_did = "posted-did" self.test_posted_verkey = "posted-verkey" - self.context.injector.bind_instance(DIDMethods, DIDMethods()) + self.did_methods = DIDMethods() + self.context.injector.bind_instance(DIDMethods, self.did_methods) async def test_missing_wallet(self): self.session_inject[BaseWallet] = None @@ -134,6 +136,46 @@ async def test_create_did_unsupported_key_type(self): with self.assertRaises(test_module.web.HTTPForbidden): await test_module.wallet_create_did(self.request) + async def test_create_did_method_requires_user_defined_did(self): + # given + did_custom = DIDMethod( + name="custom", + key_types=[ED25519], + rotation=True, + holder_defined_did=HolderDefinedDid.REQUIRED, + ) + self.did_methods.register(did_custom) + + self.request.json = async_mock.AsyncMock( + return_value={"method": "custom", "options": {"key_type": "ed25519"}} + ) + + # when - then + with self.assertRaises(test_module.web.HTTPBadRequest): + await test_module.wallet_create_did(self.request) + + async def test_create_did_method_doesnt_support_user_defined_did(self): + did_custom = DIDMethod( + name="custom", + key_types=[ED25519], + rotation=True, + holder_defined_did=HolderDefinedDid.NO, + ) + self.did_methods.register(did_custom) + + # when + self.request.json = async_mock.AsyncMock( + return_value={ + "method": "custom", + "did": "did:custom:aCustomUserDefinedDID", + "options": {"key_type": ED25519.key_type}, + } + ) + + # then + with self.assertRaises(test_module.web.HTTPForbidden): + await test_module.wallet_create_did(self.request) + async def test_create_did_x(self): self.wallet.create_local_did.side_effect = test_module.WalletError() with self.assertRaises(test_module.web.HTTPBadRequest): From cab68e136a59ddb2635b38a6e6b8c6bf43220dd0 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 4 Jan 2023 12:14:25 -0800 Subject: [PATCH 25/33] update github actions dependencies (for node16 support) Signed-off-by: Andrew Whitehead --- .github/workflows/blackformat.yml | 6 +++-- .github/workflows/codeql.yml | 9 +++++--- .github/workflows/integrationtests.yml | 2 +- .github/workflows/pythonpublish.yml | 32 +++++++++++++------------- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/.github/workflows/blackformat.yml b/.github/workflows/blackformat.yml index a9ae5dfdee..1885ba40c4 100644 --- a/.github/workflows/blackformat.yml +++ b/.github/workflows/blackformat.yml @@ -10,7 +10,9 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.9" - name: Black Code Formatter Check uses: psf/black@stable diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9d5ee6a7ea..e6f15917a0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -12,15 +12,18 @@ jobs: runs-on: ubuntu-latest if: (github.event_name == 'pull_request' && github.repository == 'hyperledger/aries-cloudagent-python') || (github.event_name != 'pull_request') + permissions: + security-events: write + steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: python - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/integrationtests.yml b/.github/workflows/integrationtests.yml index db62b14a34..2d68ee315f 100644 --- a/.github/workflows/integrationtests.yml +++ b/.github/workflows/integrationtests.yml @@ -13,7 +13,7 @@ jobs: if: (github.event_name == 'pull_request' && github.repository == 'hyperledger/aries-cloudagent-python') || (github.event_name != 'pull_request') steps: - name: checkout-acapy - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: path: acapy #- name: run-von-network diff --git a/.github/workflows/pythonpublish.yml b/.github/workflows/pythonpublish.yml index 21f2f01de1..b42e56685b 100644 --- a/.github/workflows/pythonpublish.yml +++ b/.github/workflows/pythonpublish.yml @@ -8,19 +8,19 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* From 25ca92aff9b0043137fe3d1cf2e0a4787817a945 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Mon, 9 Jan 2023 09:18:38 -0800 Subject: [PATCH 26/33] update setup-buildx-action Signed-off-by: Andrew Whitehead --- .github/workflows/tests-indy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests-indy.yml b/.github/workflows/tests-indy.yml index a893acf5b5..7e69e76b30 100644 --- a/.github/workflows/tests-indy.yml +++ b/.github/workflows/tests-indy.yml @@ -29,7 +29,7 @@ jobs: ${{ runner.os }}-buildx-test- - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Build test image uses: docker/build-push-action@v3 From 97b44f67e7723bdd12247c02358c79b2c9496e13 Mon Sep 17 00:00:00 2001 From: Roman Reinert Date: Mon, 9 Jan 2023 16:25:36 +0000 Subject: [PATCH 27/33] define state 'deleted' in BaseRecord Signed-off-by: Roman Reinert --- aries_cloudagent/messaging/models/base_record.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/messaging/models/base_record.py b/aries_cloudagent/messaging/models/base_record.py index da1f7914f1..ec0bcd0189 100644 --- a/aries_cloudagent/messaging/models/base_record.py +++ b/aries_cloudagent/messaging/models/base_record.py @@ -81,6 +81,7 @@ class Meta: EVENT_NAMESPACE: str = "acapy::record" LOG_STATE_FLAG = None TAG_NAMES = {"state"} + STATE_DELETED = "deleted" def __init__( self, @@ -420,7 +421,7 @@ async def delete_record(self, session: ProfileSession): storage = session.inject(BaseStorage) if self.state: self._previous_state = self.state - self.state = "deleted" + self.state = BaseRecord.STATE_DELETED await self.emit_event(session, self.serialize()) await storage.delete_record(self.storage_record) From c54963181b5b0bb6708f7d09c8c8ac3a3b2e8773 Mon Sep 17 00:00:00 2001 From: Roman Reinert Date: Tue, 10 Jan 2023 10:30:47 +0000 Subject: [PATCH 28/33] add function to get attributes by prefix Signed-off-by: Roman Reinert --- .../messaging/models/base_record.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/aries_cloudagent/messaging/models/base_record.py b/aries_cloudagent/messaging/models/base_record.py index ec0bcd0189..c696cf6771 100644 --- a/aries_cloudagent/messaging/models/base_record.py +++ b/aries_cloudagent/messaging/models/base_record.py @@ -498,6 +498,24 @@ def __eq__(self, other: Any) -> bool: return self.value == other.value and self.tags == other.tags return False + @classmethod + def get_attributes_by_prefix(cls, prefix: str, walk_mro: bool = True): + """ + List all values for attributes with common prefix. + + Args: + prefix: Common prefix to look for + walk_mro: Walk MRO to find attributes inherited from superclasses + """ + + bases = cls.__mro__ if walk_mro else [cls] + return [ + vars(base)[name] + for base in bases + for name in vars(base) + if name.startswith(prefix) + ] + class BaseExchangeRecord(BaseRecord): """Represents a base record with event tracing capability.""" From 4c6d7caef6bc9ecee309dc3a36e158a8f710a33e Mon Sep 17 00:00:00 2001 From: Roman Reinert Date: Tue, 10 Jan 2023 11:44:41 +0000 Subject: [PATCH 29/33] fix attribute validation in conn- cred_ex- and pres_ex records Signed-off-by: Roman Reinert --- .../connections/models/conn_record.py | 18 +++--------------- .../v2_0/models/cred_ex_record.py | 18 +++--------------- .../present_proof/v2_0/models/pres_exchange.py | 18 +++--------------- 3 files changed, 9 insertions(+), 45 deletions(-) diff --git a/aries_cloudagent/connections/models/conn_record.py b/aries_cloudagent/connections/models/conn_record.py index eb0342970a..ca9e21b07f 100644 --- a/aries_cloudagent/connections/models/conn_record.py +++ b/aries_cloudagent/connections/models/conn_record.py @@ -677,11 +677,7 @@ class Meta: required=False, description="Routing state of connection", validate=validate.OneOf( - [ - getattr(ConnRecord, m) - for m in vars(ConnRecord) - if m.startswith("ROUTING_STATE_") - ] + ConnRecord.get_attributes_by_prefix("ROUTING_STATE_", walk_mro=False) ), example=ConnRecord.ROUTING_STATE_ACTIVE, ) @@ -690,11 +686,7 @@ class Meta: description="Connection acceptance: manual or auto", example=ConnRecord.ACCEPT_AUTO, validate=validate.OneOf( - [ - getattr(ConnRecord, a) - for a in vars(ConnRecord) - if a.startswith("ACCEPT_") - ] + ConnRecord.get_attributes_by_prefix("ACCEPT_", walk_mro=False) ), ) error_msg = fields.Str( @@ -707,11 +699,7 @@ class Meta: description="Invitation mode", example=ConnRecord.INVITATION_MODE_ONCE, validate=validate.OneOf( - [ - getattr(ConnRecord, i) - for i in vars(ConnRecord) - if i.startswith("INVITATION_MODE_") - ] + ConnRecord.get_attributes_by_prefix("INVITATION_MODE_", walk_mro=False) ), ) alias = fields.Str( diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/models/cred_ex_record.py b/aries_cloudagent/protocols/issue_credential/v2_0/models/cred_ex_record.py index fbbc0adaf5..900310f524 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/models/cred_ex_record.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/models/cred_ex_record.py @@ -296,11 +296,7 @@ class Meta: description="Issue-credential exchange initiator: self or external", example=V20CredExRecord.INITIATOR_SELF, validate=validate.OneOf( - [ - getattr(V20CredExRecord, m) - for m in vars(V20CredExRecord) - if m.startswith("INITIATOR_") - ] + V20CredExRecord.get_attributes_by_prefix("INITIATOR_", walk_mro=False) ), ) role = fields.Str( @@ -308,11 +304,7 @@ class Meta: description="Issue-credential exchange role: holder or issuer", example=V20CredExRecord.ROLE_ISSUER, validate=validate.OneOf( - [ - getattr(V20CredExRecord, m) - for m in vars(V20CredExRecord) - if m.startswith("ROLE_") - ] + V20CredExRecord.get_attributes_by_prefix("ROLE_", walk_mro=False) ), ) state = fields.Str( @@ -320,11 +312,7 @@ class Meta: description="Issue-credential exchange state", example=V20CredExRecord.STATE_DONE, validate=validate.OneOf( - [ - getattr(V20CredExRecord, m) - for m in vars(V20CredExRecord) - if m.startswith("STATE_") - ] + V20CredExRecord.get_attributes_by_prefix("STATE_", walk_mro=True) ), ) cred_preview = fields.Nested( diff --git a/aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py b/aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py index cc314aa9db..4b5e22a10b 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py @@ -244,11 +244,7 @@ class Meta: description="Present-proof exchange initiator: self or external", example=V20PresExRecord.INITIATOR_SELF, validate=validate.OneOf( - [ - getattr(V20PresExRecord, m) - for m in vars(V20PresExRecord) - if m.startswith("INITIATOR_") - ] + V20PresExRecord.get_attributes_by_prefix("INITIATOR_", walk_mro=False) ), ) role = fields.Str( @@ -256,22 +252,14 @@ class Meta: description="Present-proof exchange role: prover or verifier", example=V20PresExRecord.ROLE_PROVER, validate=validate.OneOf( - [ - getattr(V20PresExRecord, m) - for m in vars(V20PresExRecord) - if m.startswith("ROLE_") - ] + V20PresExRecord.get_attributes_by_prefix("ROLE_", walk_mro=False) ), ) state = fields.Str( required=False, description="Present-proof exchange state", validate=validate.OneOf( - [ - getattr(V20PresExRecord, m) - for m in vars(V20PresExRecord) - if m.startswith("STATE_") - ] + V20PresExRecord.get_attributes_by_prefix("STATE_", walk_mro=True) ), ) pres_proposal = fields.Nested( From a6c36d02ee1f68b7fabdc11c3e5ba561b6138c85 Mon Sep 17 00:00:00 2001 From: Roman Reinert Date: Tue, 10 Jan 2023 11:45:07 +0000 Subject: [PATCH 30/33] add validation to oob record Signed-off-by: Roman Reinert --- .../protocols/out_of_band/v1_0/models/oob_record.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/models/oob_record.py b/aries_cloudagent/protocols/out_of_band/v1_0/models/oob_record.py index 69f668335b..e550d9eb54 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/models/oob_record.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/models/oob_record.py @@ -3,7 +3,7 @@ import json from typing import Any, Mapping, Optional, Union -from marshmallow import fields +from marshmallow import fields, validate from .....connections.models.conn_record import ConnRecord from .....core.profile import ProfileSession @@ -248,6 +248,9 @@ class Meta: required=True, description="Out of band message exchange state", example=OobRecord.STATE_AWAIT_RESPONSE, + validate=validate.OneOf( + OobRecord.get_attributes_by_prefix("STATE_", walk_mro=True) + ), ) invi_msg_id = fields.Str( required=True, @@ -287,4 +290,7 @@ class Meta: description="OOB Role", required=False, example=OobRecord.ROLE_RECEIVER, + validate=validate.OneOf( + OobRecord.get_attributes_by_prefix("ROLE_", walk_mro=False) + ), ) From aae6d83d14c6eb0449b64c1b218947e738fd2f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Humbert?= Date: Fri, 13 Jan 2023 07:55:57 +0100 Subject: [PATCH 31/33] fix: create-did, get did from options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Get holder-defined DID from options instead of requests' root. Signed-off-by: Clément Humbert --- aries_cloudagent/wallet/routes.py | 2 +- aries_cloudagent/wallet/tests/test_routes.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 1fb4998934..912d8e9930 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -377,7 +377,7 @@ async def wallet_create_did(request: web.BaseRequest): ) ) - did = body.get("did") + did = body.get("options", {}).get("did") if method.holder_defined_did() == HolderDefinedDid.NO and did: raise web.HTTPForbidden( reason=( diff --git a/aries_cloudagent/wallet/tests/test_routes.py b/aries_cloudagent/wallet/tests/test_routes.py index f04e6431ef..03c1dc57cb 100644 --- a/aries_cloudagent/wallet/tests/test_routes.py +++ b/aries_cloudagent/wallet/tests/test_routes.py @@ -167,8 +167,10 @@ async def test_create_did_method_doesnt_support_user_defined_did(self): self.request.json = async_mock.AsyncMock( return_value={ "method": "custom", - "did": "did:custom:aCustomUserDefinedDID", - "options": {"key_type": ED25519.key_type}, + "options": { + "key_type": ED25519.key_type, + "did": "did:custom:aCustomUserDefinedDID", + }, } ) From c29d72526a16af9576c728e884908b181a050d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Humbert?= Date: Mon, 16 Jan 2023 09:44:19 +0100 Subject: [PATCH 32/33] chore: add documentation for DIDMethods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Augment existing swagger documentation for the `POST /wallet/did/create` route * Add `DIDMethods.md` documentation to introduce the concept of the `DIDMethods` registry and its use to register new methods. Signed-off-by: Clément Humbert --- DIDMethods.md | 45 +++++++++++++++++++++++++++++++ aries_cloudagent/wallet/routes.py | 13 +++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 DIDMethods.md diff --git a/DIDMethods.md b/DIDMethods.md new file mode 100644 index 0000000000..179103aba8 --- /dev/null +++ b/DIDMethods.md @@ -0,0 +1,45 @@ +# DID methods in ACA-Py +Decentralized Identifiers, or DIDs, are URIs that point to documents that describe cryptographic primitives and protocols used in decentralized identity management. +DIDs include methods that describe where and how documents can be retrieved. +DID methods support specific types of keys and may or may not require the holder to specify the DID itself. + +ACA-Py provides a `DIDMethods` registry holding all the DID methods supported for storage in a wallet + +> :warning: Askar and InMemory are the only wallets supporting this registry. + +## Registering a DID method +By default, ACA-Py supports `did:key` and `did:sov`. +Plugins can register DID additional methods to make them available to holders. +Here's a snippet adding support for `did:web` to the registry from a plugin `setup` method. + +```python= +WEB = DIDMethod( + name="web", + key_types=[ED25519, BLS12381G2], + rotation=True, + holder_defined_did=HolderDefinedDid.REQUIRED # did:web is not derived from key material but from a user-provided respository name +) + +async def setup(context: InjectionContext): + methods = context.inject(DIDMethods) + methods.register(WEB) +``` + +## Creating a DID + +`POST /wallet/did/create` can be provided with parameters for any registered DID method. Here's a follow-up to the +`did:web` method example: + +```json= +{ + "method": "web", + "options": { + "did": "did:web:doma.in", + "key_type": "ed25519" + } +} +``` + +## Resolving DIDs + +For specifics on how DIDs are resolved in ACA-Py, see: [DID Resolution](DIDResolution.md). diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 912d8e9930..3cd204f66b 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -161,10 +161,17 @@ class DIDCreateOptionsSchema(OpenAPISchema): key_type = fields.Str( required=True, example=ED25519.key_type, + description="Key type to use for the DID keypair. " + + "Validated with the chosen DID method's supported key types.", validate=validate.OneOf([ED25519.key_type, BLS12381G2.key_type]), ) - did = fields.Str(required=False, **GENERIC_DID) + did = fields.Str( + required=False, + description="Specify final value of the did (including did:: prefix)" + + "if the method supports or requires so.", + **GENERIC_DID, + ) class DIDCreateSchema(OpenAPISchema): @@ -174,12 +181,14 @@ class DIDCreateSchema(OpenAPISchema): required=False, default=SOV.method_name, example=SOV.method_name, + description="Method for the requested DID." + + "Supported methods are 'key', 'sov', and any other registered method.", ) options = fields.Nested( DIDCreateOptionsSchema, required=False, - description="To define a key type for a did:key", + description="To define a key type and/or a did depending on chosen DID method.", ) seed = fields.Str( From 1bdf7268a72ddbf522bdb47e4b8a9ee65a4e75e7 Mon Sep 17 00:00:00 2001 From: John Kent Date: Fri, 13 Jan 2023 17:38:12 -0500 Subject: [PATCH 33/33] feat: issue #2068 Allow HEAD requests to default route Signed-off-by: John Kent --- .pre-commit-config.yaml | 2 +- aries_cloudagent/admin/server.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b4a0f811b8..6c0600e70f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: hooks: - id: black stages: [commit] - - repo: https://gitlab.com/pycqa/flake8 + - repo: https://github.com/pycqa/flake8.git rev: 3.9.0 hooks: - id: flake8 diff --git a/aries_cloudagent/admin/server.py b/aries_cloudagent/admin/server.py index c7ab79334d..846405a024 100644 --- a/aries_cloudagent/admin/server.py +++ b/aries_cloudagent/admin/server.py @@ -433,7 +433,7 @@ async def setup_context(request: web.Request, handler): ) server_routes = [ - web.get("/", self.redirect_handler, allow_head=False), + web.get("/", self.redirect_handler, allow_head=True), web.get("/plugins", self.plugins_handler, allow_head=False), web.get("/status", self.status_handler, allow_head=False), web.get("/status/config", self.config_handler, allow_head=False),