Skip to content

Commit bc1110b

Browse files
authored
Merge branch 'main' into feature/enable-aggr-vcs
2 parents 925cdc6 + 6f5eecf commit bc1110b

15 files changed

Lines changed: 329 additions & 88 deletions

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,30 @@ more work is still to be done on that before the final v1.0.0 release.
5050
- Release management pull requests
5151
- Release 1.0.0-rc0 [\#1904](https://github.com/hyperledger/aries-cloudagent-python/pull/1904) ([swcurran](https://github.com/swcurran))
5252

53+
# 0.7.5
54+
55+
## October 26, 2022
56+
57+
0.7.5 is a patch release to deal primarily to add [PR #1881 DID Exchange in
58+
ACA-Py 0.7.4 with explicit invitations and without auto-accept
59+
broken](https://github.com/hyperledger/aries-cloudagent-python/pull/1881). A
60+
couple of other PRs were added to the release, as listed below, and in
61+
[Milestone 0.7.5](https://github.com/hyperledger/aries-cloudagent-python/milestone/6).
62+
63+
### List of Pull Requests
64+
65+
- Changelog and version updates for version 0.7.5-rc1 [\#1985](https://github.com/hyperledger/aries-cloudagent-python/pull/1985) ([swcurran](https://github.com/swcurran))
66+
- Endorser doc updates and some bug fixes [\#1926](https://github.com/hyperledger/aries-cloudagent-python/pull/1926) ([ianco](https://github.com/ianco))
67+
- Fix: web.py dependency - integration tests & demos [\#1973](https://github.com/hyperledger/aries-cloudagent-python/pull/1973) ([shaangill025](https://github.com/shaangill025))
68+
- Endorser write DID transaction [\#1938](https://github.com/hyperledger/aries-cloudagent-python/pull/1938) ([ianco](https://github.com/ianco))
69+
- fix: didx request cannot be accepted [\#1881](https://github.com/hyperledger/aries-cloudagent-python/pull/1881) ([rmnre](https://github.com/rmnre))
70+
- Fix: OOB - Handling of minor versions [\#1940](https://github.com/hyperledger/aries-cloudagent-python/pull/1940) ([shaangill025](https://github.com/shaangill025))
71+
- fix: Safely shutdown when root_profile uninitialized [\#1960](https://github.com/hyperledger/aries-cloudagent-python/pull/1960) ([frostyfrog](https://github.com/frostyfrog))
72+
- feat: 00B v1.1 support [\#1962](https://github.com/hyperledger/aries-cloudagent-python/pull/1962) ([shaangill025](https://github.com/shaangill025))
73+
- 0.7.5 Cherry Picks [\#1967](https://github.com/hyperledger/aries-cloudagent-python/pull/1967) ([frostyfrog](https://github.com/frostyfrog))
74+
- Changelog and version updates for version 0.7.5-rc0 [\#1969](https://github.com/hyperledger/aries-cloudagent-python/pull/1969) ([swcurran](https://github.com/swcurran))
75+
- Final 0.7.5 changes [\#1991](https://github.com/hyperledger/aries-cloudagent-python/pull/1991) ([swcurran](https://github.com/swcurran))
76+
5377
# 0.7.4
5478

5579
## June 30, 2022

aries_cloudagent/multitenant/base.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,19 @@ async def _get_wallet_by_key(self, recipient_key: str) -> Optional[WalletRecord]
381381
except (RouteNotFoundError):
382382
pass
383383

384+
async def get_profile_for_key(
385+
self, context: InjectionContext, recipient_key: str
386+
) -> Optional[Profile]:
387+
"""Retrieve a wallet profile by recipient key."""
388+
wallet = await self._get_wallet_by_key(recipient_key)
389+
if not wallet:
390+
return None
391+
392+
if wallet.requires_external_key:
393+
raise WalletKeyMissingError()
394+
395+
return await self.get_wallet_profile(context, wallet)
396+
384397
async def get_wallets_by_message(
385398
self, message_body, wire_format: BaseWireFormat = None
386399
) -> List[WalletRecord]:

aries_cloudagent/multitenant/route_manager.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,22 @@
44
import logging
55
from typing import List, Optional, Tuple
66

7+
from ..connections.models.conn_record import ConnRecord
78
from ..core.profile import Profile
89
from ..messaging.responder import BaseResponder
910
from ..protocols.coordinate_mediation.v1_0.manager import MediationManager
1011
from ..protocols.coordinate_mediation.v1_0.models.mediation_record import (
1112
MediationRecord,
1213
)
13-
from ..protocols.coordinate_mediation.v1_0.route_manager import RouteManager
14+
from ..protocols.coordinate_mediation.v1_0.normalization import normalize_from_did_key
15+
from ..protocols.coordinate_mediation.v1_0.route_manager import (
16+
CoordinateMediationV1RouteManager,
17+
RouteManager,
18+
)
1419
from ..protocols.routing.v1_0.manager import RoutingManager
1520
from ..protocols.routing.v1_0.models.route_record import RouteRecord
1621
from ..storage.error import StorageNotFoundError
22+
from .base import BaseMultitenantManager
1723

1824

1925
LOGGER = logging.getLogger(__name__)
@@ -103,3 +109,31 @@ async def routing_info(
103109
my_endpoint = mediation_record.endpoint
104110

105111
return routing_keys, my_endpoint
112+
113+
114+
class BaseWalletRouteManager(CoordinateMediationV1RouteManager):
115+
"""Route manager for operations specific to the base wallet."""
116+
117+
async def connection_from_recipient_key(
118+
self, profile: Profile, recipient_key: str
119+
) -> ConnRecord:
120+
"""Retrieve a connection by recipient key.
121+
122+
The recipient key is expected to be a local key owned by this agent.
123+
124+
Since the multi-tenant base wallet can receive and send keylist updates
125+
for sub wallets, we check the sub wallet's connections before the base
126+
wallet.
127+
"""
128+
LOGGER.debug("Retrieving connection for recipient key for multitenant wallet")
129+
manager = profile.inject(BaseMultitenantManager)
130+
profile_to_search = (
131+
await manager.get_profile_for_key(
132+
profile.context, normalize_from_did_key(recipient_key)
133+
)
134+
or profile
135+
)
136+
137+
return await super().connection_from_recipient_key(
138+
profile_to_search, recipient_key
139+
)

aries_cloudagent/multitenant/tests/test_base.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,3 +620,18 @@ async def test_get_wallets_by_message(self):
620620
assert wallets[0] == return_wallets[0]
621621
assert wallets[1] == return_wallets[3]
622622
assert get_wallet_by_key.call_count == 4
623+
624+
async def test_get_profile_for_key(self):
625+
mock_wallet = async_mock.MagicMock()
626+
mock_wallet.requires_external_key = False
627+
with async_mock.patch.object(
628+
self.manager,
629+
"_get_wallet_by_key",
630+
async_mock.CoroutineMock(return_value=mock_wallet),
631+
), async_mock.patch.object(
632+
self.manager, "get_wallet_profile", async_mock.CoroutineMock()
633+
) as mock_get_wallet_profile:
634+
profile = await self.manager.get_profile_for_key(
635+
self.context, "test-verkey"
636+
)
637+
assert profile == mock_get_wallet_profile.return_value

aries_cloudagent/multitenant/tests/test_route_manager.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
from ...protocols.coordinate_mediation.v1_0.models.mediation_record import (
99
MediationRecord,
1010
)
11+
from ...protocols.coordinate_mediation.v1_0.route_manager import RouteManager
1112
from ...protocols.routing.v1_0.manager import RoutingManager
1213
from ...protocols.routing.v1_0.models.route_record import RouteRecord
1314
from ...storage.error import StorageNotFoundError
14-
from ..route_manager import MultitenantRouteManager
15+
from ..base import BaseMultitenantManager
16+
from ..route_manager import BaseWalletRouteManager, MultitenantRouteManager
1517

1618
TEST_RECORD_VERKEY = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx"
1719
TEST_VERKEY = "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL"
@@ -55,6 +57,11 @@ def route_manager(root_profile: Profile, sub_profile: Profile, wallet_id: str):
5557
yield MultitenantRouteManager(root_profile)
5658

5759

60+
@pytest.fixture
61+
def base_route_manager():
62+
yield BaseWalletRouteManager()
63+
64+
5865
@pytest.mark.asyncio
5966
async def test_route_for_key_sub_mediator_no_base_mediator(
6067
route_manager: MultitenantRouteManager,
@@ -360,3 +367,19 @@ async def test_routing_info_with_base_mediator_and_sub_mediator(
360367
)
361368
assert keys == [*base_mediation_record.routing_keys, *mediation_record.routing_keys]
362369
assert endpoint == mediation_record.endpoint
370+
371+
372+
@pytest.mark.asyncio
373+
async def test_connection_from_recipient_key(
374+
sub_profile: Profile, base_route_manager: BaseWalletRouteManager
375+
):
376+
manager = mock.MagicMock()
377+
manager.get_profile_for_key = mock.CoroutineMock(return_value=sub_profile)
378+
sub_profile.context.injector.bind_instance(BaseMultitenantManager, manager)
379+
with mock.patch.object(
380+
RouteManager, "connection_from_recipient_key", mock.CoroutineMock()
381+
) as mock_conn_for_recip:
382+
result = await base_route_manager.connection_from_recipient_key(
383+
sub_profile, TEST_VERKEY
384+
)
385+
assert result == mock_conn_for_recip.return_value

aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/keylist_update_response_handler.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
"""Handler for keylist-update-response message."""
22

3+
from .....core.profile import Profile
34
from .....messaging.base_handler import BaseHandler, HandlerException
45
from .....messaging.request_context import RequestContext
56
from .....messaging.responder import BaseResponder
6-
7-
from ..messages.keylist_update_response import KeylistUpdateResponse
7+
from .....storage.error import StorageNotFoundError
8+
from .....wallet.error import WalletNotFoundError
89
from ..manager import MediationManager
10+
from ..messages.keylist_update_response import KeylistUpdateResponse
11+
from ..route_manager import RouteManager
912

1013

1114
class KeylistUpdateResponseHandler(BaseHandler):
@@ -25,6 +28,39 @@ async def handle(self, context: RequestContext, responder: BaseResponder):
2528
await mgr.store_update_results(
2629
context.connection_record.connection_id, context.message.updated
2730
)
28-
await mgr.notify_keylist_updated(
29-
context.connection_record.connection_id, context.message
31+
await self.notify_keylist_updated(
32+
context.profile, context.connection_record.connection_id, context.message
33+
)
34+
35+
async def notify_keylist_updated(
36+
self, profile: Profile, connection_id: str, response: KeylistUpdateResponse
37+
):
38+
"""Notify of keylist update response received."""
39+
route_manager = profile.inject(RouteManager)
40+
self._logger.debug(
41+
"Retrieving connection ID from route manager of type %s",
42+
type(route_manager).__name__,
43+
)
44+
try:
45+
key_to_connection = {
46+
updated.recipient_key: await route_manager.connection_from_recipient_key(
47+
profile, updated.recipient_key
48+
)
49+
for updated in response.updated
50+
}
51+
except (StorageNotFoundError, WalletNotFoundError) as err:
52+
raise HandlerException(
53+
"Unknown recipient key received in keylist update response"
54+
) from err
55+
56+
await profile.notify(
57+
MediationManager.KEYLIST_UPDATED_EVENT,
58+
{
59+
"connection_id": connection_id,
60+
"thread_id": response._thread_id,
61+
"updated": [update.serialize() for update in response.updated],
62+
"mediated_connections": {
63+
key: conn.connection_id for key, conn in key_to_connection.items()
64+
},
65+
},
3066
)

aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_keylist_update_response_handler.py

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
11
"""Test handler for keylist-update-response message."""
22

3+
from functools import partial
4+
from typing import AsyncGenerator
35
import pytest
46
from asynctest import TestCase as AsyncTestCase
57
from asynctest import mock as async_mock
68

9+
710
from ......connections.models.conn_record import ConnRecord
11+
from ......core.event_bus import EventBus, MockEventBus
812
from ......messaging.base_handler import HandlerException
913
from ......messaging.request_context import RequestContext
1014
from ......messaging.responder import MockResponder
1115
from ...messages.inner.keylist_update_rule import KeylistUpdateRule
1216
from ...messages.inner.keylist_updated import KeylistUpdated
1317
from ...messages.keylist_update_response import KeylistUpdateResponse
1418
from ...manager import MediationManager
19+
from ...route_manager import RouteManager
20+
from ...tests.test_route_manager import MockRouteManager
1521
from ..keylist_update_response_handler import KeylistUpdateResponseHandler
1622

1723
TEST_CONN_ID = "conn-id"
18-
TEST_VERKEY = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx"
24+
TEST_THREAD_ID = "thread-id"
25+
TEST_VERKEY = "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL"
26+
TEST_ROUTE_VERKEY = "did:key:z6MknxTj6Zj1VrDWc1ofaZtmCVv2zNXpD58Xup4ijDGoQhya"
1927

2028

2129
class TestKeylistUpdateResponseHandler(AsyncTestCase):
@@ -34,6 +42,14 @@ async def setUp(self):
3442
self.context.message = KeylistUpdateResponse(updated=self.updated)
3543
self.context.connection_ready = True
3644
self.context.connection_record = ConnRecord(connection_id=TEST_CONN_ID)
45+
self.mock_event_bus = MockEventBus()
46+
self.context.profile.context.injector.bind_instance(
47+
EventBus, self.mock_event_bus
48+
)
49+
self.route_manager = MockRouteManager()
50+
self.context.profile.context.injector.bind_instance(
51+
RouteManager, self.route_manager
52+
)
3753

3854
async def test_handler_no_active_connection(self):
3955
handler, responder = KeylistUpdateResponseHandler(), MockResponder()
@@ -47,8 +63,86 @@ async def test_handler(self):
4763
with async_mock.patch.object(
4864
MediationManager, "store_update_results"
4965
) as mock_store, async_mock.patch.object(
50-
MediationManager, "notify_keylist_updated"
66+
handler, "notify_keylist_updated"
5167
) as mock_notify:
5268
await handler.handle(self.context, responder)
5369
mock_store.assert_called_once_with(TEST_CONN_ID, self.updated)
54-
mock_notify.assert_called_once_with(TEST_CONN_ID, self.context.message)
70+
mock_notify.assert_called_once_with(
71+
self.context.profile, TEST_CONN_ID, self.context.message
72+
)
73+
74+
async def test_notify_keylist_updated(self):
75+
"""test notify_keylist_updated."""
76+
handler = KeylistUpdateResponseHandler()
77+
78+
async def _result_generator():
79+
yield ConnRecord(connection_id="conn_id_1")
80+
yield ConnRecord(connection_id="conn_id_2")
81+
82+
async def _retrieve_by_invitation_key(
83+
generator: AsyncGenerator, *args, **kwargs
84+
):
85+
return await generator.__anext__()
86+
87+
with async_mock.patch.object(
88+
self.route_manager,
89+
"connection_from_recipient_key",
90+
partial(_retrieve_by_invitation_key, _result_generator()),
91+
):
92+
response = KeylistUpdateResponse(
93+
updated=[
94+
KeylistUpdated(
95+
recipient_key=TEST_ROUTE_VERKEY,
96+
action=KeylistUpdateRule.RULE_ADD,
97+
result=KeylistUpdated.RESULT_SUCCESS,
98+
),
99+
KeylistUpdated(
100+
recipient_key=TEST_VERKEY,
101+
action=KeylistUpdateRule.RULE_REMOVE,
102+
result=KeylistUpdated.RESULT_SUCCESS,
103+
),
104+
],
105+
)
106+
107+
response.assign_thread_id(TEST_THREAD_ID)
108+
await handler.notify_keylist_updated(
109+
self.context.profile, TEST_CONN_ID, response
110+
)
111+
assert self.mock_event_bus.events
112+
assert (
113+
self.mock_event_bus.events[0][1].topic
114+
== MediationManager.KEYLIST_UPDATED_EVENT
115+
)
116+
assert self.mock_event_bus.events[0][1].payload == {
117+
"connection_id": TEST_CONN_ID,
118+
"thread_id": TEST_THREAD_ID,
119+
"updated": [result.serialize() for result in response.updated],
120+
"mediated_connections": {
121+
TEST_ROUTE_VERKEY: "conn_id_1",
122+
TEST_VERKEY: "conn_id_2",
123+
},
124+
}
125+
126+
async def test_notify_keylist_updated_x_unknown_recip_key(self):
127+
"""test notify_keylist_updated."""
128+
handler = KeylistUpdateResponseHandler()
129+
response = KeylistUpdateResponse(
130+
updated=[
131+
KeylistUpdated(
132+
recipient_key=TEST_ROUTE_VERKEY,
133+
action=KeylistUpdateRule.RULE_ADD,
134+
result=KeylistUpdated.RESULT_SUCCESS,
135+
),
136+
KeylistUpdated(
137+
recipient_key=TEST_VERKEY,
138+
action=KeylistUpdateRule.RULE_REMOVE,
139+
result=KeylistUpdated.RESULT_SUCCESS,
140+
),
141+
],
142+
)
143+
144+
response.assign_thread_id(TEST_THREAD_ID)
145+
with pytest.raises(HandlerException):
146+
await handler.notify_keylist_updated(
147+
self.context.profile, TEST_CONN_ID, response
148+
)

0 commit comments

Comments
 (0)