diff --git a/aries_cloudagent/config/argparse.py b/aries_cloudagent/config/argparse.py index cb6f4391c3..acfc1f0a52 100644 --- a/aries_cloudagent/config/argparse.py +++ b/aries_cloudagent/config/argparse.py @@ -1200,7 +1200,8 @@ def add_arguments(self, parser: ArgumentParser): "--preserve-exchange-records", action="store_true", env_var="ACAPY_PRESERVE_EXCHANGE_RECORDS", - help="Keep credential exchange records after exchange has completed.", + help="Keep credential and presentation exchange records after " + "exchange has completed.", ) parser.add_argument( "--emit-new-didcomm-prefix", diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py index 3cba79cb88..2877905ec9 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py @@ -295,6 +295,19 @@ class CredExIdMatchInfoSchema(OpenAPISchema): ) +class V10CredentialExchangeAutoRemoveRequestSchema(OpenAPISchema): + """Request Schema for overriding default preserve exchange records setting.""" + + auto_remove = fields.Bool( + description=( + "Whether to remove the credential exchange record on completion " + "(overrides --preserve-exchange-records configuration setting)" + ), + required=False, + default=False, + ) + + @docs( tags=["issue-credential v1.0"], summary="Fetch all credential exchange records", @@ -955,6 +968,7 @@ async def credential_exchange_send_bound_offer(request: web.BaseRequest): summary="Send issuer a credential request", ) @match_info_schema(CredExIdMatchInfoSchema()) +@request_schema(V10CredentialExchangeAutoRemoveRequestSchema()) @response_schema(V10CredentialExchangeSchema(), 200, description="") async def credential_exchange_send_request(request: web.BaseRequest): """ @@ -975,6 +989,14 @@ async def credential_exchange_send_request(request: web.BaseRequest): credential_exchange_id = request.match_info["cred_ex_id"] + try: + body = await request.json() or {} + auto_remove = body.get( + "auto_remove", not profile.settings.get("preserve_exchange_records") + ) + except JSONDecodeError: + auto_remove = not profile.settings.get("preserve_exchange_records") + cred_ex_record = None connection_record = None @@ -1013,6 +1035,9 @@ async def credential_exchange_send_request(request: web.BaseRequest): # Transform recipient key into did holder_did = default_did_from_verkey(oob_record.our_recipient_key) + # assign the auto_remove flag from above... + cred_ex_record.auto_remove = auto_remove + try: credential_manager = CredentialManager(profile) ( diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py index 6def74a0f6..550cc80f15 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py @@ -358,6 +358,14 @@ class V20CredRequestRequestSchema(OpenAPISchema): allow_none=True, example="did:key:ahsdkjahsdkjhaskjdhakjshdkajhsdkjahs", ) + auto_remove = fields.Bool( + description=( + "Whether to remove the credential exchange record on completion " + "(overrides --preserve-exchange-records configuration setting)" + ), + required=False, + default=False, + ) class V20CredIssueRequestSchema(OpenAPISchema): @@ -1258,8 +1266,12 @@ async def credential_exchange_send_bound_request(request: web.BaseRequest): try: body = await request.json() or {} holder_did = body.get("holder_did") + auto_remove = body.get( + "auto_remove", not profile.settings.get("preserve_exchange_records") + ) except JSONDecodeError: holder_did = None + auto_remove = not profile.settings.get("preserve_exchange_records") cred_ex_id = request.match_info["cred_ex_id"] @@ -1301,6 +1313,9 @@ async def credential_exchange_send_bound_request(request: web.BaseRequest): # Transform recipient key into did holder_did = default_did_from_verkey(oob_record.our_recipient_key) + # assign the auto_remove flag from above... + cred_ex_record.auto_remove = auto_remove + cred_manager = V20CredManager(profile) cred_ex_record, cred_request_message = await cred_manager.create_request( cred_ex_record, diff --git a/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py b/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py index 1736d22843..f3477a9351 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py @@ -96,6 +96,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): "debug.auto_respond_presentation_request" ), trace=(context.message._trace is not None), + auto_remove=not profile.settings.get("preserve_exchange_records"), ) presentation_exchange_record = await presentation_manager.receive_request( diff --git a/aries_cloudagent/protocols/present_proof/v1_0/manager.py b/aries_cloudagent/protocols/present_proof/v1_0/manager.py index 540db13966..a7e154bd2e 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/manager.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/manager.py @@ -50,6 +50,7 @@ async def create_exchange_for_proposal( connection_id: str, presentation_proposal_message: PresentationProposal, auto_present: bool = None, + auto_remove: bool = None, ): """ Create a presentation exchange record for input presentation proposal. @@ -60,11 +61,14 @@ async def create_exchange_for_proposal( to exchange record auto_present: whether to present proof upon receiving proof request (default to configuration setting) + auto_remove: whether to remove this presentation exchange upon completion Returns: Presentation exchange record, created """ + if auto_remove is None: + auto_remove = not self._profile.settings.get("preserve_exchange_records") presentation_exchange_record = V10PresentationExchange( connection_id=connection_id, thread_id=presentation_proposal_message._thread_id, @@ -74,6 +78,7 @@ async def create_exchange_for_proposal( presentation_proposal_dict=presentation_proposal_message, auto_present=auto_present, trace=(presentation_proposal_message._trace is not None), + auto_remove=auto_remove, ) async with self._profile.session() as session: await presentation_exchange_record.save( @@ -100,6 +105,7 @@ async def receive_proposal( state=V10PresentationExchange.STATE_PROPOSAL_RECEIVED, presentation_proposal_dict=message, trace=(message._trace is not None), + auto_remove=not self._profile.settings.get("preserve_exchange_records"), ) async with self._profile.session() as session: await presentation_exchange_record.save( @@ -170,6 +176,7 @@ async def create_exchange_for_request( connection_id: str, presentation_request_message: PresentationRequest, auto_verify: bool = None, + auto_remove: bool = None, ): """ Create a presentation exchange record for input presentation request. @@ -178,11 +185,14 @@ async def create_exchange_for_request( connection_id: connection identifier presentation_request_message: presentation request to use in creating exchange record, extracting indy proof request and thread id - + auto_verify: whether to auto-verify presentation exchange + auto_remove: whether to remove this presentation exchange upon completion Returns: Presentation exchange record, updated """ + if auto_remove is None: + auto_remove = not self._profile.settings.get("preserve_exchange_records") presentation_exchange_record = V10PresentationExchange( connection_id=connection_id, thread_id=presentation_request_message._thread_id, @@ -193,6 +203,7 @@ async def create_exchange_for_request( presentation_request_dict=presentation_request_message, auto_verify=auto_verify, trace=(presentation_request_message._trace is not None), + auto_remove=auto_remove, ) async with self._profile.session() as session: await presentation_exchange_record.save( @@ -491,6 +502,11 @@ async def send_presentation_ack( # connection_id can be none in case of connectionless connection_id=presentation_exchange_record.connection_id, ) + + # all done: delete + if presentation_exchange_record.auto_remove: + async with self._profile.session() as session: + await presentation_exchange_record.delete_record(session) else: LOGGER.warning( "Configuration has no BaseResponder: cannot ack presentation on %s", @@ -530,6 +546,11 @@ async def receive_presentation_ack( session, reason="receive presentation ack" ) + # all done: delete + if presentation_exchange_record.auto_remove: + async with self._profile.session() as session: + await presentation_exchange_record.delete_record(session) + return presentation_exchange_record async def receive_problem_report( diff --git a/aries_cloudagent/protocols/present_proof/v1_0/models/presentation_exchange.py b/aries_cloudagent/protocols/present_proof/v1_0/models/presentation_exchange.py index 98a49bed34..7880d54cca 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/models/presentation_exchange.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/models/presentation_exchange.py @@ -80,6 +80,7 @@ def __init__( auto_verify: bool = False, error_msg: str = None, trace: bool = False, # backward compat: BaseRecord.from_storage() + auto_remove: bool = False, **kwargs, ): """Initialize a new PresentationExchange.""" @@ -102,6 +103,7 @@ def __init__( self.auto_present = auto_present self.auto_verify = auto_verify self.error_msg = error_msg + self.auto_remove = auto_remove @property def presentation_exchange_id(self) -> str: @@ -240,6 +242,7 @@ def record_value(self) -> Mapping: "verified", "verified_msgs", "trace", + "auto_remove", ) }, **{ @@ -344,3 +347,11 @@ class Meta: error_msg = fields.Str( required=False, description="Error message", example="Invalid structure" ) + auto_remove = fields.Bool( + required=False, + default=True, + description=( + "Verifier choice to remove this presentation exchange record when complete" + ), + example=False, + ) diff --git a/aries_cloudagent/protocols/present_proof/v1_0/models/tests/test_record.py b/aries_cloudagent/protocols/present_proof/v1_0/models/tests/test_record.py index 13d9e5aac3..3e5a579401 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/models/tests/test_record.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/models/tests/test_record.py @@ -97,6 +97,7 @@ async def test_record(self): connection_id="conn_id", thread_id="thid", auto_present=True, + auto_remove=True, ) record.presentation_proposal_dict = presentation_proposal # cover setter record.presentation_request_dict = None # cover setter @@ -115,6 +116,7 @@ async def test_record(self): "verified": None, "verified_msgs": None, "trace": False, + "auto_remove": True, } bx_record = BasexRecordImpl() diff --git a/aries_cloudagent/protocols/present_proof/v1_0/routes.py b/aries_cloudagent/protocols/present_proof/v1_0/routes.py index f13d757cfd..1b4dd8bb5c 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/routes.py @@ -97,6 +97,19 @@ class V10PresentationExchangeListSchema(OpenAPISchema): ) +class V10PresentationSendRequestSchema(IndyPresSpecSchema): + """Request schema for sending a presentation.""" + + auto_remove = fields.Bool( + description=( + "Whether to remove the presentation exchange record on completion " + "(overrides --preserve-exchange-records configuration setting)" + ), + required=False, + default=False, + ) + + class V10PresentationProposalRequestSchema(AdminAPIMessageTracingSchema): """Request schema for sending a presentation proposal admin message.""" @@ -118,6 +131,14 @@ class V10PresentationProposalRequestSchema(AdminAPIMessageTracingSchema): required=False, default=False, ) + auto_remove = fields.Bool( + description=( + "Whether to remove the presentation exchange record on completion " + "(overrides --preserve-exchange-records configuration setting)" + ), + required=False, + default=False, + ) trace = fields.Bool( description="Whether to trace event (default false)", required=False, @@ -135,6 +156,14 @@ class V10PresentationCreateRequestRequestSchema(AdminAPIMessageTracingSchema): required=False, example=False, ) + auto_remove = fields.Bool( + description=( + "Whether to remove the presentation exchange record on completion " + "(overrides --preserve-exchange-records configuration setting)" + ), + required=False, + default=False, + ) trace = fields.Bool( description="Whether to trace event (default false)", required=False, @@ -160,6 +189,14 @@ class V10PresentationSendRequestToProposalSchema(AdminAPIMessageTracingSchema): required=False, example=False, ) + auto_remove = fields.Bool( + description=( + "Whether to remove the presentation exchange record on completion " + "(overrides --preserve-exchange-records configuration setting)" + ), + required=False, + default=False, + ) trace = fields.Bool( description="Whether to trace event (default false)", required=False, @@ -424,6 +461,7 @@ async def presentation_exchange_send_proposal(request: web.BaseRequest): auto_present = body.get( "auto_present", context.settings.get("debug.auto_respond_presentation_request") ) + auto_remove = body.get("auto_remove") presentation_manager = PresentationManager(profile) pres_ex_record = None @@ -432,6 +470,7 @@ async def presentation_exchange_send_proposal(request: web.BaseRequest): connection_id=connection_id, presentation_proposal_message=presentation_proposal_message, auto_present=auto_present, + auto_remove=auto_remove, ) result = pres_ex_record.serialize() except (BaseModelError, StorageError) as err: @@ -497,6 +536,7 @@ async def presentation_exchange_create_request(request: web.BaseRequest): auto_verify = body.get( "auto_verify", context.settings.get("debug.auto_verify_presentation") ) + auto_remove = body.get("auto_remove") trace_msg = body.get("trace") presentation_request_message.assign_trace_decorator( context.settings, @@ -510,6 +550,7 @@ async def presentation_exchange_create_request(request: web.BaseRequest): connection_id=None, presentation_request_message=presentation_request_message, auto_verify=auto_verify, + auto_remove=auto_remove, ) result = pres_ex_record.serialize() except (BaseModelError, StorageError) as err: @@ -586,6 +627,7 @@ async def presentation_exchange_send_free_request(request: web.BaseRequest): auto_verify = body.get( "auto_verify", context.settings.get("debug.auto_verify_presentation") ) + auto_remove = body.get("auto_remove") pres_ex_record = None try: @@ -594,6 +636,7 @@ async def presentation_exchange_send_free_request(request: web.BaseRequest): connection_id=connection_id, presentation_request_message=presentation_request_message, auto_verify=auto_verify, + auto_remove=auto_remove, ) result = pres_ex_record.serialize() except (BaseModelError, StorageError) as err: @@ -672,6 +715,8 @@ async def presentation_exchange_send_bound_request(request: web.BaseRequest): pres_ex_record.auto_verify = body.get( "auto_verify", context.settings.get("debug.auto_verify_presentation") ) + pres_ex_record.auto_remove = body.get("auto_remove") + try: presentation_manager = PresentationManager(profile) ( @@ -711,7 +756,7 @@ async def presentation_exchange_send_bound_request(request: web.BaseRequest): @docs(tags=["present-proof v1.0"], summary="Sends a proof presentation") @match_info_schema(V10PresExIdMatchInfoSchema()) -@request_schema(IndyPresSpecSchema()) +@request_schema(V10PresentationSendRequestSchema()) @response_schema(V10PresentationExchangeSchema(), description="") async def presentation_exchange_send_presentation(request: web.BaseRequest): """ @@ -750,6 +795,12 @@ async def presentation_exchange_send_presentation(request: web.BaseRequest): ) ) + auto_remove = body.get("auto_remove") + if auto_remove is None: + auto_remove = not profile.settings.get("preserve_exchange_records") + + pres_ex_record.auto_remove = auto_remove + # Fetch connection if exchange has record connection_record = None if pres_ex_record.connection_id: diff --git a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py index 044c21d317..97675469e4 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py @@ -365,7 +365,10 @@ async def test_create_exchange_for_proposal(self): PresentationProposal, "serialize", autospec=True ): exchange = await self.manager.create_exchange_for_proposal( - CONN_ID, proposal, auto_present=None + CONN_ID, + proposal, + auto_present=None, + auto_remove=True, ) save_ex.assert_called_once() @@ -373,6 +376,7 @@ async def test_create_exchange_for_proposal(self): assert exchange.initiator == V10PresentationExchange.INITIATOR_SELF assert exchange.role == V10PresentationExchange.ROLE_PROVER assert exchange.state == V10PresentationExchange.STATE_PROPOSAL_SENT + assert exchange.auto_remove == True async def test_receive_proposal(self): connection_record = async_mock.MagicMock(connection_id=CONN_ID) @@ -424,13 +428,18 @@ async def test_create_exchange_for_request(self): with async_mock.patch.object( V10PresentationExchange, "save", autospec=True ) as save_ex: - exchange = await self.manager.create_exchange_for_request(CONN_ID, pres_req) + exchange = await self.manager.create_exchange_for_request( + CONN_ID, + pres_req, + auto_remove=True, + ) save_ex.assert_called_once() assert exchange.thread_id == pres_req._thread_id assert exchange.initiator == V10PresentationExchange.INITIATOR_SELF assert exchange.role == V10PresentationExchange.ROLE_VERIFIER assert exchange.state == V10PresentationExchange.STATE_REQUEST_SENT + assert exchange.auto_remove == True async def test_receive_request(self): exchange_in = V10PresentationExchange() diff --git a/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py b/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py index 1d8abe88b6..4b0fc18f69 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py @@ -89,6 +89,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): "debug.auto_respond_presentation_request" ), trace=(context.message._trace is not None), + auto_remove=not profile.settings.get("preserve_exchange_records"), ) pres_ex_record = await pres_manager.receive_pres_request( diff --git a/aries_cloudagent/protocols/present_proof/v2_0/manager.py b/aries_cloudagent/protocols/present_proof/v2_0/manager.py index 5b4f2362ab..bd727bf744 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/manager.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/manager.py @@ -44,6 +44,7 @@ async def create_exchange_for_proposal( connection_id: str, pres_proposal_message: V20PresProposal, auto_present: bool = None, + auto_remove: bool = None, ): """ Create a presentation exchange record for input presentation proposal. @@ -54,11 +55,14 @@ async def create_exchange_for_proposal( to exchange record auto_present: whether to present proof upon receiving proof request (default to configuration setting) + auto_remove: whether to remove this presentation exchange upon completion Returns: Presentation exchange record, created """ + if auto_remove is None: + auto_remove = not self._profile.settings.get("preserve_exchange_records") pres_ex_record = V20PresExRecord( connection_id=connection_id, thread_id=pres_proposal_message._thread_id, @@ -68,6 +72,7 @@ async def create_exchange_for_proposal( pres_proposal=pres_proposal_message, auto_present=auto_present, trace=(pres_proposal_message._trace is not None), + auto_remove=auto_remove, ) async with self._profile.session() as session: @@ -95,6 +100,7 @@ async def receive_pres_proposal( state=V20PresExRecord.STATE_PROPOSAL_RECEIVED, pres_proposal=message, trace=(message._trace is not None), + auto_remove=not self._profile.settings.get("preserve_exchange_records"), ) async with self._profile.session() as session: @@ -165,6 +171,7 @@ async def create_exchange_for_request( connection_id: str, pres_request_message: V20PresRequest, auto_verify: bool = None, + auto_remove: bool = None, ): """ Create a presentation exchange record for input presentation request. @@ -173,11 +180,15 @@ async def create_exchange_for_request( connection_id: connection identifier pres_request_message: presentation request to use in creating exchange record, extracting indy proof request and thread id + auto_verify: whether to auto-verify presentation exchange + auto_remove: whether to remove this presentation exchange upon completion Returns: Presentation exchange record, updated """ + if auto_remove is None: + auto_remove = not self._profile.settings.get("preserve_exchange_records") pres_ex_record = V20PresExRecord( connection_id=connection_id, thread_id=pres_request_message._thread_id, @@ -187,6 +198,7 @@ async def create_exchange_for_request( pres_request=pres_request_message, auto_verify=auto_verify, trace=(pres_request_message._trace is not None), + auto_remove=auto_remove, ) async with self._profile.session() as session: await pres_ex_record.save( @@ -432,6 +444,11 @@ async def send_pres_ack( # connection_id can be none in case of connectionless connection_id=pres_ex_record.connection_id, ) + + # all done: delete + if pres_ex_record.auto_remove: + async with self._profile.session() as session: + await pres_ex_record.delete_record(session) else: LOGGER.warning( "Configuration has no BaseResponder: cannot ack presentation on %s", @@ -462,6 +479,11 @@ async def receive_pres_ack(self, message: V20PresAck, conn_record: ConnRecord): await pres_ex_record.save(session, reason="receive v2.0 presentation ack") + # all done: delete + if pres_ex_record.auto_remove: + async with self._profile.session() as session: + await pres_ex_record.delete_record(session) + return pres_ex_record async def receive_problem_report( diff --git a/aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py b/aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py index ec798b2803..a950f27636 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/models/pres_exchange.py @@ -69,6 +69,7 @@ def __init__( error_msg: str = None, trace: bool = False, # backward compat: BaseRecord.FromStorage() by_format: Mapping = None, # backward compat: BaseRecord.FromStorage() + auto_remove: bool = False, **kwargs, ): """Initialize a new PresExRecord.""" @@ -86,6 +87,7 @@ def __init__( self.auto_present = auto_present self.auto_verify = auto_verify self.error_msg = error_msg + self.auto_remove = auto_remove @property def pres_ex_id(self) -> str: @@ -226,6 +228,7 @@ def record_value(self) -> Mapping: "auto_verify", "error_msg", "trace", + "auto_remove", ) }, **{ @@ -344,3 +347,11 @@ class Meta: error_msg = fields.Str( required=False, description="Error message", example="Invalid structure" ) + auto_remove = fields.Bool( + required=False, + default=True, + description=( + "Verifier choice to remove this presentation exchange record when complete" + ), + example=False, + ) diff --git a/aries_cloudagent/protocols/present_proof/v2_0/models/tests/test_record.py b/aries_cloudagent/protocols/present_proof/v2_0/models/tests/test_record.py index c22a6ff23b..b307a4fa9b 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/models/tests/test_record.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/models/tests/test_record.py @@ -98,6 +98,7 @@ async def test_record(self): verified="false", auto_present=True, error_msg="error", + auto_remove=True, ) record.pres_proposal = pres_proposal # cover setter @@ -115,6 +116,7 @@ async def test_record(self): "auto_verify": False, "error_msg": "error", "trace": False, + "auto_remove": True, } bx_record = BasexRecordImpl() diff --git a/aries_cloudagent/protocols/present_proof/v2_0/routes.py b/aries_cloudagent/protocols/present_proof/v2_0/routes.py index 6915ff3ba3..cb214dce50 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/routes.py @@ -167,6 +167,14 @@ class V20PresProposalRequestSchema(AdminAPIMessageTracingSchema): required=False, default=False, ) + auto_remove = fields.Bool( + description=( + "Whether to remove the presentation exchange record on completion " + "(overrides --preserve-exchange-records configuration setting)" + ), + required=False, + default=False, + ) trace = fields.Bool( description="Whether to trace event (default false)", required=False, @@ -216,6 +224,14 @@ class V20PresCreateRequestRequestSchema(AdminAPIMessageTracingSchema): required=False, example=False, ) + auto_remove = fields.Bool( + description=( + "Whether to remove the presentation exchange record on completion " + "(overrides --preserve-exchange-records configuration setting)" + ), + required=False, + default=False, + ) trace = fields.Bool( description="Whether to trace event (default false)", required=False, @@ -239,6 +255,14 @@ class V20PresentationSendRequestToProposalSchema(AdminAPIMessageTracingSchema): required=False, example=False, ) + auto_remove = fields.Bool( + description=( + "Whether to remove the presentation exchange record on completion " + "(overrides --preserve-exchange-records configuration setting)" + ), + required=False, + default=False, + ) trace = fields.Bool( description="Whether to trace event (default false)", required=False, @@ -262,6 +286,14 @@ class V20PresSpecByFormatRequestSchema(AdminAPIMessageTracingSchema): "overrides the PresentionExchange record's PresRequest" ), ) + auto_remove = fields.Bool( + description=( + "Whether to remove the presentation exchange record on completion " + "(overrides --preserve-exchange-records configuration setting)" + ), + required=False, + default=False, + ) @validates_schema def validate_fields(self, data, **kwargs): @@ -777,6 +809,7 @@ async def present_proof_send_proposal(request: web.BaseRequest): auto_present = body.get( "auto_present", context.settings.get("debug.auto_respond_presentation_request") ) + auto_remove = body.get("auto_remove") pres_manager = V20PresManager(profile) pres_ex_record = None @@ -785,6 +818,7 @@ async def present_proof_send_proposal(request: web.BaseRequest): connection_id=connection_id, pres_proposal_message=pres_proposal_message, auto_present=auto_present, + auto_remove=auto_remove, ) result = pres_ex_record.serialize() except (BaseModelError, StorageError) as err: @@ -847,6 +881,7 @@ async def present_proof_create_request(request: web.BaseRequest): auto_verify = body.get( "auto_verify", context.settings.get("debug.auto_verify_presentation") ) + auto_remove = body.get("auto_remove") trace_msg = body.get("trace") pres_request_message.assign_trace_decorator( context.settings, @@ -860,6 +895,7 @@ async def present_proof_create_request(request: web.BaseRequest): connection_id=None, pres_request_message=pres_request_message, auto_verify=auto_verify, + auto_remove=auto_remove, ) result = pres_ex_record.serialize() except (BaseModelError, StorageError) as err: @@ -928,6 +964,7 @@ async def present_proof_send_free_request(request: web.BaseRequest): auto_verify = body.get( "auto_verify", context.settings.get("debug.auto_verify_presentation") ) + auto_remove = body.get("auto_remove") trace_msg = body.get("trace") pres_request_message.assign_trace_decorator( context.settings, @@ -941,6 +978,7 @@ async def present_proof_send_free_request(request: web.BaseRequest): connection_id=connection_id, pres_request_message=pres_request_message, auto_verify=auto_verify, + auto_remove=auto_remove, ) result = pres_ex_record.serialize() except (BaseModelError, StorageError) as err: @@ -1018,6 +1056,7 @@ async def present_proof_send_bound_request(request: web.BaseRequest): pres_ex_record.auto_verify = body.get( "auto_verify", context.settings.get("debug.auto_verify_presentation") ) + pres_ex_record.auto_remove = body.get("auto_remove") pres_manager = V20PresManager(profile) try: ( @@ -1104,6 +1143,12 @@ async def present_proof_send_presentation(request: web.BaseRequest): ) ) + auto_remove = body.get("auto_remove") + if auto_remove is None: + auto_remove = not profile.settings.get("preserve_exchange_records") + + pres_ex_record.auto_remove = auto_remove + # Fetch connection if exchange has record conn_record = None if pres_ex_record.connection_id: diff --git a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py index 14a22ce4d8..007af80786 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py @@ -533,7 +533,10 @@ async def test_create_exchange_for_proposal(self): V20PresProposal, "serialize", autospec=True ): px_rec = await self.manager.create_exchange_for_proposal( - CONN_ID, proposal, auto_present=None + CONN_ID, + proposal, + auto_present=None, + auto_remove=True, ) save_ex.assert_called_once() @@ -541,6 +544,7 @@ async def test_create_exchange_for_proposal(self): assert px_rec.initiator == V20PresExRecord.INITIATOR_SELF assert px_rec.role == V20PresExRecord.ROLE_PROVER assert px_rec.state == V20PresExRecord.STATE_PROPOSAL_SENT + assert px_rec.auto_remove == True async def test_receive_proposal(self): connection_record = async_mock.MagicMock(connection_id=CONN_ID) @@ -747,13 +751,18 @@ async def test_create_exchange_for_request(self): pres_req.assign_thread_id("dummy") with async_mock.patch.object(V20PresExRecord, "save", autospec=True) as save_ex: - px_rec = await self.manager.create_exchange_for_request(CONN_ID, pres_req) + px_rec = await self.manager.create_exchange_for_request( + CONN_ID, + pres_req, + auto_remove=True, + ) save_ex.assert_called_once() assert px_rec.thread_id == pres_req._thread_id assert px_rec.initiator == V20PresExRecord.INITIATOR_SELF assert px_rec.role == V20PresExRecord.ROLE_VERIFIER assert px_rec.state == V20PresExRecord.STATE_REQUEST_SENT + assert px_rec.auto_remove == True async def test_receive_pres_request(self): px_rec_in = V20PresExRecord()