diff --git a/aries_cloudagent/messaging/decorators/attach_decorator.py b/aries_cloudagent/messaging/decorators/attach_decorator.py index 7943727386..31d07fb1ca 100644 --- a/aries_cloudagent/messaging/decorators/attach_decorator.py +++ b/aries_cloudagent/messaging/decorators/attach_decorator.py @@ -571,6 +571,7 @@ def data_base64( cls, mapping: Mapping, *, + aip2_flag: bool = False, ident: str = None, description: str = None, filename: str = None, @@ -590,8 +591,13 @@ def data_base64( filename: optional attachment filename lastmod_time: optional attachment last modification time byte_count: optional attachment byte count + aip2_flag: whether attach base64url encoded data """ + if aip2_flag: + b64_attach = bytes_to_b64(json.dumps(mapping).encode(), urlsafe=True) + else: + b64_attach = bytes_to_b64(json.dumps(mapping).encode()) return AttachDecorator( ident=ident or str(uuid.uuid4()), description=description, @@ -599,9 +605,7 @@ def data_base64( mime_type="application/json", lastmod_time=lastmod_time, byte_count=byte_count, - data=AttachDecoratorData( - base64_=bytes_to_b64(json.dumps(mapping).encode()) - ), + data=AttachDecoratorData(base64_=b64_attach), ) @classmethod diff --git a/aries_cloudagent/protocols/didexchange/v1_0/manager.py b/aries_cloudagent/protocols/didexchange/v1_0/manager.py index b7a59a0d42..c7d99e54d0 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/manager.py @@ -272,7 +272,13 @@ async def create_request( ), ) pthid = conn_rec.invitation_msg_id or f"did:sov:{conn_rec.their_public_did}" - attach = AttachDecorator.data_base64(did_doc.serialize()) + # if self._session.profile.settings.get_value("aip_version", 1) >= 2: + if self._session.profile.settings.get_value( + "emit_new_didcomm_mime_type" + ) and self._session.profile.settings.get_value("emit_new_didcomm_prefix"): + attach = AttachDecorator.data_base64(did_doc.serialize(), aip2_flag=True) + else: + attach = AttachDecorator.data_base64(did_doc.serialize()) await attach.data.sign(my_info.verkey, wallet) if not my_label: my_label = self._session.settings.get("default_label") @@ -589,7 +595,13 @@ async def create_response( filter(None, [base_mediation_record, mediation_record]) ), ) - attach = AttachDecorator.data_base64(did_doc.serialize()) + # if self._session.profile.settings.get_value("aip_version", 1) >= 2: + if self._session.profile.settings.get_value( + "emit_new_didcomm_mime_type" + ) and self._session.profile.settings.get_value("emit_new_didcomm_prefix"): + attach = AttachDecorator.data_base64(did_doc.serialize(), aip2_flag=True) + else: + attach = AttachDecorator.data_base64(did_doc.serialize()) await attach.data.sign(conn_rec.invitation_key, wallet) response = DIDXResponse(did=my_info.did, did_doc_attach=attach) # Assign thread information diff --git a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py index a17a91f26d..2f97a4cc96 100644 --- a/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/didexchange/v1_0/tests/test_manager.py @@ -261,6 +261,34 @@ async def test_create_request(self): didx_req = await self.manager.create_request(mock_conn_rec) assert didx_req + async def test_create_request_aip2(self): + self.session.profile.settings["emit_new_didcomm_mime_type"] = True + self.session.profile.settings["emit_new_didcomm_prefix"] = True + mock_conn_rec = async_mock.MagicMock( + connection_id="dummy", + my_did=self.did_info.did, + their_did=TestConfig.test_target_did, + their_role=ConnRecord.Role.RESPONDER.rfc23, + state=ConnRecord.State.REQUEST.rfc23, + retrieve_invitation=async_mock.CoroutineMock( + return_value=async_mock.MagicMock( + service_blocks=None, + service_dids=[TestConfig.test_target_did], + ) + ), + save=async_mock.CoroutineMock(), + ) + + with async_mock.patch.object( + self.manager, "create_did_document", async_mock.CoroutineMock() + ) as mock_create_did_doc: + mock_create_did_doc.return_value = async_mock.MagicMock( + serialize=async_mock.MagicMock(return_value={}) + ) + + didx_req = await self.manager.create_request(mock_conn_rec) + assert didx_req + async def test_create_request_multitenant(self): self.context.update_settings( {"multitenant.enabled": True, "wallet.id": "test_wallet"} @@ -1345,6 +1373,37 @@ async def test_create_response(self): await self.manager.create_response(conn_rec, "http://10.20.30.40:5060/") + async def test_create_response_aip2(self): + self.session.profile.settings["emit_new_didcomm_mime_type"] = True + self.session.profile.settings["emit_new_didcomm_prefix"] = True + conn_rec = ConnRecord( + connection_id="dummy", state=ConnRecord.State.REQUEST.rfc23 + ) + + with async_mock.patch.object( + test_module.ConnRecord, "retrieve_request", async_mock.CoroutineMock() + ) as mock_retrieve_req, async_mock.patch.object( + conn_rec, "save", async_mock.CoroutineMock() + ) as mock_save, async_mock.patch.object( + test_module, "DIDDoc", autospec=True + ) as mock_did_doc, async_mock.patch.object( + test_module, "AttachDecorator", autospec=True + ) as mock_attach_deco, async_mock.patch.object( + test_module, "DIDXResponse", autospec=True + ) as mock_response, async_mock.patch.object( + self.manager, "create_did_document", async_mock.CoroutineMock() + ) as mock_create_did_doc: + mock_create_did_doc.return_value = async_mock.MagicMock( + serialize=async_mock.MagicMock() + ) + mock_attach_deco.data_base64 = async_mock.MagicMock( + return_value=async_mock.MagicMock( + data=async_mock.MagicMock(sign=async_mock.CoroutineMock()) + ) + ) + + await self.manager.create_response(conn_rec, "http://10.20.30.40:5060/") + async def test_create_response_mediation_id(self): mediation_record = MediationRecord( role=MediationRecord.ROLE_CLIENT, diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py index dd46efdf66..9bdc7244e6 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py @@ -287,7 +287,15 @@ async def _create(cred_def_id): credential_offer_message = CredentialOffer( comment=comment, credential_preview=credential_preview, - offers_attach=[CredentialOffer.wrap_indy_offer(credential_offer)], + offers_attach=[ + ( + CredentialOffer.wrap_indy_offer(credential_offer, aip2_flag=True) + # if self._profile.settings.get_value("aip_version", 1) >= 2 + if self._profile.settings.get_value("emit_new_didcomm_mime_type") + and self._profile.settings.get_value("emit_new_didcomm_prefix") + else CredentialOffer.wrap_indy_offer(credential_offer) + ) + ], ) credential_offer_message._thread = {"thid": cred_ex_record.thread_id} @@ -437,8 +445,16 @@ async def _create(): credential_request_message = CredentialRequest( requests_attach=[ - CredentialRequest.wrap_indy_cred_req( - cred_ex_record._credential_request.ser + ( + CredentialRequest.wrap_indy_cred_req( + cred_ex_record._credential_request.ser, aip2_flag=True + ) + # if self._profile.settings.get_value("aip_version", 1) >= 2 + if self._profile.settings.get_value("emit_new_didcomm_mime_type") + and self._profile.settings.get_value("emit_new_didcomm_prefix") + else CredentialRequest.wrap_indy_cred_req( + cred_ex_record._credential_request.ser + ) ) ] ) @@ -664,7 +680,17 @@ async def issue_credential( credential_message = CredentialIssue( comment=comment, credentials_attach=[ - CredentialIssue.wrap_indy_credential(cred_ex_record._credential.ser) + ( + CredentialIssue.wrap_indy_credential( + cred_ex_record._credential.ser, aip2_flag=True + ) + # if self._profile.settings.get_value("aip_version", 1) >= 2 + if self._profile.settings.get_value("emit_new_didcomm_mime_type") + and self._profile.settings.get_value("emit_new_didcomm_prefix") + else CredentialIssue.wrap_indy_credential( + cred_ex_record._credential.ser + ) + ) ], ) credential_message._thread = {"thid": cred_ex_record.thread_id} diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/messages/credential_issue.py b/aries_cloudagent/protocols/issue_credential/v1_0/messages/credential_issue.py index 23b83a82a6..5f6a74350d 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/messages/credential_issue.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/messages/credential_issue.py @@ -59,10 +59,14 @@ def indy_credential(self, index: int = 0): return self.credentials_attach[index].content @classmethod - def wrap_indy_credential(cls, indy_cred: dict) -> AttachDecorator: + def wrap_indy_credential( + cls, indy_cred: dict, aip2_flag: bool = False + ) -> AttachDecorator: """Convert an indy credential offer to an attachment decorator.""" return AttachDecorator.data_base64( - mapping=indy_cred, ident=ATTACH_DECO_IDS[CREDENTIAL_ISSUE] + mapping=indy_cred, + ident=ATTACH_DECO_IDS[CREDENTIAL_ISSUE], + aip2_flag=aip2_flag, ) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/messages/credential_offer.py b/aries_cloudagent/protocols/issue_credential/v1_0/messages/credential_offer.py index 84ec5d2f37..2eb0248705 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/messages/credential_offer.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/messages/credential_offer.py @@ -63,10 +63,14 @@ def indy_offer(self, index: int = 0) -> dict: return self.offers_attach[index].content @classmethod - def wrap_indy_offer(cls, indy_offer: dict) -> AttachDecorator: + def wrap_indy_offer( + cls, indy_offer: dict, aip2_flag: bool = False + ) -> AttachDecorator: """Convert an indy credential offer to an attachment decorator.""" return AttachDecorator.data_base64( - mapping=indy_offer, ident=ATTACH_DECO_IDS[CREDENTIAL_OFFER] + mapping=indy_offer, + ident=ATTACH_DECO_IDS[CREDENTIAL_OFFER], + aip2_flag=aip2_flag, ) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/messages/credential_request.py b/aries_cloudagent/protocols/issue_credential/v1_0/messages/credential_request.py index d15d1db551..3e76ffd462 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/messages/credential_request.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/messages/credential_request.py @@ -60,10 +60,14 @@ def indy_cred_req(self, index: int = 0): return self.requests_attach[index].content @classmethod - def wrap_indy_cred_req(cls, indy_cred_req: dict) -> AttachDecorator: + def wrap_indy_cred_req( + cls, indy_cred_req: dict, aip2_flag: bool = False + ) -> AttachDecorator: """Convert an indy credential request to an attachment decorator.""" return AttachDecorator.data_base64( - mapping=indy_cred_req, ident=ATTACH_DECO_IDS[CREDENTIAL_REQUEST] + mapping=indy_cred_req, + ident=ATTACH_DECO_IDS[CREDENTIAL_REQUEST], + aip2_flag=aip2_flag, ) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py index aa75042c84..0c300aca4f 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py @@ -326,6 +326,82 @@ async def test_create_free_offer(self): comment=comment, ) # once more to cover case where offer is available in cache + async def test_create_free_offer_aip2(self): + self.session.profile.settings["emit_new_didcomm_mime_type"] = True + self.session.profile.settings["emit_new_didcomm_prefix"] = True + connection_id = "test_conn_id" + comment = "comment" + schema_id_parts = SCHEMA_ID.split(":") + + preview = CredentialPreview( + attributes=( + CredAttrSpec(name="legalName", value="value"), + CredAttrSpec(name="jurisdictionId", value="value"), + CredAttrSpec(name="incorporationDate", value="value"), + ) + ) + proposal = CredentialProposal( + credential_proposal=preview, cred_def_id=CRED_DEF_ID, schema_id=None + ) + + exchange = V10CredentialExchange( + credential_exchange_id="dummy-cxid", + credential_definition_id=CRED_DEF_ID, + role=V10CredentialExchange.ROLE_ISSUER, + credential_proposal_dict=proposal.serialize(), + ) + + with async_mock.patch.object( + V10CredentialExchange, "save", autospec=True + ) as save_ex: + self.cache = InMemoryCache() + self.context.injector.bind_instance(BaseCache, self.cache) + + issuer = async_mock.MagicMock(IndyIssuer, autospec=True) + issuer.create_credential_offer = async_mock.CoroutineMock( + return_value=json.dumps(INDY_OFFER) + ) + self.context.injector.bind_instance(IndyIssuer, issuer) + + cred_def_record = StorageRecord( + CRED_DEF_SENT_RECORD_TYPE, + CRED_DEF_ID, + { + "schema_id": SCHEMA_ID, + "schema_issuer_did": schema_id_parts[0], + "schema_name": schema_id_parts[-2], + "schema_version": schema_id_parts[-1], + "issuer_did": TEST_DID, + "cred_def_id": CRED_DEF_ID, + "epoch": str(int(time())), + }, + ) + await self.session.storage.add_record(cred_def_record) + + (ret_exchange, ret_offer) = await self.manager.create_offer( + cred_ex_record=exchange, + counter_proposal=None, + comment=comment, + ) + assert ret_exchange is exchange + save_ex.assert_called_once() + + issuer.create_credential_offer.assert_called_once_with(CRED_DEF_ID) + + assert exchange.credential_exchange_id == ret_exchange._id # cover property + assert exchange.thread_id == ret_offer._thread_id + assert exchange.credential_definition_id == CRED_DEF_ID + assert exchange.role == V10CredentialExchange.ROLE_ISSUER + assert exchange.schema_id == SCHEMA_ID + assert exchange.state == V10CredentialExchange.STATE_OFFER_SENT + assert exchange._credential_offer.ser == INDY_OFFER + + (ret_exchange, ret_offer) = await self.manager.create_offer( + cred_ex_record=exchange, + counter_proposal=None, + comment=comment, + ) # once more to cover case where offer is available in cache + async def test_create_free_offer_attr_mismatch(self): connection_id = "test_conn_id" comment = "comment" @@ -642,6 +718,71 @@ async def test_create_request(self): assert ret_existing_exchange == ret_exchange assert ret_existing_request._thread_id == thread_id + async def test_create_request_aip2(self): + self.session.profile.settings["emit_new_didcomm_mime_type"] = True + self.session.profile.settings["emit_new_didcomm_prefix"] = True + connection_id = "test_conn_id" + thread_id = "thread-id" + holder_did = "did" + + stored_exchange = V10CredentialExchange( + credential_exchange_id="dummy-cxid", + connection_id=connection_id, + credential_definition_id=CRED_DEF_ID, + credential_offer=INDY_OFFER, + initiator=V10CredentialExchange.INITIATOR_SELF, + role=V10CredentialExchange.ROLE_HOLDER, + state=V10CredentialExchange.STATE_OFFER_RECEIVED, + schema_id=SCHEMA_ID, + thread_id=thread_id, + ) + + self.cache = InMemoryCache() + self.context.injector.bind_instance(BaseCache, self.cache) + + with async_mock.patch.object( + V10CredentialExchange, "save", autospec=True + ) as save_ex: + cred_def = {"cred": "def"} + self.ledger.get_credential_definition = async_mock.CoroutineMock( + return_value=cred_def + ) + + cred_req_meta = {} + holder = async_mock.MagicMock() + holder.create_credential_request = async_mock.CoroutineMock( + return_value=(json.dumps(INDY_CRED_REQ), json.dumps(cred_req_meta)) + ) + self.context.injector.bind_instance(IndyHolder, holder) + + ret_exchange, ret_request = await self.manager.create_request( + stored_exchange, holder_did + ) + + holder.create_credential_request.assert_called_once_with( + INDY_OFFER, cred_def, holder_did + ) + + assert ret_request.indy_cred_req() == INDY_CRED_REQ + assert ret_request._thread_id == thread_id + + assert ret_exchange.state == V10CredentialExchange.STATE_REQUEST_SENT + + # cover case with request in cache + stored_exchange.credential_request = None + stored_exchange.state = V10CredentialExchange.STATE_OFFER_RECEIVED + await self.manager.create_request(stored_exchange, holder_did) + + # cover case with existing cred req + stored_exchange.state = V10CredentialExchange.STATE_OFFER_RECEIVED + stored_exchange.credential_request = INDY_CRED_REQ + ( + ret_existing_exchange, + ret_existing_request, + ) = await self.manager.create_request(stored_exchange, holder_did) + assert ret_existing_exchange == ret_exchange + assert ret_existing_request._thread_id == thread_id + async def test_create_request_no_cache(self): connection_id = "test_conn_id" thread_id = "thread-id" @@ -822,6 +963,92 @@ async def test_issue_credential(self): assert ret_existing_exchange == ret_exchange assert ret_existing_cred._thread_id == thread_id + async def test_issue_credential_aip2(self): + self.session.profile.settings["emit_new_didcomm_mime_type"] = True + self.session.profile.settings["emit_new_didcomm_prefix"] = True + connection_id = "test_conn_id" + comment = "comment" + cred_values = {"attr": "value"} + thread_id = "thread-id" + + stored_exchange = V10CredentialExchange( + credential_exchange_id="dummy-cxid", + connection_id=connection_id, + credential_definition_id=CRED_DEF_ID, + credential_offer=INDY_OFFER, + credential_request=INDY_CRED_REQ, + credential_proposal_dict=CredentialProposal( + credential_proposal=CredentialPreview.deserialize( + {"attributes": [{"name": "attr", "value": "value"}]} + ), + cred_def_id=CRED_DEF_ID, + schema_id=SCHEMA_ID, + ).serialize(), + initiator=V10CredentialExchange.INITIATOR_SELF, + role=V10CredentialExchange.ROLE_ISSUER, + state=V10CredentialExchange.STATE_REQUEST_RECEIVED, + thread_id=thread_id, + ) + + issuer = async_mock.MagicMock() + cred = {"indy": "credential"} + cred_rev_id = "1000" + issuer.create_credential = async_mock.CoroutineMock( + return_value=(json.dumps(cred), cred_rev_id) + ) + self.context.injector.bind_instance(IndyIssuer, issuer) + + with async_mock.patch.object( + test_module, "IndyRevocation", autospec=True + ) as revoc, async_mock.patch.object( + asyncio, "ensure_future", autospec=True + ) as asyncio_mock, async_mock.patch.object( + V10CredentialExchange, "save", autospec=True + ) as save_ex: + revoc.return_value.get_active_issuer_rev_reg_record = async_mock.CoroutineMock( + return_value=async_mock.MagicMock( # active_rev_reg_rec + revoc_reg_id=REV_REG_ID, + get_registry=async_mock.CoroutineMock( + return_value=async_mock.MagicMock( # rev_reg + tails_local_path="dummy-path", + get_or_fetch_local_tails_path=async_mock.CoroutineMock(), + ) + ), + ) + ) + (ret_exchange, ret_cred_issue) = await self.manager.issue_credential( + stored_exchange, comment=comment, retries=1 + ) + + save_ex.assert_called_once() + + issuer.create_credential.assert_called_once_with( + SCHEMA, + INDY_OFFER, + INDY_CRED_REQ, + cred_values, + stored_exchange.credential_exchange_id, + REV_REG_ID, + "dummy-path", + ) + + assert ret_exchange._credential.ser == cred + assert ret_cred_issue.indy_credential() == cred + assert ret_exchange.state == V10CredentialExchange.STATE_ISSUED + assert ret_cred_issue._thread_id == thread_id + + # cover case with existing cred + stored_exchange.credential = cred + stored_exchange.state = V10CredentialExchange.STATE_REQUEST_RECEIVED + ( + ret_existing_exchange, + ret_existing_cred, + ) = await self.manager.issue_credential( + stored_exchange, comment=comment, retries=0 + ) + assert ret_existing_exchange == ret_exchange + assert ret_existing_cred._thread_id == thread_id + async def test_issue_credential_non_revocable(self): CRED_DEF_NR = deepcopy(CRED_DEF) CRED_DEF_NR["value"]["revocation"] = None diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/handler.py index 301110a047..8f232d4029 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/handler.py @@ -104,12 +104,22 @@ def get_format_data(self, message_type: str, data: dict) -> CredFormatAttachment CredFormatAttachment: Credential format and attachment data objects """ + # aip2_flag = (self.profile.settings.get("aip_version", 1) >= 2) + aip2_flag = self.profile.settings.get( + "emit_new_didcomm_mime_type" + ) and self.profile.settings.get("emit_new_didcomm_prefix") return ( V20CredFormat( attach_id=self.format.api, format_=self.get_format_identifier(message_type), ), - AttachDecorator.data_base64(data, ident=self.format.api), + ( + AttachDecorator.data_base64( + data, ident=self.format.api, aip2_flag=aip2_flag + ) + if aip2_flag + else AttachDecorator.data_base64(data, ident=self.format.api) + ), ) @abstractclassmethod diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index a85c976072..1792a73c97 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -102,7 +102,6 @@ async def create_proposal( """Create indy credential proposal.""" if proposal_data is None: proposal_data = {} - return self.get_format_data(CRED_20_PROPOSAL, proposal_data) async def receive_proposal( diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py index 032b970c02..4d12b6885b 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py @@ -357,7 +357,7 @@ class V20CredExIdMatchInfoSchema(OpenAPISchema): ) -def _formats_filters(filt_spec: Mapping) -> Mapping: +def _formats_filters(filt_spec: Mapping, aip2_flag: bool = False) -> Mapping: """Break out formats and filters for v2.0 cred proposal messages.""" return ( @@ -370,7 +370,13 @@ def _formats_filters(filt_spec: Mapping) -> Mapping: for fmt_api in filt_spec ], "filters_attach": [ - AttachDecorator.data_base64(filt_by_fmt, ident=fmt_api) + ( + AttachDecorator.data_base64( + filt_by_fmt, ident=fmt_api, aip2_flag=aip2_flag + ) + if aip2_flag + else AttachDecorator.data_base64(filt_by_fmt, ident=fmt_api) + ) for (fmt_api, filt_by_fmt) in filt_spec.items() ], } @@ -517,6 +523,10 @@ async def credential_exchange_create(request: web.BaseRequest): if not filt_spec: raise web.HTTPBadRequest(reason="Missing filter") trace_msg = body.get("trace") + # aip2_flag = (context.profile.settings.get_value("aip_version", 1) >= 2) + aip2_flag = context.profile.settings.get_value( + "emit_new_didcomm_mime_type" + ) and context.profile.settings.get_value("emit_new_didcomm_prefix") try: # Not all formats use credential preview @@ -526,7 +536,7 @@ async def credential_exchange_create(request: web.BaseRequest): cred_proposal = V20CredProposal( comment=comment, credential_preview=cred_preview, - **_formats_filters(filt_spec), + **_formats_filters(filt_spec, aip2_flag=aip2_flag), ) cred_proposal.assign_trace_decorator( context.settings, @@ -594,6 +604,10 @@ async def credential_exchange_send(request: web.BaseRequest): preview_spec = body.get("credential_preview") auto_remove = body.get("auto_remove") trace_msg = body.get("trace") + # aip2_flag = (context.profile.settings.get_value("aip_version", 1) >= 2) + aip2_flag = context.profile.settings.get_value( + "emit_new_didcomm_mime_type" + ) and context.profile.settings.get_value("emit_new_didcomm_prefix") conn_record = None cred_ex_record = None @@ -612,7 +626,7 @@ async def credential_exchange_send(request: web.BaseRequest): cred_proposal = V20CredProposal( comment=comment, credential_preview=cred_preview, - **_formats_filters(filt_spec), + **_formats_filters(filt_spec, aip2_flag=aip2_flag), ) cred_proposal.assign_trace_decorator( context.settings, @@ -758,10 +772,14 @@ async def _create_free_offer( """Create a credential offer and related exchange record.""" cred_preview = V20CredPreview.deserialize(preview_spec) if preview_spec else None + # aip2_flag = (profile.settings.get_value("aip_version", 1) >= 2) + aip2_flag = profile.settings.get_value( + "emit_new_didcomm_mime_type" + ) and profile.settings.get_value("emit_new_didcomm_prefix") cred_proposal = V20CredProposal( comment=comment, credential_preview=cred_preview, - **_formats_filters(filt_spec), + **_formats_filters(filt_spec, aip2_flag=aip2_flag), ) cred_proposal.assign_trace_decorator( profile.settings, @@ -1008,6 +1026,11 @@ async def credential_exchange_send_bound_offer(request: web.BaseRequest): preview_spec = body.get("counter_preview") cred_ex_id = request.match_info["cred_ex_id"] + # aip2_flag = (context.profile.settings.get_value("aip_version", 1) >= 2) + aip2_flag = context.profile.settings.get_value( + "emit_new_didcomm_mime_type" + ) and context.profile.settings.get_value("emit_new_didcomm_prefix") + cred_ex_record = None conn_record = None try: @@ -1040,7 +1063,7 @@ async def credential_exchange_send_bound_offer(request: web.BaseRequest): counter_proposal=V20CredProposal( comment=None, credential_preview=(V20CredPreview.deserialize(preview_spec)), - **_formats_filters(filt_spec), + **_formats_filters(filt_spec, aip2_flag=aip2_flag), ) if preview_spec else None, @@ -1113,6 +1136,10 @@ async def credential_exchange_send_free_request(request: web.BaseRequest): raise web.HTTPBadRequest(reason="Missing filter") auto_remove = body.get("auto_remove") trace_msg = body.get("trace") + # aip2_flag = (context.profile.settings.get_value("aip_version", 1) >= 2) + aip2_flag = context.profile.settings.get_value( + "emit_new_didcomm_mime_type" + ) and context.profile.settings.get_value("emit_new_didcomm_prefix") conn_record = None cred_ex_record = None @@ -1131,7 +1158,7 @@ async def credential_exchange_send_free_request(request: web.BaseRequest): cred_proposal = V20CredProposal( comment=comment, - **_formats_filters(filt_spec), + **_formats_filters(filt_spec, aip2_flag=aip2_flag), ) cred_ex_record = V20CredExRecord( diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_routes.py index 8268bccc6c..fa78b095b9 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_routes.py @@ -273,6 +273,38 @@ async def test_credential_exchange_create(self): mock_response.assert_called_once_with(mock_cx_rec.serialize.return_value) + async def test_credential_exchange_create_aip2(self): + self.request.json = async_mock.CoroutineMock() + self.context.profile.settings.set_value("emit_new_didcomm_mime_type", True) + self.context.profile.settings.set_value("emit_new_didcomm_prefix", True) + with async_mock.patch.object( + test_module, "ConnRecord", autospec=True + ) as mock_connection_record, async_mock.patch.object( + test_module, "V20CredManager", autospec=True + ) as mock_cred_mgr, async_mock.patch.object( + test_module.V20CredPreview, "deserialize", autospec=True + ) as mock_cred_preview_deser, async_mock.patch.object( + test_module.web, "json_response" + ) as mock_response: + mock_cred_mgr.return_value.create_offer = async_mock.CoroutineMock() + + mock_cred_mgr.return_value.create_offer.return_value = ( + async_mock.CoroutineMock(), + async_mock.CoroutineMock(), + ) + + mock_cx_rec = async_mock.MagicMock() + mock_cred_offer = async_mock.MagicMock() + + mock_cred_mgr.return_value.prepare_send.return_value = ( + mock_cx_rec, + mock_cred_offer, + ) + + await test_module.credential_exchange_create(self.request) + + mock_response.assert_called_once_with(mock_cx_rec.serialize.return_value) + async def test_credential_exchange_create_x(self): self.request.json = async_mock.CoroutineMock() @@ -346,6 +378,38 @@ async def test_credential_exchange_send(self): mock_response.assert_called_once_with(mock_cx_rec.serialize.return_value) + async def test_credential_exchange_send_aip2(self): + self.request.json = async_mock.CoroutineMock() + self.context.profile.settings.set_value("emit_new_didcomm_mime_type", True) + self.context.profile.settings.set_value("emit_new_didcomm_prefix", True) + with async_mock.patch.object( + test_module, "ConnRecord", autospec=True + ) as mock_conn_rec, async_mock.patch.object( + test_module, "V20CredManager", autospec=True + ) as mock_cred_mgr, async_mock.patch.object( + test_module.V20CredPreview, "deserialize", autospec=True + ) as mock_cred_preview_deser, async_mock.patch.object( + test_module.web, "json_response" + ) as mock_response: + mock_cred_mgr.return_value.create_offer = async_mock.CoroutineMock() + + mock_cred_mgr.return_value.create_offer.return_value = ( + async_mock.CoroutineMock(), + async_mock.CoroutineMock(), + ) + + mock_cx_rec = async_mock.MagicMock() + mock_cred_offer = async_mock.MagicMock() + + mock_cred_mgr.return_value.prepare_send.return_value = ( + mock_cx_rec, + mock_cred_offer, + ) + + await test_module.credential_exchange_send(self.request) + + mock_response.assert_called_once_with(mock_cx_rec.serialize.return_value) + async def test_credential_exchange_send_no_conn_record(self): connection_id = "connection-id" preview_spec = {"attributes": [{"name": "attr", "value": "value"}]} @@ -827,6 +891,40 @@ async def test_credential_exchange_send_free_offer(self): mock_response.assert_called_once_with(mock_cx_rec.serialize.return_value) + async def test_credential_exchange_send_free_offer_aip2(self): + self.request.json = async_mock.CoroutineMock( + return_value={ + "auto_issue": False, + "credential_preview": { + "attributes": [{"name": "hello", "value": "world"}] + }, + "filter": {"indy": {"schema_version": "1.0"}}, + } + ) + self.context.profile.settings.set_value("emit_new_didcomm_mime_type", True) + self.context.profile.settings.set_value("emit_new_didcomm_prefix", True) + + with async_mock.patch.object( + test_module, "ConnRecord", autospec=True + ) as mock_conn_rec, async_mock.patch.object( + test_module, "V20CredManager", autospec=True + ) as mock_cred_mgr, async_mock.patch.object( + test_module.web, "json_response" + ) as mock_response: + + mock_cred_mgr.return_value.create_offer = async_mock.CoroutineMock() + + mock_cx_rec = async_mock.MagicMock() + + mock_cred_mgr.return_value.create_offer.return_value = ( + mock_cx_rec, + async_mock.MagicMock(), + ) + + await test_module.credential_exchange_send_free_offer(self.request) + + mock_response.assert_called_once_with(mock_cx_rec.serialize.return_value) + async def test_credential_exchange_send_free_offer_no_filter(self): self.request.json = async_mock.CoroutineMock( return_value={ @@ -934,6 +1032,39 @@ async def test_credential_exchange_send_bound_offer(self): mock_response.assert_called_once_with(mock_cx_rec.serialize.return_value) + async def test_credential_exchange_send_bound_offer_aip2(self): + self.request.json = async_mock.CoroutineMock(return_value={}) + self.request.match_info = {"cred_ex_id": "dummy"} + self.context.profile.settings.set_value("emit_new_didcomm_mime_type", True) + self.context.profile.settings.set_value("emit_new_didcomm_prefix", True) + with async_mock.patch.object( + test_module, "ConnRecord", autospec=True + ) as mock_conn_rec, async_mock.patch.object( + test_module, "V20CredManager", autospec=True + ) as mock_cred_mgr, async_mock.patch.object( + test_module, "V20CredExRecord", autospec=True + ) as mock_cls_cx_rec, async_mock.patch.object( + test_module.web, "json_response" + ) as mock_response: + + mock_cls_cx_rec.retrieve_by_id = async_mock.CoroutineMock() + mock_cls_cx_rec.retrieve_by_id.return_value.state = ( + test_module.V20CredExRecord.STATE_PROPOSAL_RECEIVED + ) + + mock_cred_mgr.return_value.create_offer = async_mock.CoroutineMock() + + mock_cx_rec = async_mock.MagicMock() + + mock_cred_mgr.return_value.create_offer.return_value = ( + mock_cx_rec, + async_mock.MagicMock(), + ) + + await test_module.credential_exchange_send_bound_offer(self.request) + + mock_response.assert_called_once_with(mock_cx_rec.serialize.return_value) + async def test_credential_exchange_send_bound_offer_bad_cred_ex_id(self): self.request.json = async_mock.CoroutineMock(return_value={}) self.request.match_info = {"cred_ex_id": "dummy"} @@ -1170,6 +1301,36 @@ async def test_credential_exchange_send_free_request(self): mock_response.assert_called_once_with(mock_cx_rec.serialize.return_value) + async def test_credential_exchange_send_free_request_aip2(self): + self.context.profile.settings.set_value("emit_new_didcomm_mime_type", True) + self.context.profile.settings.set_value("emit_new_didcomm_prefix", True) + self.request.json = async_mock.CoroutineMock( + return_value={ + "filter": {"ld_proof": LD_PROOF_VC_DETAIL}, + } + ) + + with async_mock.patch.object( + test_module, "ConnRecord", autospec=True + ) as mock_conn_rec, async_mock.patch.object( + test_module, "V20CredManager", autospec=True + ) as mock_cred_mgr, async_mock.patch.object( + test_module.web, "json_response" + ) as mock_response: + + mock_cred_mgr.return_value.create_request = async_mock.CoroutineMock() + + mock_cx_rec = async_mock.MagicMock() + + mock_cred_mgr.return_value.create_request.return_value = ( + mock_cx_rec, + async_mock.MagicMock(), + ) + + await test_module.credential_exchange_send_free_request(self.request) + + mock_response.assert_called_once_with(mock_cx_rec.serialize.return_value) + async def test_credential_exchange_send_free_request_no_filter(self): self.request.json = async_mock.CoroutineMock( return_value={"comment": "comment"} diff --git a/aries_cloudagent/protocols/present_proof/v1_0/manager.py b/aries_cloudagent/protocols/present_proof/v1_0/manager.py index 351927267f..513c7028e6 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/manager.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/manager.py @@ -144,9 +144,21 @@ async def create_bound_request( presentation_request_message = PresentationRequest( comment=comment, request_presentations_attach=[ - AttachDecorator.data_base64( - mapping=indy_proof_request, - ident=ATTACH_DECO_IDS[PRESENTATION_REQUEST], + ( + AttachDecorator.data_base64( + mapping=indy_proof_request, + ident=ATTACH_DECO_IDS[PRESENTATION_REQUEST], + aip2_flag=True, + ) + # if self._profile.settings.get_value("aip_version", 1) >= 2 + if ( + self._profile.settings.get_value("emit_new_didcomm_mime_type") + and self._profile.settings.get_value("emit_new_didcomm_prefix") + ) + else AttachDecorator.data_base64( + mapping=indy_proof_request, + ident=ATTACH_DECO_IDS[PRESENTATION_REQUEST], + ) ) ], ) @@ -425,8 +437,20 @@ async def create_presentation( presentation_message = Presentation( comment=comment, presentations_attach=[ - AttachDecorator.data_base64( - mapping=indy_proof, ident=ATTACH_DECO_IDS[PRESENTATION] + ( + AttachDecorator.data_base64( + mapping=indy_proof, + ident=ATTACH_DECO_IDS[PRESENTATION], + aip2_flag=True, + ) + # if self._profile.settings.get_value("aip_version", 1) >= 2 + if ( + self._profile.settings.get_value("emit_new_didcomm_mime_type") + and self._profile.settings.get_value("emit_new_didcomm_prefix") + ) + else AttachDecorator.data_base64( + mapping=indy_proof, ident=ATTACH_DECO_IDS[PRESENTATION] + ) ) ], ) diff --git a/aries_cloudagent/protocols/present_proof/v1_0/routes.py b/aries_cloudagent/protocols/present_proof/v1_0/routes.py index 14244c082c..bbfdfc03ca 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/routes.py @@ -460,15 +460,27 @@ async def presentation_exchange_create_request(request: web.BaseRequest): comment = body.get("comment") indy_proof_request = body.get("proof_request") + # aip2_flag = (context.profile.settings.get_value("aip_version", 1) >= 2) + aip2_flag = context.profile.settings.get_value( + "emit_new_didcomm_mime_type" + ) and context.profile.settings.get_value("emit_new_didcomm_prefix") if not indy_proof_request.get("nonce"): indy_proof_request["nonce"] = await generate_pr_nonce() presentation_request_message = PresentationRequest( comment=comment, request_presentations_attach=[ - AttachDecorator.data_base64( - mapping=indy_proof_request, - ident=ATTACH_DECO_IDS[PRESENTATION_REQUEST], + ( + AttachDecorator.data_base64( + mapping=indy_proof_request, + ident=ATTACH_DECO_IDS[PRESENTATION_REQUEST], + aip2_flag=aip2_flag, + ) + if aip2_flag + else AttachDecorator.data_base64( + mapping=indy_proof_request, + ident=ATTACH_DECO_IDS[PRESENTATION_REQUEST], + ) ) ], ) @@ -543,15 +555,27 @@ async def presentation_exchange_send_free_request(request: web.BaseRequest): comment = body.get("comment") indy_proof_request = body.get("proof_request") + # aip2_flag = (context.profile.settings.get_value("aip_version", 1) >= 2) + aip2_flag = context.profile.settings.get_value( + "emit_new_didcomm_mime_type" + ) and context.profile.settings.get_value("emit_new_didcomm_prefix") if not indy_proof_request.get("nonce"): indy_proof_request["nonce"] = await generate_pr_nonce() presentation_request_message = PresentationRequest( comment=comment, request_presentations_attach=[ - AttachDecorator.data_base64( - mapping=indy_proof_request, - ident=ATTACH_DECO_IDS[PRESENTATION_REQUEST], + ( + AttachDecorator.data_base64( + mapping=indy_proof_request, + ident=ATTACH_DECO_IDS[PRESENTATION_REQUEST], + aip2_flag=aip2_flag, + ) + if aip2_flag + else AttachDecorator.data_base64( + mapping=indy_proof_request, + ident=ATTACH_DECO_IDS[PRESENTATION_REQUEST], + ) ) ], ) 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 2d3cb895ed..94896db9a3 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 @@ -389,6 +389,27 @@ async def test_create_bound_request(self): assert ret_exchange is exchange exchange.save.assert_called_once() + async def test_create_bound_request_aip2(self): + self.profile.settings["emit_new_didcomm_mime_type"] = True + self.profile.settings["emit_new_didcomm_prefix"] = True + + comment = "comment" + proposal = PresentationProposal(presentation_proposal=PRES_PREVIEW) + exchange = V10PresentationExchange( + presentation_proposal_dict=proposal.serialize(), + role=V10PresentationExchange.ROLE_VERIFIER, + ) + exchange.save = async_mock.CoroutineMock() + (ret_exchange, pres_req_msg) = await self.manager.create_bound_request( + presentation_exchange_record=exchange, + name=PROOF_REQ_NAME, + version=PROOF_REQ_VERSION, + nonce=PROOF_REQ_NONCE, + comment=comment, + ) + assert ret_exchange is exchange + exchange.save.assert_called_once() + async def test_create_exchange_for_request(self): indy_proof_req = await PRES_PREVIEW.indy_proof_request( name=PROOF_REQ_NAME, @@ -469,6 +490,51 @@ async def test_create_presentation(self): save_ex.assert_called_once() assert exchange_out.state == V10PresentationExchange.STATE_PRESENTATION_SENT + async def test_create_presentation_aip2(self): + self.profile.settings["emit_new_didcomm_mime_type"] = True + self.profile.settings["emit_new_didcomm_prefix"] = True + + exchange_in = V10PresentationExchange() + indy_proof_req = await PRES_PREVIEW.indy_proof_request( + name=PROOF_REQ_NAME, + version=PROOF_REQ_VERSION, + nonce=PROOF_REQ_NONCE, + ledger=self.ledger, + ) + + exchange_in.presentation_request = indy_proof_req + + more_magic_rr = async_mock.MagicMock( + get_or_fetch_local_tails_path=async_mock.CoroutineMock( + return_value="/tmp/sample/tails/path" + ) + ) + with async_mock.patch.object( + V10PresentationExchange, "save", autospec=True + ) as save_ex, async_mock.patch.object( + test_module, "AttachDecorator", autospec=True + ) as mock_attach_decorator, async_mock.patch.object( + test_module, "RevocationRegistry", autospec=True + ) as mock_rr: + mock_rr.from_definition = async_mock.MagicMock(return_value=more_magic_rr) + + mock_attach_decorator.data_base64 = async_mock.MagicMock( + return_value=mock_attach_decorator + ) + + req_creds = await indy_proof_req_preview2indy_requested_creds( + indy_proof_req, holder=self.holder + ) + assert not req_creds["self_attested_attributes"] + assert len(req_creds["requested_attributes"]) == 2 + assert len(req_creds["requested_predicates"]) == 1 + + (exchange_out, pres_msg) = await self.manager.create_presentation( + exchange_in, req_creds + ) + save_ex.assert_called_once() + assert exchange_out.state == V10PresentationExchange.STATE_PRESENTATION_SENT + async def test_create_presentation_proof_req_non_revoc_interval_none(self): exchange_in = V10PresentationExchange() indy_proof_req = await PRES_PREVIEW.indy_proof_request( diff --git a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py index 28a52ac611..fd33d689b6 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py @@ -476,6 +476,62 @@ async def test_presentation_exchange_create_request(self): mock_presentation_exchange.serialize.return_value ) + async def test_presentation_exchange_create_request_aip2(self): + self.profile.settings.set_value("emit_new_didcomm_mime_type", True) + self.profile.settings.set_value("emit_new_didcomm_prefix", True) + self.request.json = async_mock.CoroutineMock( + return_value={"comment": "dummy", "proof_request": {}} + ) + + with async_mock.patch( + "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", + autospec=True, + ) as mock_presentation_manager, async_mock.patch( + "aries_cloudagent.indy.sdk.models.pres_preview.IndyPresPreview", + autospec=True, + ) as mock_preview, async_mock.patch.object( + test_module, "PresentationRequest", autospec=True + ) as mock_presentation_request, async_mock.patch( + "aries_cloudagent.messaging.decorators.attach_decorator.AttachDecorator", + autospec=True, + ) as mock_attach_decorator, async_mock.patch( + ( + "aries_cloudagent.protocols.present_proof.v1_0." + "models.presentation_exchange.V10PresentationExchange" + ), + autospec=True, + ) as mock_presentation_exchange, async_mock.patch( + "aries_cloudagent.indy.util.generate_pr_nonce", + autospec=True, + ) as mock_generate_nonce: + + # Since we are mocking import + importlib.reload(test_module) + + mock_generate_nonce = async_mock.CoroutineMock() + + mock_attach_decorator.data_base64 = async_mock.MagicMock( + return_value=mock_attach_decorator + ) + mock_presentation_exchange.serialize = async_mock.MagicMock() + mock_presentation_exchange.serialize.return_value = { + "thread_id": "sample-thread-id" + } + mock_mgr = async_mock.MagicMock( + create_exchange_for_request=async_mock.CoroutineMock( + return_value=mock_presentation_exchange + ) + ) + mock_presentation_manager.return_value = mock_mgr + + with async_mock.patch.object( + test_module.web, "json_response" + ) as mock_response: + await test_module.presentation_exchange_create_request(self.request) + mock_response.assert_called_once_with( + mock_presentation_exchange.serialize.return_value + ) + async def test_presentation_exchange_create_request_x(self): self.request.json = async_mock.CoroutineMock( return_value={"comment": "dummy", "proof_request": {}} @@ -583,6 +639,71 @@ async def test_presentation_exchange_send_free_request(self): mock_presentation_exchange.serialize.return_value ) + async def test_presentation_exchange_send_free_request_aip2(self): + self.profile.settings.set_value("emit_new_didcomm_mime_type", True) + self.profile.settings.set_value("emit_new_didcomm_prefix", True) + self.request.json = async_mock.CoroutineMock( + return_value={ + "connection_id": "dummy", + "comment": "dummy", + "proof_request": {}, + } + ) + + with async_mock.patch( + "aries_cloudagent.connections.models.conn_record.ConnRecord", + autospec=True, + ) as mock_connection_record, async_mock.patch( + "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", + autospec=True, + ) as mock_presentation_manager, async_mock.patch( + "aries_cloudagent.indy.util.generate_pr_nonce", + autospec=True, + ) as mock_generate_nonce, async_mock.patch( + "aries_cloudagent.indy.sdk.models.pres_preview.IndyPresPreview", + autospec=True, + ) as mock_preview, async_mock.patch.object( + test_module, "PresentationRequest", autospec=True + ) as mock_presentation_request, async_mock.patch( + "aries_cloudagent.messaging.decorators.attach_decorator.AttachDecorator", + autospec=True, + ) as mock_attach_decorator, async_mock.patch( + ( + "aries_cloudagent.protocols.present_proof.v1_0." + "models.presentation_exchange.V10PresentationExchange" + ), + autospec=True, + ) as mock_presentation_exchange: + + # Since we are mocking import + importlib.reload(test_module) + + mock_connection_record.retrieve_by_id = async_mock.CoroutineMock( + return_value=mock_connection_record + ) + mock_attach_decorator.data_base64 = async_mock.MagicMock( + return_value=mock_attach_decorator + ) + mock_presentation_exchange.serialize = async_mock.MagicMock() + mock_presentation_exchange.serialize.return_value = { + "thread_id": "sample-thread-id" + } + + mock_mgr = async_mock.MagicMock( + create_exchange_for_request=async_mock.CoroutineMock( + return_value=mock_presentation_exchange + ) + ) + mock_presentation_manager.return_value = mock_mgr + + with async_mock.patch.object( + test_module.web, "json_response" + ) as mock_response: + await test_module.presentation_exchange_send_free_request(self.request) + mock_response.assert_called_once_with( + mock_presentation_exchange.serialize.return_value + ) + async def test_presentation_exchange_send_free_request_not_found(self): self.request.json = async_mock.CoroutineMock( return_value={"connection_id": "dummy"} diff --git a/aries_cloudagent/protocols/present_proof/v2_0/manager.py b/aries_cloudagent/protocols/present_proof/v2_0/manager.py index 54d9127fd2..f9a1f92b2d 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/manager.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/manager.py @@ -156,9 +156,21 @@ async def create_bound_request( ) ], request_presentations_attach=[ - AttachDecorator.data_base64( - mapping=indy_proof_request, - ident="indy", + ( + AttachDecorator.data_base64( + mapping=indy_proof_request, + ident="indy", + aip2_flag=True, + ) + # if self._profile.settings.get_value("aip_version", 1) >= 2 + if ( + self._profile.settings.get_value("emit_new_didcomm_mime_type") + and self._profile.settings.get_value("emit_new_didcomm_prefix") + ) + else AttachDecorator.data_base64( + mapping=indy_proof_request, + ident="indy", + ) ) ], ) @@ -437,7 +449,17 @@ async def create_pres( ) ], presentations_attach=[ - AttachDecorator.data_base64(mapping=indy_proof, ident="indy") + ( + AttachDecorator.data_base64( + mapping=indy_proof, ident="indy", aip2_flag=True + ) + # if self._profile.settings.get_value("aip_version", 1) >= 2 + if ( + self._profile.settings.get_value("emit_new_didcomm_mime_type") + and self._profile.settings.get_value("emit_new_didcomm_prefix") + ) + else AttachDecorator.data_base64(mapping=indy_proof, ident="indy") + ) ], ) @@ -456,9 +478,21 @@ async def create_pres( ), ], presentations_attach=[ - AttachDecorator.data_base64( - mapping=indy_proof, - ident="indy", + ( + AttachDecorator.data_base64( + mapping=indy_proof, + ident="indy", + aip2_flag=True, + ) + # if self._profile.settings.get_value("aip_version", 1) >= 2 + if ( + self._profile.settings.get_value("emit_new_didcomm_mime_type") + and self._profile.settings.get_value("emit_new_didcomm_prefix") + ) + else AttachDecorator.data_base64( + mapping=indy_proof, + ident="indy", + ) ) ], ) diff --git a/aries_cloudagent/protocols/present_proof/v2_0/routes.py b/aries_cloudagent/protocols/present_proof/v2_0/routes.py index b574ad7ed2..bc3754aaa8 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/routes.py @@ -321,7 +321,9 @@ async def _add_nonce(indy_proof_request: Mapping) -> Mapping: return indy_proof_request -def _formats_attach(by_format: Mapping, msg_type: str, spec: str) -> Mapping: +def _formats_attach( + by_format: Mapping, msg_type: str, spec: str, aip2_flag: bool = False +) -> Mapping: """Break out formats and proposals/requests/presentations for v2.0 messages.""" return { @@ -333,7 +335,13 @@ def _formats_attach(by_format: Mapping, msg_type: str, spec: str) -> Mapping: for fmt_api in by_format ], f"{spec}_attach": [ - AttachDecorator.data_base64(mapping=item_by_fmt, ident=fmt_api) + ( + AttachDecorator.data_base64( + mapping=item_by_fmt, ident=fmt_api, aip2_flag=aip2_flag + ) + if aip2_flag + else AttachDecorator.data_base64(mapping=item_by_fmt, ident=fmt_api) + ) for (fmt_api, item_by_fmt) in by_format.items() ], } @@ -520,13 +528,19 @@ async def present_proof_send_proposal(request: web.BaseRequest): connection_id = body.get("connection_id") pres_proposal = body.get("presentation_proposal") + # aip2_flag = (context.profile.settings.get_value("aip_version", 1) >= 2) + aip2_flag = context.profile.settings.get_value( + "emit_new_didcomm_mime_type" + ) and context.profile.settings.get_value("emit_new_didcomm_prefix") conn_record = None async with context.session() as session: try: conn_record = await ConnRecord.retrieve_by_id(session, connection_id) pres_proposal_message = V20PresProposal( comment=comment, - **_formats_attach(pres_proposal, PRES_20_PROPOSAL, "proposals"), + **_formats_attach( + pres_proposal, PRES_20_PROPOSAL, "proposals", aip2_flag=aip2_flag + ), ) except (BaseModelError, StorageError) as err: return await internal_error( @@ -608,13 +622,22 @@ async def present_proof_create_request(request: web.BaseRequest): comment = body.get("comment") pres_request_spec = body.get("presentation_request") + # aip2_flag = (context.profile.settings.get_value("aip_version", 1) >= 2) + aip2_flag = context.profile.settings.get_value( + "emit_new_didcomm_mime_type" + ) and context.profile.settings.get_value("emit_new_didcomm_prefix") if pres_request_spec and V20PresFormat.Format.INDY.api in pres_request_spec: await _add_nonce(pres_request_spec[V20PresFormat.Format.INDY.api]) pres_request_message = V20PresRequest( comment=comment, will_confirm=True, - **_formats_attach(pres_request_spec, PRES_20_REQUEST, "request_presentations"), + **_formats_attach( + pres_request_spec, + PRES_20_REQUEST, + "request_presentations", + aip2_flag=aip2_flag, + ), ) trace_msg = body.get("trace") pres_request_message.assign_trace_decorator( @@ -687,12 +710,21 @@ async def present_proof_send_free_request(request: web.BaseRequest): comment = body.get("comment") pres_request_spec = body.get("presentation_request") + # aip2_flag = (context.profile.settings.get_value("aip_version", 1) >= 2) + aip2_flag = context.profile.settings.get_value( + "emit_new_didcomm_mime_type" + ) and context.profile.settings.get_value("emit_new_didcomm_prefix") if pres_request_spec and V20PresFormat.Format.INDY.api in pres_request_spec: await _add_nonce(pres_request_spec[V20PresFormat.Format.INDY.api]) pres_request_message = V20PresRequest( comment=comment, will_confirm=True, - **_formats_attach(pres_request_spec, PRES_20_REQUEST, "request_presentations"), + **_formats_attach( + pres_request_spec, + PRES_20_REQUEST, + "request_presentations", + aip2_flag=aip2_flag, + ), ) trace_msg = body.get("trace") pres_request_message.assign_trace_decorator( 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 166996092f..5d15d070de 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 @@ -563,6 +563,39 @@ async def test_create_bound_request(self): assert ret_px_rec is px_rec px_rec.save.assert_called_once() + async def test_create_bound_request_aip2(self): + self.profile.settings["emit_new_didcomm_mime_type"] = True + self.profile.settings["emit_new_didcomm_prefix"] = True + + comment = "comment" + proposal = V20PresProposal( + formats=[ + V20PresFormat( + attach_id="indy", + format_=ATTACHMENT_FORMAT[PRES_20_PROPOSAL][ + V20PresFormat.Format.INDY.api + ], + ) + ], + proposals_attach=[ + AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + ], + ) + px_rec = V20PresExRecord( + pres_proposal=proposal.serialize(), + role=V20PresExRecord.ROLE_VERIFIER, + ) + px_rec.save = async_mock.CoroutineMock() + (ret_px_rec, pres_req_msg) = await self.manager.create_bound_request( + pres_ex_record=px_rec, + name=PROOF_REQ_NAME, + version=PROOF_REQ_VERSION, + nonce=PROOF_REQ_NONCE, + comment=comment, + ) + assert ret_px_rec is px_rec + px_rec.save.assert_called_once() + async def test_create_exchange_for_request(self): pres_req = V20PresRequest( comment="Test", @@ -645,6 +678,55 @@ async def test_create_pres(self): save_ex.assert_called_once() assert px_rec_out.state == V20PresExRecord.STATE_PRESENTATION_SENT + async def test_create_pres_aip2(self): + self.profile.settings["emit_new_didcomm_mime_type"] = True + self.profile.settings["emit_new_didcomm_prefix"] = True + + pres_request = V20PresRequest( + formats=[ + V20PresFormat( + attach_id="indy", + format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][ + V20PresFormat.Format.INDY.api + ], + ) + ], + request_presentations_attach=[ + AttachDecorator.data_base64(INDY_PROOF_REQ_NAME, ident="indy") + ], + ) + px_rec_in = V20PresExRecord(pres_request=pres_request.serialize()) + more_magic_rr = async_mock.MagicMock( + get_or_fetch_local_tails_path=async_mock.CoroutineMock( + return_value="/tmp/sample/tails/path" + ) + ) + with async_mock.patch.object( + V20PresExRecord, "save", autospec=True + ) as save_ex, async_mock.patch.object( + test_module, "AttachDecorator", autospec=True + ) as mock_attach_decorator, async_mock.patch.object( + test_module, "RevocationRegistry", autospec=True + ) as mock_rr: + mock_rr.from_definition = async_mock.MagicMock(return_value=more_magic_rr) + + mock_attach_decorator.data_base64 = async_mock.MagicMock( + return_value=mock_attach_decorator + ) + + req_creds = await indy_proof_req_preview2indy_requested_creds( + INDY_PROOF_REQ_NAME, preview=None, holder=self.holder + ) + assert not req_creds["self_attested_attributes"] + assert len(req_creds["requested_attributes"]) == 2 + assert len(req_creds["requested_predicates"]) == 1 + + (px_rec_out, pres_msg) = await self.manager.create_pres( + px_rec_in, req_creds + ) + save_ex.assert_called_once() + assert px_rec_out.state == V20PresExRecord.STATE_PRESENTATION_SENT + async def test_create_pres_proof_req_non_revoc_interval_none(self): indy_proof_req_vcx = deepcopy(INDY_PROOF_REQ_NAME) indy_proof_req_vcx["non_revoked"] = None # simulate interop with indy-vcx diff --git a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py index e80d560885..487fbf719b 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py @@ -396,6 +396,40 @@ async def test_present_proof_send_proposal(self): mock_px_rec_inst.serialize.return_value ) + async def test_present_proof_send_proposal_aip2(self): + self.profile.settings.set_value("emit_new_didcomm_mime_type", True) + self.profile.settings.set_value("emit_new_didcomm_prefix", True) + self.request.json = async_mock.CoroutineMock( + return_value={ + "connection_id": "dummy-conn-id", + "presentation_proposal": { + V20PresFormat.Format.INDY.api: INDY_PROOF_REQ + }, + } + ) + + with async_mock.patch.object( + test_module, "ConnRecord", autospec=True + ) as mock_conn_rec, async_mock.patch.object( + test_module, "V20PresManager", autospec=True + ) as mock_pres_mgr, async_mock.patch.object( + test_module, "V20PresExRecord", autospec=True + ) as mock_pres_ex_rec_cls, async_mock.patch.object( + test_module.web, "json_response", async_mock.MagicMock() + ) as mock_response: + mock_conn_rec.retrieve_by_id = async_mock.CoroutineMock( + return_value=async_mock.MagicMock(is_ready=True) + ) + mock_px_rec_inst = async_mock.MagicMock() + mock_pres_mgr.return_value.create_exchange_for_proposal = ( + async_mock.CoroutineMock(return_value=mock_px_rec_inst) + ) + + await test_module.present_proof_send_proposal(self.request) + mock_response.assert_called_once_with( + mock_px_rec_inst.serialize.return_value + ) + async def test_present_proof_send_proposal_no_conn_record(self): self.request.json = async_mock.CoroutineMock() @@ -474,6 +508,43 @@ async def test_present_proof_create_request(self): mock_px_rec_inst.serialize.return_value ) + async def test_present_proof_create_request_aip2(self): + self.profile.settings.set_value("emit_new_didcomm_mime_type", True) + self.profile.settings.set_value("emit_new_didcomm_prefix", True) + indy_proof_req = deepcopy(INDY_PROOF_REQ) + indy_proof_req.pop("nonce") # exercise _add_nonce() + + self.request.json = async_mock.CoroutineMock( + return_value={ + "comment": "dummy", + "presentation_request": {V20PresFormat.Format.INDY.api: indy_proof_req}, + } + ) + + with async_mock.patch.object( + test_module, "V20PresManager", autospec=True + ) as mock_pres_mgr_cls, async_mock.patch.object( + test_module, "V20PresRequest", autospec=True + ) as mock_pres_request, async_mock.patch.object( + test_module.web, "json_response", async_mock.MagicMock() + ) as mock_response: + mock_px_rec_inst = async_mock.MagicMock( + serialize=async_mock.MagicMock( + return_value={"thread_id": "sample-thread-id"} + ) + ) + mock_pres_mgr_inst = async_mock.MagicMock( + create_exchange_for_request=async_mock.CoroutineMock( + return_value=mock_px_rec_inst + ) + ) + mock_pres_mgr_cls.return_value = mock_pres_mgr_inst + + await test_module.present_proof_create_request(self.request) + mock_response.assert_called_once_with( + mock_px_rec_inst.serialize.return_value + ) + async def test_present_proof_create_request_x(self): self.request.json = async_mock.CoroutineMock( return_value={ @@ -537,6 +608,45 @@ async def test_present_proof_send_free_request(self): mock_px_rec_inst.serialize.return_value ) + async def test_present_proof_send_free_request_aip2(self): + self.profile.settings.set_value("emit_new_didcomm_mime_type", True) + self.profile.settings.set_value("emit_new_didcomm_prefix", True) + self.request.json = async_mock.CoroutineMock( + return_value={ + "connection_id": "dummy", + "comment": "dummy", + "presentation_request": {V20PresFormat.Format.INDY.api: INDY_PROOF_REQ}, + } + ) + + with async_mock.patch.object( + test_module, "ConnRecord", autospec=True + ) as mock_conn_rec_cls, async_mock.patch.object( + test_module, "V20PresManager", autospec=True + ) as mock_pres_mgr_cls, async_mock.patch.object( + test_module, "V20PresRequest", autospec=True + ) as mock_pres_request, async_mock.patch.object( + test_module, "V20PresExRecord", autospec=True + ) as mock_pres_ex_rec_cls, async_mock.patch.object( + test_module.web, "json_response", async_mock.MagicMock() + ) as mock_response: + mock_conn_rec_cls.retrieve_by_id = async_mock.CoroutineMock() + mock_px_rec_inst = async_mock.MagicMock( + serialize=async_mock.MagicMock({"thread_id": "sample-thread-id"}) + ) + + mock_pres_mgr_inst = async_mock.MagicMock( + create_exchange_for_request=async_mock.CoroutineMock( + return_value=mock_px_rec_inst + ) + ) + mock_pres_mgr_cls.return_value = mock_pres_mgr_inst + + await test_module.present_proof_send_free_request(self.request) + mock_response.assert_called_once_with( + mock_px_rec_inst.serialize.return_value + ) + async def test_present_proof_send_free_request_not_found(self): self.request.json = async_mock.CoroutineMock( return_value={"connection_id": "dummy"}