Skip to content
This repository has been archived by the owner on Sep 22, 2023. It is now read-only.

Commit

Permalink
Merge pull request #46 from dbluhm/feature/coordinate-mediation
Browse files Browse the repository at this point in the history
Coordinate Mediation Support
  • Loading branch information
dbluhm authored Dec 18, 2020
2 parents 53fb8fb + 4e844ec commit 93a51dc
Show file tree
Hide file tree
Showing 21 changed files with 595 additions and 140 deletions.
80 changes: 48 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ Requirements:
- Docker Compose

### Disclaimer regarding the use of ngrok
Both compose setups use the ngrok tunneling service to make your agent available
to the outside world. One caveat of this, however, is that connections made from
your docker agent will expire within 8 hours as a limitation of the ngrok
free-tier. Therefore, **these setups are intended for demonstration purposes
only** and should not be relied upon as is for production environments.

### One Agent demo
Two of the docker compose setups use the ngrok tunneling service to make your
agent available to the outside world. One caveat of this, however, is that
connections made from your docker agent will expire within 8 hours as a
limitation of the ngrok free-tier. Therefore, **these setups are intended for
demonstration purposes only** and should not be relied upon as is for production
environments.

### One Agent Demo
To start the single agent demo:

```sh
$ docker-compose -f docker-compose.yml up --build
$ docker-compose -f docker/docker-compose.yml up --build
```

This will start two containers, one for the ngrok tunnel and one for the agent.
Expand All @@ -35,15 +36,30 @@ starting up. The agent container will emit a fair amount of logs, including
of starting up it will print an invitation to the screen that can then be pasted
into the toolbox to connect to and remotely administer your docker agent.

### Two Agent demo
To start up an Alice and Bob demo:
### Two Agent Demos
To start up an Alice and Bob demo **with** ngrok:

```sh
$ docker-compose -f docker/docker-compose-a-b-ngrok.yml up --build
```
This will start four containers, two ngrok tunnels and two agent containers.

To start up an Alice and Bob demo **without** ngrok (**Windows** and **Mac**):

```sh
$ docker-compose -f docker-compose_alice_bob.yml up --build
$ docker-compose -f docker/docker-compose-a-b.yml up --build
```

This will start four containers, two ngrok tunnels and two agent containers. Two
invitations will be printed to the screen corresponding to Alice and Bob.
Due to differences in how networking is handled on Windows and Mac docker when
compared to Linux docker, use the following if you are on **Linux**:

```sh
$ docker-compose -f docker/docker-compose-a-b-linux.yml up --build
```

This will start only the two agent containers.

Two invitations will be printed to the screen corresponding to Alice and Bob.
Pasting these invitations into the toolbox will result in "Alice (Admin)" and
"Bob (Admin)" connections. Using the toolbox, you can then cause these two
agents to interact with each other in various ways.
Expand Down Expand Up @@ -109,10 +125,10 @@ $ pip install git+https://github.com/hyperledger/aries-acapy-plugin-toolbox.git@
Start up ACA-Py with the plugin parameter:
```sh
$ aca-py start \
-it http localhost 3000 -it ws localhost 3001 \
-ot http \
-e http://localhost:3000 ws://localhost:3001 \
--plugin acapy_plugin_toolbox
-it http localhost 3000 -it ws localhost 3001 \
-ot http \
-e http://localhost:3000 ws://localhost:3001 \
--plugin acapy_plugin_toolbox
```

Passing the whole package `acapy_plugin_toolbox` will load all protocol
Expand Down Expand Up @@ -143,11 +159,11 @@ Available groups include:
To load the "connections" group and the "basicmessage" module:
```sh
$ aca-py start \
-it http localhost 3000 -it ws localhost 3001 \
-ot http \
-e http://localhost:3000 ws://localhost:3001 \
--plugin acapy_plugin_toolbox.group.connections
--plugin acapy_plugin_toolbox.basicmessage
-it http localhost 3000 -it ws localhost 3001 \
-ot http \
-e http://localhost:3000 ws://localhost:3001 \
--plugin acapy_plugin_toolbox.group.connections
--plugin acapy_plugin_toolbox.basicmessage
```

### Generating an invitation for use with the Toolbox
Expand All @@ -159,11 +175,11 @@ can then be loaded into the Aries Toolbox:

```sh
$ aca-py start \
-it http localhost 3000 -it ws localhost 3001 \
-ot http \
-e http://localhost:3000 ws://localhost:3001 \
--plugin acapy_plugin_toolbox \
--invite --invite-label "My agent admin connection" --invite-role admin
-it http localhost 3000 -it ws localhost 3001 \
-ot http \
-e http://localhost:3000 ws://localhost:3001 \
--plugin acapy_plugin_toolbox \
--invite --invite-label "My agent admin connection" --invite-role admin
```

The invitation will be printed to the screen after the agent has started up and
Expand All @@ -187,8 +203,8 @@ and a start up command similar to the following (with environment variables
replaced with your appropriate values or available in the environment):
```sh
$ aca-py start \
-it http localhost 3000 -it ws localhost 3001 \
-ot http \
-it http localhost 3000 -it ws localhost 3001 \
-ot http \
-e $ENDPOINT ${ENDPOINT/http/ws} \
--label $AGENT_NAME \
--auto-accept-requests --auto-ping-connection \
Expand All @@ -209,9 +225,9 @@ that generally provides only one port-to-port tunnel at a time.
To use the HTTP+WS transport:
```sh
$ aca-py start \
-it acapy_plugin_toolbox.http_ws localhost 3000 \
-ot http \
-e http://localhost:3000 ws://localhost:3000
-it acapy_plugin_toolbox.http_ws localhost 3000 \
-ot http \
-e http://localhost:3000 ws://localhost:3000
```

Note that you do not need to load any other plugins for this transport but you
Expand Down
46 changes: 31 additions & 15 deletions acapy_plugin_toolbox/basicmessage.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
"""BasicMessage Plugin."""
# pylint: disable=invalid-name, too-few-public-methods

from typing import Union
import json
from datetime import datetime
from typing import Union

from marshmallow import fields

from aries_cloudagent.connections.models.conn_record import ConnRecord
from aries_cloudagent.core.profile import ProfileSession
from aries_cloudagent.core.protocol_registry import ProtocolRegistry
from aries_cloudagent.connections.models.conn_record import (
ConnRecord
)
from aries_cloudagent.messaging.base_handler import (
BaseHandler, BaseResponder, RequestContext
)
Expand All @@ -21,12 +18,18 @@
BaseRecord, BaseRecordSchema
)
from aries_cloudagent.messaging.valid import INDY_ISO8601_DATETIME
from aries_cloudagent.protocols.connections.v1_0.manager import ConnectionManager
from aries_cloudagent.protocols.problem_report.v1_0.message import ProblemReport
from aries_cloudagent.protocols.connections.v1_0.manager import (
ConnectionManager
)
from aries_cloudagent.protocols.problem_report.v1_0.message import (
ProblemReport
)
from aries_cloudagent.storage.base import BaseStorage
from aries_cloudagent.storage.error import StorageNotFoundError
from marshmallow import fields

from .util import (
generate_model_schema, admin_only, timestamp_utc_iso, datetime_from_iso
admin_only, datetime_from_iso, generate_model_schema, timestamp_utc_iso
)

PROTOCOL_URI = "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/basicmessage/1.0"
Expand Down Expand Up @@ -217,14 +220,15 @@ class BasicMessageHandler(BaseHandler):

async def handle(self, context: RequestContext, responder: BaseResponder):
"""Handle received basic message."""
session = await context.session()
msg = BasicMessageRecord(
connection_id=context.connection_record.connection_id,
message_id=context.message._id,
sent_time=context.message.sent_time,
content=context.message.content,
state=BasicMessageRecord.STATE_RECV
)
await msg.save(context, reason='New message received.')
await msg.save(session, reason='New message received.')

await responder.send_webhook(
"basicmessages",
Expand All @@ -236,11 +240,22 @@ async def handle(self, context: RequestContext, responder: BaseResponder):
},
)

connection_mgr = ConnectionManager(context)
session = await context.session()
admins = await ConnRecord.query(
session, post_filter_positive={'their_role': 'admin'}
connection_mgr = ConnectionManager(session)
storage = session.inject(BaseStorage)
admin_ids = map(
lambda record: record.tags['connection_id'],
filter(
lambda record: json.loads(record.value) == 'admin',
await storage.find_all_records(
ConnRecord.RECORD_TYPE_METADATA, {'key': 'group'}
)
)
)
admins = [
await ConnRecord.retrieve_by_id(session, id)
for id in admin_ids
]

if not admins:
return
Expand Down Expand Up @@ -377,9 +392,10 @@ class SendHandler(BaseHandler):
async def handle(self, context: RequestContext, responder: BaseResponder):
"""Handle received send requests."""
# pylint: disable=protected-access
session = await context.session()
try:
connection = await ConnRecord.retrieve_by_id(
context, context.message.connection_id
session, context.message.connection_id
)
except StorageNotFoundError:
report = ProblemReport(
Expand All @@ -404,7 +420,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder):
content=msg.content,
state=BasicMessageRecord.STATE_SENT
)
await record.save(context, reason='Message sent.')
await record.save(session, reason='Message sent.')
sent_msg = Sent(connection_id=connection.connection_id, message=record)
sent_msg.assign_thread_from(context.message)
await responder.send_reply(sent_msg)
Expand Down
9 changes: 6 additions & 3 deletions acapy_plugin_toolbox/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,8 @@ async def handle(self, context: RequestContext, responder: BaseResponder):
'invitation': fields.Str(required=True),
'auto_accept': fields.Bool(
missing=False
)
),
'mediation_id': fields.Str(required=False),
}
)

Expand All @@ -281,11 +282,13 @@ class ReceiveInvitationHandler(BaseHandler):
@admin_only
async def handle(self, context: RequestContext, responder: BaseResponder):
"""Handle recieve invitation request."""
connection_mgr = ConnectionManager(context)
session = await context.session()
connection_mgr = ConnectionManager(session)
invitation = ConnectionInvitation.from_url(context.message.invitation)
connection = await connection_mgr.receive_invitation(
invitation,
auto_accept=context.message.auto_accept
auto_accept=context.message.auto_accept,
mediation_id=context.message.mediation_id,
)
connection_resp = Connection(**conn_record_to_message_repr(connection))
await responder.send_reply(connection_resp)
4 changes: 2 additions & 2 deletions acapy_plugin_toolbox/credential_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder):
state=SchemaRecord.STATE_WRITTEN,
author=SchemaRecord.AUTHOR_OTHER
)
await schema_record.save(context, reason='Retrieved from ledger')
await schema_record.save(session, reason='Retrieved from ledger')

try:
async with ledger:
Expand Down Expand Up @@ -296,7 +296,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder):
state=SchemaRecord.STATE_WRITTEN,
author=SchemaRecord.AUTHOR_OTHER
)
await schema_record.save(context, reason='Retrieved from ledger')
await schema_record.save(session, reason='Retrieved from ledger')

cred_def_record = CredDefRecord(
cred_def_id=credential_definition['id'],
Expand Down
6 changes: 4 additions & 2 deletions acapy_plugin_toolbox/holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,10 @@ async def handle(self, context: RequestContext, responder: BaseResponder):

credential_manager = CredentialManager(context)

session = await context.session()
try:
conn_record = await ConnRecord.retrieve_by_id(
context,
session,
connection_id
)
except StorageNotFoundError:
Expand Down Expand Up @@ -173,9 +174,10 @@ async def handle(self, context: RequestContext, responder: BaseResponder):
"""Handle received send presentation proposal request."""

connection_id = str(context.message.connection_id)
session = await context.session()
try:
conn_record = await ConnRecord.retrieve_by_id(
context,
session,
connection_id
)
except StorageNotFoundError:
Expand Down
1 change: 1 addition & 0 deletions acapy_plugin_toolbox/http_ws.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from aries_cloudagent.transport.inbound.base import BaseInboundTransport
from aries_cloudagent.transport.inbound import http, ws


class HttpWsTransport(BaseInboundTransport):
"""Http+Ws Transport class."""

Expand Down
18 changes: 15 additions & 3 deletions acapy_plugin_toolbox/invitations.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,24 +75,26 @@ async def setup(
schema={
'label': fields.Str(required=False),
'alias': fields.Str(required=False), # default?
'role': fields.Str(required=False),
'group': fields.Str(required=False),
'auto_accept': fields.Boolean(missing=False),
'multi_use': fields.Boolean(missing=False),
'mediation_id': fields.Str(required=False)
}
)

BaseInvitationSchema = Schema.from_dict({
'id': fields.Str(required=True),
'label': fields.Str(required=False),
'alias': fields.Str(required=False), # default?
'role': fields.Str(required=False),
'group': fields.Str(required=False),
'auto_accept': fields.Boolean(missing=False),
'multi_use': fields.Boolean(missing=False),
'invitation_url': fields.Str(required=True),
'created_date': fields.Str(
required=False, description="Time of record creation",
**INDY_ISO8601_DATETIME
),
'mediation_id': fields.Str(required=False),
'raw_repr': fields.Dict(required=False),
})

Expand All @@ -119,23 +121,31 @@ class CreateInvitationHandler(BaseHandler):
@admin_only
async def handle(self, context: RequestContext, responder: BaseResponder):
"""Handle create invitation request."""
connection_mgr = ConnectionManager(context)
session = await context.session()
connection_mgr = ConnectionManager(session)
connection, invitation = await connection_mgr.create_invitation(
my_label=context.message.label,
auto_accept=context.message.auto_accept,
multi_use=bool(context.message.multi_use),
public=False,
alias=context.message.alias,
mediation_id=context.message.mediation_id,
)
if context.message.group:
await connection.metadata_set(
session, "group", context.message.group
)
invite_response = Invitation(
id=connection.connection_id,
label=invitation.label,
alias=connection.alias,
group=context.message.group,
auto_accept=connection.accept == ConnRecord.ACCEPT_AUTO,
multi_use=(
connection.invitation_mode ==
ConnRecord.INVITATION_MODE_MULTI
),
mediation_id=context.message.mediation_id,
invitation_url=invitation.to_url(),
created_date=connection.created_at,
raw_repr={
Expand Down Expand Up @@ -176,11 +186,13 @@ async def handle(self, context: RequestContext, responder: BaseResponder):
invitation = await connection.retrieve_invitation(session)
except StorageNotFoundError:
continue
group = await connection.metadata_get(session, 'group')

invite = {
'id': connection.connection_id,
'label': invitation.label,
'alias': connection.alias,
'group': group,
'auto_accept': (
connection.accept == ConnRecord.ACCEPT_AUTO
),
Expand Down
Loading

0 comments on commit 93a51dc

Please sign in to comment.