Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DID updates for endorser #1601

Merged
merged 4 commits into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions aries_cloudagent/ledger/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,14 @@ async def update_endpoint_for_did(

@abstractmethod
async def register_nym(
self, did: str, verkey: str, alias: str = None, role: str = None
):
self,
did: str,
verkey: str,
alias: str = None,
role: str = None,
write_ledger: bool = True,
endorser_did: str = None,
) -> Tuple[bool, dict]:
"""
Register a nym on the ledger.

Expand Down
22 changes: 17 additions & 5 deletions aries_cloudagent/ledger/indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -928,8 +928,14 @@ async def update_endpoint_for_did(
return False

async def register_nym(
self, did: str, verkey: str, alias: str = None, role: str = None
):
self,
did: str,
verkey: str,
alias: str = None,
role: str = None,
write_ledger: bool = True,
endorser_did: str = None,
) -> Tuple[bool, dict]:
"""
Register a nym on the ledger.

Expand Down Expand Up @@ -957,17 +963,23 @@ async def register_nym(
request_json = await indy.ledger.build_nym_request(
public_info.did, did, verkey, alias, role
)
await self._submit(
request_json
if endorser_did and not write_ledger:
request_json = await indy.ledger.append_request_endorser(
request_json, endorser_did
)
resp = await self._submit(
request_json, sign=True, sign_did=public_info, write_ledger=write_ledger
) # let ledger raise on insufficient privilege

if not write_ledger:
return True, {"signed_txn": resp}
try:
did_info = await wallet.get_local_did(did)
except WalletNotFoundError:
pass # registering another user's NYM
else:
metadata = {**did_info.metadata, **DIDPosture.POSTED.metadata}
await wallet.replace_local_did_metadata(did, metadata)
return True, None

async def get_nym_role(self, did: str) -> Role:
"""
Expand Down
19 changes: 16 additions & 3 deletions aries_cloudagent/ledger/indy_vdr.py
Original file line number Diff line number Diff line change
Expand Up @@ -938,8 +938,14 @@ async def update_endpoint_for_did(
return False

async def register_nym(
self, did: str, verkey: str, alias: str = None, role: str = None
):
self,
did: str,
verkey: str,
alias: str = None,
role: str = None,
write_ledger: bool = True,
endorser_did: str = None,
) -> Tuple[bool, dict]:
"""
Register a nym on the ledger.

Expand All @@ -965,8 +971,14 @@ async def register_nym(
except VdrError as err:
raise LedgerError("Exception when building nym request") from err

await self._submit(nym_req, sign=True, sign_did=public_info)
if endorser_did and not write_ledger:
nym_req.set_endorser(endorser_did)

resp = await self._submit(
nym_req, sign=True, sign_did=public_info, write_ledger=write_ledger
)
if not write_ledger:
return True, {"signed_txn": resp}
async with self.profile.session() as session:
wallet = session.inject(BaseWallet)
try:
Expand All @@ -976,6 +988,7 @@ async def register_nym(
else:
metadata = {**did_info.metadata, **DIDPosture.POSTED.metadata}
await wallet.replace_local_did_metadata(did, metadata)
return True, None

async def get_nym_role(self, did: str) -> Role:
"""
Expand Down
138 changes: 132 additions & 6 deletions aries_cloudagent/ledger/routes.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
"""Ledger admin routes."""

import json

from aiohttp import web
from aiohttp_apispec import docs, querystring_schema, request_schema, response_schema
from marshmallow import fields, validate

from ..admin.request_context import AdminRequestContext
from ..connections.models.conn_record import ConnRecord
from ..messaging.models.base import BaseModelError
from ..messaging.models.openapi import OpenAPISchema
from ..messaging.valid import (
ENDPOINT,
ENDPOINT_TYPE,
INDY_DID,
INDY_RAW_PUBLIC_KEY,
INT_EPOCH,
UUIDFour,
)

from ..protocols.endorse_transaction.v1_0.manager import (
TransactionManager,
TransactionManagerError,
)
from ..protocols.endorse_transaction.v1_0.models.transaction_record import (
TransactionRecordSchema,
)
from ..protocols.endorse_transaction.v1_0.util import (
is_author_role,
get_endorser_connection_id,
)
from ..storage.error import StorageError
from ..storage.error import StorageError, StorageNotFoundError
from ..wallet.error import WalletError, WalletNotFoundError

from .base import BaseLedger, Role as LedgerRole
Expand All @@ -32,6 +49,7 @@
)
from .endpoint_type import EndpointType
from .error import BadLedgerRequestError, LedgerError, LedgerTransactionError
from .util import notify_did_event


class LedgerModulesResultSchema(OpenAPISchema):
Expand Down Expand Up @@ -109,6 +127,23 @@ class RegisterLedgerNymQueryStringSchema(OpenAPISchema):
)


class CreateDidTxnForEndorserOptionSchema(OpenAPISchema):
"""Class for user to input whether to create a transaction for endorser or not."""

create_transaction_for_endorser = fields.Boolean(
description="Create Transaction For Endorser's signature",
required=False,
)


class SchemaConnIdMatchInfoSchema(OpenAPISchema):
"""Path parameters and validators for request taking connection id."""

conn_id = fields.Str(
description="Connection identifier", required=False, example=UUIDFour.EXAMPLE
)


class QueryStringDIDSchema(OpenAPISchema):
"""Parameters and validators for query string with DID only."""

Expand All @@ -128,14 +163,20 @@ class QueryStringEndpointSchema(OpenAPISchema):
)


class RegisterLedgerNymResponseSchema(OpenAPISchema):
class TxnOrRegisterLedgerNymResponseSchema(OpenAPISchema):
"""Response schema for ledger nym registration."""

success = fields.Bool(
description="Success of nym registration operation",
example=True,
)

txn = fields.Nested(
TransactionRecordSchema(),
required=False,
description="DID transaction to endorse",
)


class GetNymRoleResponseSchema(OpenAPISchema):
"""Response schema to get nym role operation."""
Expand Down Expand Up @@ -172,7 +213,9 @@ class GetDIDEndpointResponseSchema(OpenAPISchema):
summary="Send a NYM registration to the ledger.",
)
@querystring_schema(RegisterLedgerNymQueryStringSchema())
@response_schema(RegisterLedgerNymResponseSchema(), 200, description="")
@querystring_schema(CreateDidTxnForEndorserOptionSchema())
@querystring_schema(SchemaConnIdMatchInfoSchema())
@response_schema(TxnOrRegisterLedgerNymResponseSchema(), 200, description="")
async def register_ledger_nym(request: web.BaseRequest):
"""
Request handler for registering a NYM with the ledger.
Expand All @@ -181,6 +224,7 @@ async def register_ledger_nym(request: web.BaseRequest):
request: aiohttp request object
"""
context: AdminRequestContext = request["context"]
outbound_handler = request["outbound_message_router"]
async with context.profile.session() as session:
ledger = session.inject_or(BaseLedger)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, it's not updated by this PR, but BaseLedger should be injected via the profile not the session. Generally anything injected by the session shouldn't be touched after the async with block as it might refer to a closed session. This is also true for the IndyLedgerRequestExecutor instances which seem to be set up by the conductor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK thanks for the tip!

if not ledger:
Expand All @@ -201,11 +245,63 @@ async def register_ledger_nym(request: web.BaseRequest):
if role == "reset": # indy: empty to reset, null for regular user
role = "" # visually: confusing - correct 'reset' to empty string here

create_transaction_for_endorser = json.loads(
request.query.get("create_transaction_for_endorser", "false")
)
write_ledger = not create_transaction_for_endorser
endorser_did = None
connection_id = request.query.get("conn_id")

# check if we need to endorse
if is_author_role(context.profile):
# authors cannot write to the ledger
write_ledger = False
create_transaction_for_endorser = True
if not connection_id:
# author has not provided a connection id, so determine which to use
connection_id = await get_endorser_connection_id(context.profile)
if not connection_id:
raise web.HTTPBadRequest(reason="No endorser connection found")

if not write_ledger:
try:
async with context.profile.session() as session:
connection_record = await ConnRecord.retrieve_by_id(
session, connection_id
)
except StorageNotFoundError as err:
raise web.HTTPNotFound(reason=err.roll_up) from err
except BaseModelError as err:
raise web.HTTPBadRequest(reason=err.roll_up) from err

async with context.profile.session() as session:
endorser_info = await connection_record.metadata_get(
session, "endorser_info"
)
if not endorser_info:
raise web.HTTPForbidden(
reason="Endorser Info is not set up in "
"connection metadata for this connection record"
)
if "endorser_did" not in endorser_info.keys():
raise web.HTTPForbidden(
reason=' "endorser_did" is not set in "endorser_info"'
" in connection metadata for this connection record"
)
endorser_did = endorser_info["endorser_did"]

success = False
txn = None
async with ledger:
try:
await ledger.register_nym(did, verkey, alias, role)
success = True
(success, txn) = await ledger.register_nym(
did,
verkey,
alias,
role,
write_ledger=write_ledger,
endorser_did=endorser_did,
)
except LedgerTransactionError as err:
raise web.HTTPForbidden(reason=err.roll_up)
except LedgerError as err:
Expand All @@ -220,7 +316,37 @@ async def register_ledger_nym(request: web.BaseRequest):
)
)

return web.json_response({"success": success})
meta_data = {"verkey": verkey, "alias": alias, "role": role}
if not create_transaction_for_endorser:
# Notify event
await notify_did_event(context.profile, did, meta_data)
return web.json_response({"success": success})
else:
transaction_mgr = TransactionManager(context.profile)
try:
transaction = await transaction_mgr.create_record(
messages_attach=txn["signed_txn"],
connection_id=connection_id,
meta_data=meta_data,
)
except StorageError as err:
raise web.HTTPBadRequest(reason=err.roll_up) from err

# if auto-request, send the request to the endorser
if context.settings.get_value("endorser.auto_request"):
try:
transaction, transaction_request = await transaction_mgr.create_request(
transaction=transaction,
# TODO see if we need to parameterize these params
# expires_time=expires_time,
# endorser_write_txn=endorser_write_txn,
)
except (StorageError, TransactionManagerError) as err:
raise web.HTTPBadRequest(reason=err.roll_up) from err

await outbound_handler(transaction_request, connection_id=connection_id)

return web.json_response({"success": success, "txn": txn})


@docs(
Expand Down
Loading