diff --git a/aries_cloudagent/ledger/base.py b/aries_cloudagent/ledger/base.py index 8d1ea4e5d0..06bd362d95 100644 --- a/aries_cloudagent/ledger/base.py +++ b/aries_cloudagent/ledger/base.py @@ -98,17 +98,15 @@ async def _construct_attr_json( if not routing_keys: routing_keys = [] - endpoint_dict = {"endpoint": endpoint} - if all_exist_endpoints: - all_exist_endpoints[endpoint_type.indy] = endpoint_dict - endpoint_dict["routingKeys"] = routing_keys + all_exist_endpoints[endpoint_type.indy] = endpoint + all_exist_endpoints["routingKeys"] = routing_keys attr_json = json.dumps({"endpoint": all_exist_endpoints}) else: - endpoint_val = {endpoint_type.indy: endpoint_dict} + endpoint_dict = {endpoint_type.indy: endpoint} endpoint_dict["routingKeys"] = routing_keys - attr_json = json.dumps({"endpoint": endpoint_val}) + attr_json = json.dumps({"endpoint": endpoint_dict}) return attr_json diff --git a/aries_cloudagent/ledger/tests/test_indy.py b/aries_cloudagent/ledger/tests/test_indy.py index 767eaa50a3..bda1890c2a 100644 --- a/aries_cloudagent/ledger/tests/test_indy.py +++ b/aries_cloudagent/ledger/tests/test_indy.py @@ -2308,17 +2308,37 @@ async def test_construct_attr_json_with_routing_keys(self, mock_close, mock_open attr_json = await ledger._construct_attr_json( "https://url", EndpointType.ENDPOINT, - all_exist_endpoints={"Endpoint": "https://endpoint"}, routing_keys=["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], ) assert attr_json == json.dumps( { "endpoint": { - "Endpoint": "https://endpoint", - "endpoint": { - "endpoint": "https://url", - "routingKeys": ["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], - }, + "endpoint": "https://url", + "routingKeys": ["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], + } + } + ) + + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedgerPool.context_open") + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedgerPool.context_close") + @pytest.mark.asyncio + async def test_construct_attr_json_with_routing_keys_all_exist_endpoints( + self, mock_close, mock_open + ): + ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) + async with ledger: + attr_json = await ledger._construct_attr_json( + "https://url", + EndpointType.ENDPOINT, + all_exist_endpoints={"profile": "https://endpoint/profile"}, + routing_keys=["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], + ) + assert attr_json == json.dumps( + { + "endpoint": { + "profile": "https://endpoint/profile", + "endpoint": "https://url", + "routingKeys": ["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], } } ) diff --git a/aries_cloudagent/ledger/tests/test_indy_vdr.py b/aries_cloudagent/ledger/tests/test_indy_vdr.py index 13db2c5563..f20781e838 100644 --- a/aries_cloudagent/ledger/tests/test_indy_vdr.py +++ b/aries_cloudagent/ledger/tests/test_indy_vdr.py @@ -612,27 +612,24 @@ async def test_update_endpoint_for_did( "all_exist_endpoints, routing_keys, result", [ ( - {"Endpoint": "https://endpoint"}, + {"profile": "https://endpoint/profile"}, ["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], { "endpoint": { - "Endpoint": "https://endpoint", - "endpoint": { - "endpoint": "https://url", - "routingKeys": [ - "3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn" - ], - }, + "profile": "https://endpoint/profile", + "endpoint": "https://url", + "routingKeys": ["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], } }, ), ( - {"Endpoint": "https://endpoint"}, + {"profile": "https://endpoint/profile"}, None, { "endpoint": { - "Endpoint": "https://endpoint", - "endpoint": {"endpoint": "https://url", "routingKeys": []}, + "profile": "https://endpoint/profile", + "endpoint": "https://url", + "routingKeys": [], } }, ), @@ -641,21 +638,24 @@ async def test_update_endpoint_for_did( ["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], { "endpoint": { - "endpoint": { - "endpoint": "https://url", - "routingKeys": [ - "3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn" - ], - } + "endpoint": "https://url", + "routingKeys": ["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], } }, ), + (None, None, {"endpoint": {"endpoint": "https://url", "routingKeys": []}}), ( - None, - None, + { + "profile": "https://endpoint/profile", + "spec_divergent_endpoint": "https://endpoint", + }, + ["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], { "endpoint": { - "endpoint": {"endpoint": "https://url", "routingKeys": []} + "profile": "https://endpoint/profile", + "spec_divergent_endpoint": "https://endpoint", + "endpoint": "https://url", + "routingKeys": ["3YJCx3TqotDWFGv7JMR5erEvrmgu5y4FDqjR7sKWxgXn"], } }, ), diff --git a/aries_cloudagent/resolver/default/indy.py b/aries_cloudagent/resolver/default/indy.py index 3d65d03a08..e3e66688e6 100644 --- a/aries_cloudagent/resolver/default/indy.py +++ b/aries_cloudagent/resolver/default/indy.py @@ -3,7 +3,8 @@ Resolution is performed using the IndyLedger class. """ -from typing import Any, Mapping, Pattern +import logging +from typing import Optional, Pattern from pydid import DID, DIDDocumentBuilder from pydid.verification_method import Ed25519VerificationKey2018, VerificationMethod @@ -21,6 +22,8 @@ from ..base import BaseDIDResolver, DIDNotFound, ResolverError, ResolverType +LOGGER = logging.getLogger(__name__) + class NoIndyLedger(ResolverError): """Raised when there is no Indy ledger instance configured.""" @@ -46,60 +49,85 @@ def supported_did_regex(self) -> Pattern: """Return supported_did_regex of Indy DID Resolver.""" return IndyDID.PATTERN - def _add_endpoint_as_endpoint_value_pair( - self, - builder: DIDDocumentBuilder, - endpoint: str, - recipient_key: VerificationMethod, - ): - builder.service.add_didcomm( - ident=self.SERVICE_TYPE_DID_COMMUNICATION, - type_=self.SERVICE_TYPE_DID_COMMUNICATION, - service_endpoint=endpoint, - priority=1, - recipient_keys=[recipient_key], - routing_keys=[], - ) - - def _add_endpoint_as_map( + def process_endpoint_types(self, types): + """Process endpoint types. + + Returns expected types, subset of expected types, + or default types. + """ + expected_types = ["endpoint", "did-communication", "DIDComm"] + default_types = ["endpoint", "did-communication"] + if len(types) <= 0: + return default_types + for type in types: + if type not in expected_types: + return default_types + return types + + def add_services( self, builder: DIDDocumentBuilder, - endpoint: Mapping[str, Any], - recipient_key: VerificationMethod, + endpoints: Optional[dict], + recipient_key: VerificationMethod = None, ): - types = endpoint.get("types", [self.SERVICE_TYPE_DID_COMMUNICATION]) - routing_keys = endpoint.get("routingKeys", []) - endpoint_url = endpoint.get("endpoint") - if not endpoint_url: - raise ValueError("endpoint url not found in endpoint attrib") - - if self.SERVICE_TYPE_DIDCOMM in types: - builder.service.add( - ident="#didcomm-1", - type_=self.SERVICE_TYPE_DIDCOMM, - service_endpoint=endpoint_url, - recipient_keys=[recipient_key.id], - routing_keys=routing_keys, - accept=["didcomm/v2"], - ) - builder.context.append(self.CONTEXT_DIDCOMM_V2) - if self.SERVICE_TYPE_DID_COMMUNICATION in types: - builder.service.add( - ident="did-communication", - type_=self.SERVICE_TYPE_DID_COMMUNICATION, - service_endpoint=endpoint_url, - priority=1, - routing_keys=routing_keys, - recipient_keys=[recipient_key.id], - accept=["didcomm/aip2;env=rfc19"], - ) - if self.SERVICE_TYPE_ENDPOINT in types: - builder.service.add( - ident="endpoint", - service_endpoint=endpoint_url, - type_=self.SERVICE_TYPE_ENDPOINT, + """Add services.""" + if not endpoints: + return + + endpoint = endpoints.get("endpoint") + routing_keys = endpoints.get("routingKeys", []) + types = endpoints.get("types", [self.SERVICE_TYPE_DID_COMMUNICATION]) + + other_endpoints = { + key: endpoints[key] + for key in ("profile", "linked_domains") + if key in endpoints + } + + if endpoint: + processed_types = self.process_endpoint_types(types) + + if self.SERVICE_TYPE_ENDPOINT in processed_types: + builder.service.add( + ident="endpoint", + service_endpoint=endpoint, + type_=self.SERVICE_TYPE_ENDPOINT, + ) + + if self.SERVICE_TYPE_DID_COMMUNICATION in processed_types: + builder.service.add( + ident="did-communication", + type_=self.SERVICE_TYPE_DID_COMMUNICATION, + service_endpoint=endpoint, + priority=1, + routing_keys=routing_keys, + recipient_keys=[recipient_key.id], + accept=["didcomm/aip2;env=rfc19"], + ) + + if self.SERVICE_TYPE_DIDCOMM in types: + builder.service.add( + ident="#didcomm-1", + type_=self.SERVICE_TYPE_DIDCOMM, + service_endpoint=endpoint, + recipient_keys=[recipient_key.id], + routing_keys=routing_keys, + accept=["didcomm/v2"], + ) + builder.context.append(self.CONTEXT_DIDCOMM_V2) + else: + LOGGER.warning( + "No endpoint for DID although endpoint attrib was resolvable" ) + if other_endpoints: + for type_, endpoint in other_endpoints.items(): + builder.service.add( + ident=type_, + type_=EndpointType.get(type_).w3c, + service_endpoint=endpoint, + ) + async def _resolve(self, profile: Profile, did: str) -> dict: """Resolve an indy DID.""" multitenant_mgr = profile.inject_or(BaseMultitenantManager) @@ -119,7 +147,7 @@ async def _resolve(self, profile: Profile, did: str) -> dict: try: async with ledger: recipient_key = await ledger.get_key_for_did(did) - endpoints = await ledger.get_all_endpoints_for_did(did) + endpoints: Optional[dict] = await ledger.get_all_endpoints_for_did(did) except LedgerError as err: raise DIDNotFound(f"DID {did} could not be resolved") from err @@ -130,22 +158,7 @@ async def _resolve(self, profile: Profile, did: str) -> dict: ) builder.authentication.reference(vmethod.id) builder.assertion_method.reference(vmethod.id) - if endpoints: - for type_, endpoint in endpoints.items(): - if type_ == EndpointType.ENDPOINT.indy: - if isinstance(endpoint, dict): - self._add_endpoint_as_map(builder, endpoint, vmethod) - else: - self._add_endpoint_as_endpoint_value_pair( - builder, endpoint, vmethod - ) - else: - # Accept all service types for now, i.e. profile, linked_domains - builder.service.add( - ident=type_, - type_=type_, - service_endpoint=endpoint, - ) + self.add_services(builder, endpoints, vmethod) result = builder.build() return result.serialize() diff --git a/aries_cloudagent/resolver/default/tests/test_indy.py b/aries_cloudagent/resolver/default/tests/test_indy.py index b53c06d7c7..d1a17dd357 100644 --- a/aries_cloudagent/resolver/default/tests/test_indy.py +++ b/aries_cloudagent/resolver/default/tests/test_indy.py @@ -3,6 +3,7 @@ import pytest from asynctest import mock as async_mock +from pydid.verification_method import VerificationMethod from ....core.in_memory import InMemoryProfile from ....core.profile import Profile @@ -116,11 +117,11 @@ async def test_supports_updated_did_sov_rules( ): """Test that new attrib structure is supported.""" example = { - "endpoint": { - "endpoint": "https://example.com/endpoint", - "routingKeys": ["a-routing-key"], - "types": ["DIDComm", "did-communication", "endpoint"], - } + "endpoint": "https://example.com/endpoint", + "routingKeys": ["a-routing-key"], + "types": ["DIDComm", "did-communication", "endpoint"], + "profile": "https://example.com", + "linked_domains": "https://example.com", } ledger.get_all_endpoints_for_did = async_mock.CoroutineMock( @@ -129,19 +130,41 @@ async def test_supports_updated_did_sov_rules( assert await resolver.resolve(profile, TEST_DID0) @pytest.mark.asyncio - async def test_supports_updated_did_sov_rules_x_no_endpoint_url( + async def test_supports_updated_did_sov_rules_no_endpoint_url( self, resolver: IndyDIDResolver, ledger: BaseLedger, profile: Profile ): """Test that new attrib structure is supported.""" example = { - "endpoint": { - "routingKeys": ["a-routing-key"], - "types": ["DIDComm", "did-communication", "endpoint"], - } + "routingKeys": ["a-routing-key"], + "types": ["DIDComm", "did-communication", "endpoint"], } ledger.get_all_endpoints_for_did = async_mock.CoroutineMock( return_value=example ) - with pytest.raises(ValueError): - await resolver.resolve(profile, TEST_DID0) + result = await resolver.resolve(profile, TEST_DID0) + assert "service" not in result + + @pytest.mark.parametrize( + "types, result", + [ + ( + [], + ["endpoint", "did-communication"], + ), + ( + ["did-communication"], + ["did-communication"], + ), + ( + ["endpoint", "did-communication", "DIDComm", "other-endpoint-type"], + ["endpoint", "did-communication"], + ), + ( + ["endpoint", "did-communication", "DIDComm"], + ["endpoint", "did-communication", "DIDComm"], + ), + ], + ) + def test_process_endpoint_types(self, resolver: IndyDIDResolver, types, result): + assert resolver.process_endpoint_types(types) == result