From 6a7b67f51b19061e771a74ecbd8ea4fd74777348 Mon Sep 17 00:00:00 2001 From: Karol Krzosa Date: Mon, 28 Dec 2020 13:50:25 +0100 Subject: [PATCH] Init acknowledgment phase in present-proof, Update responses in issue and present protocols, Add endpoint for swagger retrieval --- aries_cloudagent/holder/routes.py | 1 - aries_cloudagent/pdstorage_thcf/routes.py | 6 ++ .../protocols/issue_credential/v1_1/routes.py | 47 +++++++--- .../protocols/issue_credential/v1_1/utils.py | 1 + .../v1_1/handlers/acknowledge_proof.py | 55 +++++++++++ .../v1_1/handlers/present_proof.py | 3 +- .../present_proof/v1_1/message_types.py | 4 + .../v1_1/messages/acknowledge_proof.py | 34 +++++++ .../v1_1/messages/present_proof.py | 4 +- .../v1_1/messages/request_proof.py | 3 - .../v1_1/models/presentation_exchange.py | 12 ++- .../present_proof/v1_1/models/utils.py | 5 +- .../protocols/present_proof/v1_1/routes.py | 94 +++++++++++++++++-- 13 files changed, 240 insertions(+), 29 deletions(-) create mode 100644 aries_cloudagent/protocols/present_proof/v1_1/handlers/acknowledge_proof.py create mode 100644 aries_cloudagent/protocols/present_proof/v1_1/messages/acknowledge_proof.py diff --git a/aries_cloudagent/holder/routes.py b/aries_cloudagent/holder/routes.py index d3175365af..b73976135d 100644 --- a/aries_cloudagent/holder/routes.py +++ b/aries_cloudagent/holder/routes.py @@ -268,7 +268,6 @@ async def register(app: web.Application): def post_process_routes(app: web.Application): """Amend swagger API.""" - print("SWAGGER: ", app._state["swagger_dict"]) # Add top-level tags description if "tags" not in app._state["swagger_dict"]: app._state["swagger_dict"]["tags"] = [] diff --git a/aries_cloudagent/pdstorage_thcf/routes.py b/aries_cloudagent/pdstorage_thcf/routes.py index 2348822c1e..4b55d8480a 100644 --- a/aries_cloudagent/pdstorage_thcf/routes.py +++ b/aries_cloudagent/pdstorage_thcf/routes.py @@ -287,6 +287,11 @@ async def get_table_of_records(request: web.BaseRequest): return web.json_response(json.loads(result)) +@docs(tags=["Swagger"], summary="Get agent's swagger schema in json format") +async def get_swagger_schema(request: web.BaseRequest): + return web.json_response(request.app._state["swagger_dict"]) + + async def register(app: web.Application): """Register routes.""" app.add_routes( @@ -318,5 +323,6 @@ async def register(app: web.Application): get_table_of_records, allow_head=False, ), + web.get("/swagger", get_swagger_schema, allow_head=False), ] ) diff --git a/aries_cloudagent/protocols/issue_credential/v1_1/routes.py b/aries_cloudagent/protocols/issue_credential/v1_1/routes.py index 359fd9ad7b..ee61011f47 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_1/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_1/routes.py @@ -53,12 +53,14 @@ async def issue_credential(request: web.BaseRequest): credential_exchange_id = request.query.get("credential_exchange_id") exchange = await retrieve_credential_exchange(context, credential_exchange_id) + raise_exception_invalid_state( exchange, CredentialExchangeRecord.STATE_REQUEST_RECEIVED, CredentialExchangeRecord.ROLE_ISSUER, web.HTTPBadRequest, ) + connection = await retrieve_connection(context, exchange.connection_id) request = exchange.credential_request credential = await create_credential( @@ -67,8 +69,8 @@ async def issue_credential(request: web.BaseRequest): their_public_did=exchange.their_public_did, exception=web.HTTPError, ) - LOG("CREDENTIAL %s", credential) + LOG("CREDENTIAL %s", credential) issue = CredentialIssue(credential=credential) issue.assign_thread_id(exchange.thread_id) await outbound_handler(issue, connection_id=connection.connection_id) @@ -76,7 +78,31 @@ async def issue_credential(request: web.BaseRequest): exchange.state = CredentialExchangeRecord.STATE_ISSUED await exchange.save(context) - return web.json_response(credential) + return web.json_response( + { + "success": True, + "credential_exchange_id": exchange._id, + } + ) + + +async def routes_get_public_did(context): + wallet: BaseWallet = await context.inject(BaseWallet) + public_did = await wallet.get_public_did() + + if public_did is None: + raise web.HTTPBadRequest( + reason="Your public did is None!, acquire a public did before requesting a credential" + ) + + public_did = public_did[0] + + if public_did is None: + raise web.HTTPBadRequest( + reason="Your public did is None!, acquire a public did before requesting a credential" + ) + + return public_did @docs(tags=["issue-credential"], summary="Request Credential") @@ -84,21 +110,14 @@ async def issue_credential(request: web.BaseRequest): async def request_credential(request: web.BaseRequest): context = request.app["request_context"] outbound_handler = request.app["outbound_message_router"] - wallet: BaseWallet = await context.inject(BaseWallet) body = await request.json() credential_values = body.get("credential_values") connection_id = body.get("connection_id") connection_record = await retrieve_connection(context, connection_id) - public_did = await wallet.get_public_did() - public_did = public_did[0] + public_did = await routes_get_public_did(context) print("Public DID: ", public_did) - if public_did is None: - raise web.HTTPBadRequest( - reason="Your public did is None!, acquire a public did before requesting a credential" - ) - issue = CredentialRequest( credential={"credential_values": credential_values}, did=public_did ) @@ -117,7 +136,13 @@ async def request_credential(request: web.BaseRequest): context, reason="Save record of agent credential exchange" ) - return web.json_response({"credential_exchange_id": exchange_id}) + return web.json_response( + { + "success": True, + "thread_id": issue._thread_id, + "credential_exchange_id": exchange_id, + } + ) @docs(tags=["issue-credential"], summary="Retrieve Credential Exchange") diff --git a/aries_cloudagent/protocols/issue_credential/v1_1/utils.py b/aries_cloudagent/protocols/issue_credential/v1_1/utils.py index ac689cf6db..a4bfb21cf2 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_1/utils.py +++ b/aries_cloudagent/protocols/issue_credential/v1_1/utils.py @@ -72,6 +72,7 @@ async def create_credential( """ credential_type = credential_request.get("credential_type") credential_values = credential_request.get("credential_values") + if their_public_did is not None: assert_type(their_public_did, str) credential_values.update({"id": their_public_did}) diff --git a/aries_cloudagent/protocols/present_proof/v1_1/handlers/acknowledge_proof.py b/aries_cloudagent/protocols/present_proof/v1_1/handlers/acknowledge_proof.py new file mode 100644 index 0000000000..68acc82dda --- /dev/null +++ b/aries_cloudagent/protocols/present_proof/v1_1/handlers/acknowledge_proof.py @@ -0,0 +1,55 @@ +from .....messaging.base_handler import ( + BaseHandler, + BaseResponder, + HandlerException, + RequestContext, +) +from aries_cloudagent.aathcf.utils import debug_handler +from aries_cloudagent.protocols.present_proof.v1_1.models.presentation_exchange import ( + THCFPresentationExchange, +) +from aries_cloudagent.protocols.present_proof.v1_1.messages.acknowledge_proof import ( + AcknowledgeProof, +) +from aries_cloudagent.verifier.base import BaseVerifier +from ..models.utils import retrieve_exchange_by_thread +import json +from collections import OrderedDict +from aries_cloudagent.aathcf.credentials import raise_exception_invalid_state + + +# TODO Error handling +class AcknowledgeProofHandler(BaseHandler): + """ + Message handler logic for incoming credential presentations / incoming proofs. + """ + + async def handle(self, context: RequestContext, responder: BaseResponder): + debug_handler(self._logger.info, context, AcknowledgeProof) + + exchange_record: THCFPresentationExchange = await retrieve_exchange_by_thread( + context, + responder.connection_id, + context.message._thread_id, + HandlerException, + ) + + raise_exception_invalid_state( + exchange_record, + THCFPresentationExchange.STATE_PRESENTATION_SENT, + THCFPresentationExchange.ROLE_PROVER, + HandlerException, + ) + + exchange_record.acknowledgment_credential = context.message.credential + exchange_record.state = exchange_record.STATE_ACKNOWLEDGED + await exchange_record.save(context) + + await responder.send_webhook( + "present_proof", + { + "type": "acknowledge_proof", + "exchange_record_id": exchange_record._id, + "connection_id": responder.connection_id, + }, + ) diff --git a/aries_cloudagent/protocols/present_proof/v1_1/handlers/present_proof.py b/aries_cloudagent/protocols/present_proof/v1_1/handlers/present_proof.py index 0d84ea2bbd..d5a5ddd910 100644 --- a/aries_cloudagent/protocols/present_proof/v1_1/handlers/present_proof.py +++ b/aries_cloudagent/protocols/present_proof/v1_1/handlers/present_proof.py @@ -63,7 +63,8 @@ async def handle(self, context: RequestContext, responder: BaseResponder): exchange_record.presentation = presentation exchange_record.verified = True - exchange_record.state = exchange_record.STATE_VERIFIED + exchange_record.prover_public_did = context.message.prover_public_did + exchange_record.state = exchange_record.STATE_PRESENTATION_RECEIVED await exchange_record.save(context, reason="PresentationExchange updated!") await responder.send_webhook( diff --git a/aries_cloudagent/protocols/present_proof/v1_1/message_types.py b/aries_cloudagent/protocols/present_proof/v1_1/message_types.py index 84b234dfc9..82680f6d72 100644 --- a/aries_cloudagent/protocols/present_proof/v1_1/message_types.py +++ b/aries_cloudagent/protocols/present_proof/v1_1/message_types.py @@ -5,8 +5,12 @@ REQUEST_PROOF = f"{PROTOCOL_URI}/request-proof" PRESENT_PROOF = f"{PROTOCOL_URI}/present-proof" +ACKNOWLEDGE_PROOF = f"{PROTOCOL_URI}/acknowledge-proof" MESSAGE_TYPES = { REQUEST_PROOF: (f"{PROTOCOL_PACKAGE}.messages.request_proof.RequestProof"), PRESENT_PROOF: (f"{PROTOCOL_PACKAGE}.messages.present_proof.PresentProof"), + ACKNOWLEDGE_PROOF: ( + f"{PROTOCOL_PACKAGE}.messages.acknowledge_proof.AcknowledgeProof" + ), } diff --git a/aries_cloudagent/protocols/present_proof/v1_1/messages/acknowledge_proof.py b/aries_cloudagent/protocols/present_proof/v1_1/messages/acknowledge_proof.py new file mode 100644 index 0000000000..4d73a9c55c --- /dev/null +++ b/aries_cloudagent/protocols/present_proof/v1_1/messages/acknowledge_proof.py @@ -0,0 +1,34 @@ +from marshmallow import fields +from .....messaging.agent_message import AgentMessage, AgentMessageSchema +from ..message_types import ACKNOWLEDGE_PROOF, PROTOCOL_PACKAGE + +HANDLER_CLASS = f"{PROTOCOL_PACKAGE}.handlers.acknowledge_proof.AcknowledgeProofHandler" + + +class AcknowledgeProof(AgentMessage): + class Meta: + handler_class = HANDLER_CLASS + schema_class = "AcknowledgeProofSchema" + message_type = ACKNOWLEDGE_PROOF + + def __init__( + self, + _id: str = None, + *, + credential=None, + **kwargs, + ): + """Initialize credential issue object.""" + super().__init__(_id=_id, **kwargs) + self.credential = credential + + +class AcknowledgeProofSchema(AgentMessageSchema): + """Credential schema.""" + + class Meta: + """Credential schema metadata.""" + + model_class = AcknowledgeProof + + credential = fields.Str(required=False) diff --git a/aries_cloudagent/protocols/present_proof/v1_1/messages/present_proof.py b/aries_cloudagent/protocols/present_proof/v1_1/messages/present_proof.py index f77fbd2c76..7234a2d2f2 100644 --- a/aries_cloudagent/protocols/present_proof/v1_1/messages/present_proof.py +++ b/aries_cloudagent/protocols/present_proof/v1_1/messages/present_proof.py @@ -1,4 +1,3 @@ -from typing import Sequence from marshmallow import fields from .....messaging.agent_message import AgentMessage, AgentMessageSchema from ..message_types import PRESENT_PROOF, PROTOCOL_PACKAGE @@ -17,11 +16,13 @@ def __init__( _id: str = None, *, credential_presentation=None, + prover_public_did=None, **kwargs, ): """Initialize credential issue object.""" super().__init__(_id=_id, **kwargs) self.credential_presentation = credential_presentation + self.prover_public_did = prover_public_did class PresentProofSchema(AgentMessageSchema): @@ -33,3 +34,4 @@ class Meta: model_class = PresentProof credential_presentation = fields.Str(required=True) + prover_public_did = fields.Str(required=True) diff --git a/aries_cloudagent/protocols/present_proof/v1_1/messages/request_proof.py b/aries_cloudagent/protocols/present_proof/v1_1/messages/request_proof.py index 584ae2116c..ea798828af 100644 --- a/aries_cloudagent/protocols/present_proof/v1_1/messages/request_proof.py +++ b/aries_cloudagent/protocols/present_proof/v1_1/messages/request_proof.py @@ -1,12 +1,9 @@ -from typing import Sequence from marshmallow import fields from .....messaging.agent_message import AgentMessage, AgentMessageSchema from ..message_types import REQUEST_PROOF, PROTOCOL_PACKAGE from aries_cloudagent.aathcf.credentials import ( PresentationRequestSchema, - PresentationRequestedAttributesSchema, ) -import uuid HANDLER_CLASS = f"{PROTOCOL_PACKAGE}.handlers.request_proof.RequestProofHandler" diff --git a/aries_cloudagent/protocols/present_proof/v1_1/models/presentation_exchange.py b/aries_cloudagent/protocols/present_proof/v1_1/models/presentation_exchange.py index 9a6a35d56e..15ef984617 100644 --- a/aries_cloudagent/protocols/present_proof/v1_1/models/presentation_exchange.py +++ b/aries_cloudagent/protocols/present_proof/v1_1/models/presentation_exchange.py @@ -34,14 +34,15 @@ class Meta: STATE_REQUEST_RECEIVED = "request_received" STATE_PRESENTATION_SENT = "presentation_sent" STATE_PRESENTATION_RECEIVED = "presentation_received" - STATE_VERIFIED = "verified" STATE_PRESENTATION_ACKED = "presentation_acked" + STATE_ACKNOWLEDGED = "presentation_acknowledged" def __init__( self, *, presentation_exchange_id: str = None, connection_id: str = None, + prover_public_did: str = None, thread_id: str = None, initiator: str = None, role: str = None, @@ -49,6 +50,7 @@ def __init__( presentation_proposal: dict = None, presentation_request: dict = None, presentation: dict = None, + acknowledgment_credential: str = None, verified: str = None, auto_present: bool = False, error_msg: str = None, @@ -64,6 +66,8 @@ def __init__( self.state = state self.presentation_proposal = presentation_proposal self.presentation_request = presentation_request + self.prover_public_did = prover_public_did + self.acknowledgment_credential = acknowledgment_credential self.presentation = presentation self.verified = verified self.auto_present = auto_present @@ -86,6 +90,8 @@ def record_value(self) -> dict: "initiator", "presentation_proposal", "presentation_request", + "acknowledgment_credential", + "prover_public_did", "presentation", "role", "state", @@ -171,7 +177,7 @@ class Meta: state = fields.Str( required=False, description="Present-proof exchange state", - example=THCFPresentationExchange.STATE_VERIFIED, + example=THCFPresentationExchange.STATE_ACKNOWLEDGED, ) presentation_proposal = fields.Dict( required=False, description="Serialized presentation proposal message" @@ -198,3 +204,5 @@ class Meta: error_msg = fields.Str( required=False, description="Error message", example="Invalid structure" ) + prover_public_did = fields.Str(required=False) + acknowledgment_credential = fields.Str(required=False) \ No newline at end of file diff --git a/aries_cloudagent/protocols/present_proof/v1_1/models/utils.py b/aries_cloudagent/protocols/present_proof/v1_1/models/utils.py index f2d5e6b543..9304252781 100644 --- a/aries_cloudagent/protocols/present_proof/v1_1/models/utils.py +++ b/aries_cloudagent/protocols/present_proof/v1_1/models/utils.py @@ -9,7 +9,7 @@ async def retrieve_exchange(context, record_id, exception): exchange_record: THCFPresentationExchange = ( await THCFPresentationExchange.retrieve_by_id(context, record_id) ) - except StorageNotFoundError as err: + except StorageNotFoundError: raise exception( reason=f"Couldnt find exchange_record through this id {record_id}" ) @@ -30,7 +30,8 @@ async def retrieve_exchange_by_thread(context, connection_id, thread_id, excepti raise exception( f"""Couldnt find exchange_record through this: connection id: {connection_id} - thread id: {thread_id}""" + thread id: {thread_id} + Exception: {err.roll_up}""" ) except StorageError as err: raise exception(err.roll_up) diff --git a/aries_cloudagent/protocols/present_proof/v1_1/routes.py b/aries_cloudagent/protocols/present_proof/v1_1/routes.py index 36e35fe883..d3cbad1f1b 100644 --- a/aries_cloudagent/protocols/present_proof/v1_1/routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_1/routes.py @@ -27,9 +27,15 @@ from aries_cloudagent.pdstorage_thcf.api import load_table from aries_cloudagent.holder.pds import CREDENTIALS_TABLE from aries_cloudagent.pdstorage_thcf.error import PDSError +from ...issue_credential.v1_1.utils import create_credential +from aries_cloudagent.protocols.issue_credential.v1_1.routes import ( + routes_get_public_did, +) LOG = logging.getLogger(__name__).info +from .messages.acknowledge_proof import AcknowledgeProof + class PresentationRequestAPISchema(OpenAPISchema): connection_id = fields.Str(required=True) @@ -51,6 +57,11 @@ class RetrieveExchangeQuerySchema(OpenAPISchema): state = fields.Str(required=False) +class AcknowledgeProofSchema(OpenAPISchema): + exchange_record_id = fields.Str(required=True) + decision = fields.Str(required=True) + + @docs(tags=["present-proof"], summary="Sends a proof presentation") @request_schema(PresentationRequestAPISchema()) async def request_presentation_api(request: web.BaseRequest): @@ -84,7 +95,16 @@ async def request_presentation_api(request: web.BaseRequest): LOG("exchange_record %s", exchange_record) await exchange_record.save(context) - return web.json_response({"thread_id": exchange_record.thread_id}) + + return web.json_response( + { + "success": True, + "message": "proof sent and exchange updated", + "exchange_id": exchange_record._id, + "thread_id": message._thread_id, + "connection_id": connection_id, + } + ) @docs(tags=["present-proof"], summary="Send a credential presentation") @@ -134,8 +154,10 @@ async def present_proof_api(request: web.BaseRequest): except HolderError as err: raise web.HTTPInternalServerError(reason=err.roll_up) - print("Presentation present proof api:::::", presentation) - message = PresentProof(credential_presentation=presentation) + public_did = await routes_get_public_did(context) + message = PresentProof( + credential_presentation=presentation, prover_public_did=public_did + ) message.assign_thread_id(exchange_record.thread_id) await outbound_handler(message, connection_id=connection_record.connection_id) @@ -145,7 +167,63 @@ async def present_proof_api(request: web.BaseRequest): ) await exchange_record.save(context) - return web.json_response("success, proof sent and exchange updated") + return web.json_response( + { + "success": True, + "message": "proof sent and exchange updated", + "exchange_id": exchange_record._id, + } + ) + + +@docs(tags=["present-proof"], summary="retrieve exchange record") +@request_schema(AcknowledgeProofSchema()) +async def acknowledge_proof(request: web.BaseRequest): + context = request.app["request_context"] + outbound_handler = request.app["outbound_message_router"] + body = await request.json() + + exchange_record: THCFPresentationExchange = await retrieve_exchange( + context, body.get("exchange_record_id"), web.HTTPNotFound + ) + + raise_exception_invalid_state( + exchange_record, + THCFPresentationExchange.STATE_PRESENTATION_RECEIVED, + THCFPresentationExchange.ROLE_VERIFIER, + web.HTTPBadRequest, + ) + + connection_record: ConnectionRecord = await retrieve_connection( + context, exchange_record.connection_id + ) + + credential = await create_credential( + context, + { + "credential_type": "ProofAcknowledgment", + "credential_values": { + "decision": body.get("decision"), + }, + }, + their_public_did=exchange_record.prover_public_did, + exception=web.HTTPError, + ) + + message = AcknowledgeProof(credential=credential) + message.assign_thread_id(exchange_record.thread_id) + await outbound_handler(message, connection_id=connection_record.connection_id) + + exchange_record.acknowledgment_credential = credential + exchange_record.state = exchange_record.STATE_ACKNOWLEDGED + await exchange_record.save(context) + return web.json_response( + { + "success": True, + "message": "ack sent and exchange record updated", + "exchange_record_id": exchange_record._id, + } + ) @docs(tags=["present-proof"], summary="retrieve exchange record") @@ -155,10 +233,6 @@ async def retrieve_credential_exchange_api(request: web.BaseRequest): records = await THCFPresentationExchange.query(context, tag_filter=request.query) - """ - Serialize the result into a json format - """ - result = [] for i in records: result.append(i.serialize()) @@ -247,6 +321,10 @@ async def register(app: web.Application): "/present-proof/present", present_proof_api, ), + web.post( + "/present-proof/acknowledge", + acknowledge_proof, + ), web.get( "/present-proof/exchange/record", retrieve_credential_exchange_api,