Skip to content

Commit

Permalink
Author subwallet setup automation
Browse files Browse the repository at this point in the history
Signed-off-by: jamshale <[email protected]>
  • Loading branch information
jamshale committed Feb 16, 2024
1 parent a7c022a commit 50e2e77
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 104 deletions.
45 changes: 29 additions & 16 deletions aries_cloudagent/multitenant/admin/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
request_schema,
response_schema,
)

from marshmallow import ValidationError, fields, validate, validates_schema

from ...admin.request_context import AdminRequestContext
Expand All @@ -19,46 +18,55 @@
from ...messaging.valid import UUID4_EXAMPLE, JSONWebToken
from ...multitenant.base import BaseMultitenantManager
from ...storage.error import StorageError, StorageNotFoundError
from ...utils.endorsement import attempt_auto_author_with_endorser_setup
from ...wallet.error import WalletSettingsError
from ...wallet.models.wallet_record import WalletRecord, WalletRecordSchema
from ..error import WalletKeyMissingError

ACAPY_LIFECYCLE_CONFIG_FLAG_MAP = {
"ACAPY_LOG_LEVEL": "log.level",
"ACAPY_INVITE_PUBLIC": "debug.invite_public",
"ACAPY_PUBLIC_INVITES": "public_invites",
"ACAPY_AUTO_ACCEPT_INVITES": "debug.auto_accept_invites",
"ACAPY_AUTO_ACCEPT_REQUESTS": "debug.auto_accept_requests",
"ACAPY_AUTO_PING_CONNECTION": "auto_ping_connection",
"ACAPY_MONITOR_PING": "debug.monitor_ping",
"ACAPY_AUTO_RESPOND_MESSAGES": "debug.auto_respond_messages",
"ACAPY_AUTO_PROMOTE_AUTHOR_DID": "endorser.auto_promote_author_did",
"ACAPY_AUTO_REQUEST_ENDORSEMENT": "endorser.auto_request",
"ACAPY_AUTO_RESPOND_CREDENTIAL_OFFER": "debug.auto_respond_credential_offer",
"ACAPY_AUTO_RESPOND_CREDENTIAL_REQUEST": "debug.auto_respond_credential_request",
"ACAPY_AUTO_RESPOND_MESSAGES": "debug.auto_respond_messages",
"ACAPY_AUTO_VERIFY_PRESENTATION": "debug.auto_verify_presentation",
"ACAPY_NOTIFY_REVOCATION": "revocation.notify",
"ACAPY_AUTO_REQUEST_ENDORSEMENT": "endorser.auto_request",
"ACAPY_AUTO_WRITE_TRANSACTIONS": "endorser.auto_write",
"ACAPY_CREATE_REVOCATION_TRANSACTIONS": "endorser.auto_create_rev_reg",
"ACAPY_ENDORSER_ALIAS": "endorser.endorser_alias",
"ACAPY_ENDORSER_INVITATION": "endorser.endorser_invitation",
"ACAPY_ENDORSER_PUBLIC_DID": "endorser.endorser_public_did",
"ACAPY_ENDORSER_ROLE": "endorser.protocol_role",
"ACAPY_INVITE_PUBLIC": "debug.invite_public",
"ACAPY_LOG_LEVEL": "log.level",
"ACAPY_MONITOR_PING": "debug.monitor_ping",
"ACAPY_NOTIFY_REVOCATION": "revocation.notify",
"ACAPY_PUBLIC_INVITES": "public_invites",
}

ACAPY_LIFECYCLE_CONFIG_FLAG_ARGS_MAP = {
"log-level": "log.level",
"invite-public": "debug.invite_public",
"public-invites": "public_invites",
"auto-accept-invites": "debug.auto_accept_invites",
"auto-accept-requests": "debug.auto_accept_requests",
"auto-create-revocation-transactions": "endorser.auto_create_rev_reg",
"auto-ping-connection": "auto_ping_connection",
"monitor-ping": "debug.monitor_ping",
"auto-respond-messages": "debug.auto_respond_messages",
"auto-promote-author-did": "endorser.auto_promote_author_did",
"auto-request-endorsement": "endorser.auto_request",
"auto-respond-credential-offer": "debug.auto_respond_credential_offer",
"auto-respond-credential-request": "debug.auto_respond_credential_request",
"auto-respond-messages": "debug.auto_respond_messages",
"auto-verify-presentation": "debug.auto_verify_presentation",
"notify-revocation": "revocation.notify",
"auto-request-endorsement": "endorser.auto_request",
"auto-write-transactions": "endorser.auto_write",
"auto-create-revocation-transactions": "endorser.auto_create_rev_reg",
"endorser-alias": "endorser.endorser_alias",
"endorser-invitation": "endorser.endorser_invitation",
"endorser-protocol-role": "endorser.protocol_role",
"endorser-public-did": "endorser.endorser_public_did",
"invite-public": "debug.invite_public",
"log-level": "log.level",
"monitor-ping": "debug.monitor_ping",
"notify-revocation": "revocation.notify",
"public-invites": "public_invites",
}

ACAPY_ENDORSER_FLAGS_DEPENDENT_ON_AUTHOR_ROLE = [
Expand Down Expand Up @@ -452,6 +460,11 @@ async def wallet_create(request: web.BaseRequest):
)

token = await multitenant_mgr.create_auth_token(wallet_record, wallet_key)

wallet_profile = await multitenant_mgr.get_wallet_profile(
context, wallet_record, extra_settings=settings
)
await attempt_auto_author_with_endorser_setup(wallet_profile)
except BaseError as err:
raise web.HTTPBadRequest(reason=err.roll_up) from err

Expand Down
4 changes: 3 additions & 1 deletion aries_cloudagent/protocols/didexchange/v1_0/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from did_peer_4 import LONG_PATTERN, long_to_short

from ....admin.server import AdminResponder
from ....connections.base_manager import BaseConnectionManager
from ....connections.models.conn_record import ConnRecord
from ....connections.models.connection_target import ConnectionTarget
Expand Down Expand Up @@ -150,7 +151,8 @@ async def receive_invitation(

if conn_rec.accept == ConnRecord.ACCEPT_AUTO:
request = await self.create_request(conn_rec, mediation_id=mediation_id)
responder = self.profile.inject_or(BaseResponder)
base_responder = self.profile.inject(BaseResponder)
responder = AdminResponder(self.profile, base_responder.send_fn)
if responder:
await responder.send_reply(
request,
Expand Down
89 changes: 2 additions & 87 deletions aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
request_schema,
response_schema,
)

from marshmallow import fields, validate

from ....admin.request_context import AdminRequestContext
Expand All @@ -23,17 +22,11 @@
from ....messaging.models.base import BaseModelError
from ....messaging.models.openapi import OpenAPISchema
from ....messaging.valid import UUID4_EXAMPLE
from ....protocols.connections.v1_0.manager import ConnectionManager
from ....protocols.connections.v1_0.messages.connection_invitation import (
ConnectionInvitation,
)
from ....protocols.out_of_band.v1_0.manager import OutOfBandManager
from ....protocols.out_of_band.v1_0.messages.invitation import InvitationMessage
from ....storage.error import StorageError, StorageNotFoundError
from ....utils.endorsement import attempt_auto_author_with_endorser_setup
from .manager import TransactionManager, TransactionManagerError
from .models.transaction_record import TransactionRecord, TransactionRecordSchema
from .transaction_jobs import TransactionJob
from .util import get_endorser_connection_id, is_author_role

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -707,85 +700,7 @@ def register_events(event_bus: EventBus):
async def on_startup_event(profile: Profile, event: Event):
"""Handle any events we need to support."""

# auto setup is only for authors
if not is_author_role(profile):
return

# see if we have an invitation to connect to the endorser
endorser_invitation = profile.settings.get_value("endorser.endorser_invitation")
if not endorser_invitation:
# no invitation, we can't connect automatically
return

# see if we need to initiate an endorser connection
endorser_alias = profile.settings.get_value("endorser.endorser_alias")
if not endorser_alias:
# no alias is specified for the endorser connection
# note that alias is required if invitation is specified
return

connection_id = await get_endorser_connection_id(profile)
if connection_id:
# there is already a connection
return

endorser_did = profile.settings.get_value("endorser.endorser_public_did")
if not endorser_did:
# no DID, we can connect but we can't properly setup the connection metadata
# note that DID is required if invitation is specified
return

try:
# OK, we are an author, we have no endorser connection but we have enough info
# to automatically initiate the connection
invite = InvitationMessage.from_url(endorser_invitation)
if invite:
oob_mgr = OutOfBandManager(profile)
oob_record = await oob_mgr.receive_invitation(
invitation=invite,
auto_accept=True,
alias=endorser_alias,
)
async with profile.session() as session:
conn_record = await ConnRecord.retrieve_by_id(
session, oob_record.connection_id
)
else:
invite = ConnectionInvitation.from_url(endorser_invitation)
if invite:
conn_mgr = ConnectionManager(profile)
conn_record = await conn_mgr.receive_invitation(
invitation=invite,
auto_accept=True,
alias=endorser_alias,
)
else:
raise Exception(
"Failed to establish endorser connection, invalid "
"invitation format."
)

# configure the connection role and info (don't need to wait for the connection)
transaction_mgr = TransactionManager(profile)
await transaction_mgr.set_transaction_my_job(
record=conn_record,
transaction_my_job=TransactionJob.TRANSACTION_AUTHOR.name,
)

async with profile.session() as session:
value = await conn_record.metadata_get(session, "endorser_info")
if value:
value["endorser_did"] = endorser_did
value["endorser_name"] = endorser_alias
else:
value = {"endorser_did": endorser_did, "endorser_name": endorser_alias}
await conn_record.metadata_set(session, key="endorser_info", value=value)

except Exception:
# log the error, but continue
LOGGER.exception(
"Error accepting endorser invitation/configuring endorser connection: %s",
)
await attempt_auto_author_with_endorser_setup(profile)


async def on_shutdown_event(profile: Profile, event: Event):
Expand Down
105 changes: 105 additions & 0 deletions aries_cloudagent/utils/endorsement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""Common endorsement utilities."""

import logging

from ..connections.models.conn_record import ConnRecord
from ..core.profile import Profile
from ..protocols.connections.v1_0.manager import ConnectionManager
from ..protocols.connections.v1_0.messages.connection_invitation import (
ConnectionInvitation,
)
from ..protocols.endorse_transaction.v1_0.manager import TransactionManager
from ..protocols.endorse_transaction.v1_0.transaction_jobs import TransactionJob
from ..protocols.endorse_transaction.v1_0.util import (
get_endorser_connection_id,
is_author_role,
)
from ..protocols.out_of_band.v1_0.manager import OutOfBandManager
from ..protocols.out_of_band.v1_0.messages.invitation import InvitationMessage

LOGGER = logging.getLogger(__name__)


async def attempt_auto_author_with_endorser_setup(profile: Profile):
"""Automatically setup the author's endorser connection if possible."""

if not is_author_role(profile):
return

endorser_invitation = profile.settings.get_value("endorser.endorser_invitation")
if not endorser_invitation:
LOGGER.info("No endorser invitation, can't connect automatically.")
return

endorser_alias = profile.settings.get_value("endorser.endorser_alias")
if not endorser_alias:
LOGGER.info("No endorser alias, alias is required if invitation is specified.")
return

connection_id = await get_endorser_connection_id(profile)
if connection_id:
LOGGER.info("Connected to endorser from previous connection.")
return

endorser_did = profile.settings.get_value("endorser.endorser_public_did")
if not endorser_did:
LOGGER.info(
"No endorser DID, can connect, but can't setup connection metadata."
)
return

try:
# OK, we are an author, we have no endorser connection but we have enough info
# to automatically initiate the connection
invite = InvitationMessage.from_url(endorser_invitation)
if invite:
oob_mgr = OutOfBandManager(profile)
oob_record = await oob_mgr.receive_invitation(
invitation=invite,
auto_accept=True,
alias=endorser_alias,
)
async with profile.session() as session:
conn_record = await ConnRecord.retrieve_by_id(
session, oob_record.connection_id
)
else:
invite = ConnectionInvitation.from_url(endorser_invitation)
if invite:
conn_mgr = ConnectionManager(profile)
conn_record = await conn_mgr.receive_invitation(
invitation=invite,
auto_accept=True,
alias=endorser_alias,
)
else:
raise Exception(
"Failed to establish endorser connection, invalid "
"invitation format."
)

# configure the connection role and info (don't need to wait for the connection)
transaction_mgr = TransactionManager(profile)
await transaction_mgr.set_transaction_my_job(
record=conn_record,
transaction_my_job=TransactionJob.TRANSACTION_AUTHOR.name,
)

async with profile.session() as session:
value = await conn_record.metadata_get(session, "endorser_info")
if value:
value["endorser_did"] = endorser_did
value["endorser_name"] = endorser_alias
else:
value = {"endorser_did": endorser_did, "endorser_name": endorser_alias}
await conn_record.metadata_set(session, key="endorser_info", value=value)

LOGGER.info(
"Successfully connected to endorser from invitation, and setup connection metadata." # noqa: E501
)

except Exception:
# log the error, but continue
LOGGER.info(
"Error accepting endorser invitation/configuring endorser connection"
)

0 comments on commit 50e2e77

Please sign in to comment.