From 24a0fa626f6aed665d72974cc7474e5ac4241074 Mon Sep 17 00:00:00 2001 From: sklump Date: Thu, 18 Jun 2020 14:35:56 +0000 Subject: [PATCH 1/3] work in progress Signed-off-by: sklump --- .../protocols/issue_credential/v1_0/manager.py | 18 +++++++++++++++++- .../protocols/issue_credential/v1_0/routes.py | 8 +++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py index 1df2b3d919..679c5c6434 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py @@ -181,7 +181,6 @@ async def receive_proposal(self) -> V10CredentialExchange: The resulting credential exchange record, created """ - # go to cred def via ledger to get authoritative schema id credential_proposal_message = self.context.message connection_id = self.context.connection_record.connection_id @@ -242,6 +241,23 @@ async def _create(cred_def_id): ) cred_preview = credential_proposal_message.credential_proposal + # vet attributes + ledger: BaseLedger = await self.context.inject(BaseLedger) + async with ledger: + credential_definition = await ledger.get_credential_definition( + credential_definition_id + ) + cred_def_attrs = { + attr for attr in credential_definition["value"]["primary"]["r"] + if attr != "master_secret" + } + preview_attrs = {attr for attr in cred_preview.attr_dict()} + if preview_attrs != cred_def_attrs: + raise CredentialManagerError( + f"Preview attributes {preview_attrs} " + f"mismatch corresponding schema attributes {cred_def_attrs}" + ) + credential_offer = None cache_key = f"credential_offer::{cred_def_id}" cache: BaseCache = await self.context.inject(BaseCache, required=False) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py index f34a71984e..926a410d80 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py @@ -600,7 +600,7 @@ async def credential_exchange_create_free_offer(request: web.BaseRequest): context, credential_offer_message, conn_did, endpoint ) result = credential_exchange_record.serialize() - except BaseModelError as err: + except (BaseModelError, CredentialManagerError, LedgerError) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err response = {"record": result, "oob_url": oob_url} @@ -672,7 +672,9 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): trace_msg, ) result = credential_exchange_record.serialize() - except (StorageNotFoundError, BaseModelError) as err: + except ( + StorageNotFoundError, BaseModelError, CredentialManagerError, LedgerError + ) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err await outbound_handler(credential_offer_message, connection_id=connection_id) @@ -749,7 +751,7 @@ async def credential_exchange_send_bound_offer(request: web.BaseRequest): ) result = credential_exchange_record.serialize() - except (StorageError, BaseModelError) as err: + except (StorageError, BaseModelError, CredentialManagerError, LedgerError) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err await outbound_handler(credential_offer_message, connection_id=connection_id) From 294b291b2df6ae8ac52996c019b01ab8c99b0a2d Mon Sep 17 00:00:00 2001 From: sklump Date: Thu, 18 Jun 2020 15:40:42 +0000 Subject: [PATCH 2/3] issuer check attributes from preview before making hopeless offer not matching its cred def Signed-off-by: sklump --- .../issue_credential/v1_0/manager.py | 7 +- .../protocols/issue_credential/v1_0/routes.py | 8 +- .../v1_0/tests/test_manager.py | 135 +++++++++++++++++- 3 files changed, 140 insertions(+), 10 deletions(-) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py index 679c5c6434..19e2df3682 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py @@ -244,11 +244,10 @@ async def _create(cred_def_id): # vet attributes ledger: BaseLedger = await self.context.inject(BaseLedger) async with ledger: - credential_definition = await ledger.get_credential_definition( - credential_definition_id - ) + credential_definition = await ledger.get_credential_definition(cred_def_id) cred_def_attrs = { - attr for attr in credential_definition["value"]["primary"]["r"] + attr + for attr in credential_definition["value"]["primary"]["r"] if attr != "master_secret" } preview_attrs = {attr for attr in cred_preview.attr_dict()} diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py index 926a410d80..a67d9a0d79 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py @@ -15,6 +15,7 @@ from ....connections.models.connection_record import ConnectionRecord from ....issuer.indy import IssuerRevocationRegistryFullError +from ....ledger.error import LedgerError from ....messaging.credential_definitions.util import CRED_DEF_TAGS from ....messaging.models.base import BaseModelError from ....messaging.valid import ( @@ -35,7 +36,7 @@ from ...problem_report.v1_0.message import ProblemReport -from .manager import CredentialManager +from .manager import CredentialManager, CredentialManagerError from .message_types import SPEC_URI from .messages.credential_proposal import CredentialProposal from .messages.credential_offer import CredentialOfferSchema @@ -673,7 +674,10 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): ) result = credential_exchange_record.serialize() except ( - StorageNotFoundError, BaseModelError, CredentialManagerError, LedgerError + StorageNotFoundError, + BaseModelError, + CredentialManagerError, + LedgerError, ) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err 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 77bb345710..10ad1b2262 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 @@ -258,6 +258,24 @@ async def test_receive_proposal(self): self.ledger.credential_definition_id2schema_id = async_mock.CoroutineMock( return_value=SCHEMA_ID ) + self.ledger.get_credential_definition = async_mock.CoroutineMock( + return_value={ + "ver": "1.0", + "id": CRED_DEF_ID, + "schemaId": SCHEMA_ID, + "type": "CL", + "tag": "default", + "value": { + "primary": { + "n": "...", + "s": "...", + "r": {"master_secret": "...", "attr": "value"}, + "rctxt": "...", + }, + "revocation": None, + }, + } + ) with async_mock.patch.object( V10CredentialExchange, "save", autospec=True @@ -310,8 +328,24 @@ async def test_create_free_offer(self): V10CredentialExchange, "save", autospec=True ) as save_ex: self.ledger.get_credential_definition = async_mock.CoroutineMock( - return_value={"value": {}} + return_value={ + "ver": "1.0", + "id": CRED_DEF_ID, + "schemaId": SCHEMA_ID, + "type": "CL", + "tag": "default", + "value": { + "primary": { + "n": "...", + "s": "...", + "r": {"master_secret": "...", "attr": "value"}, + "rctxt": "...", + }, + "revocation": None, + }, + } ) + self.cache = BasicCache() self.context.injector.bind_instance(BaseCache, self.cache) @@ -361,6 +395,84 @@ async def test_create_free_offer(self): credential_exchange_record=exchange, 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" + schema_id_parts = SCHEMA_ID.split(":") + + preview = CredentialPreview( + attributes=(CredAttrSpec(name="attr", value="value"),) + ) + proposal = CredentialProposal( + credential_proposal=preview, cred_def_id=CRED_DEF_ID, schema_id=None + ) + + exchange = V10CredentialExchange( + 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.ledger.get_credential_definition = async_mock.CoroutineMock( + return_value={ + "ver": "1.0", + "id": CRED_DEF_ID, + "schemaId": SCHEMA_ID, + "type": "CL", + "tag": "default", + "value": { + "primary": { + "n": "...", + "s": "...", + "r": { + "master_secret": "...", + "attr": "value", + "extra": "cred-def-attr", + }, + "rctxt": "...", + }, + "revocation": None, + }, + } + ) + + self.cache = BasicCache() + self.context.injector.bind_instance(BaseCache, self.cache) + + cred_offer = {"cred_def_id": CRED_DEF_ID, "schema_id": SCHEMA_ID} + + issuer = async_mock.MagicMock(BaseIssuer, autospec=True) + issuer.create_credential_offer = async_mock.CoroutineMock( + return_value=json.dumps(cred_offer) + ) + self.context.injector.bind_instance(BaseIssuer, issuer) + + self.storage = BasicStorage() + self.context.injector.bind_instance(BaseStorage, self.storage) + 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())), + }, + ) + storage: BaseStorage = await self.context.inject(BaseStorage) + await storage.add_record(cred_def_record) + + with self.assertRaises(CredentialManagerError): + await self.manager.create_offer( + credential_exchange_record=exchange, comment=comment + ) + async def test_create_bound_offer(self): TEST_DID = "LjgpST2rjsoxYegQDRm7EL" schema_id_parts = SCHEMA_ID.split(":") @@ -376,6 +488,24 @@ async def test_create_bound_offer(self): credential_proposal_dict=proposal.serialize(), role=V10CredentialExchange.ROLE_ISSUER, ) + self.ledger.get_credential_definition = async_mock.CoroutineMock( + return_value={ + "ver": "1.0", + "id": CRED_DEF_ID, + "schemaId": SCHEMA_ID, + "type": "CL", + "tag": "default", + "value": { + "primary": { + "n": "...", + "s": "...", + "r": {"master_secret": "...", "attr": "value"}, + "rctxt": "...", + }, + "revocation": None, + }, + } + ) with async_mock.patch.object( V10CredentialExchange, "save", autospec=True @@ -384,9 +514,6 @@ async def test_create_bound_offer(self): ) as get_cached_key, async_mock.patch.object( V10CredentialExchange, "set_cached_key", autospec=True ) as set_cached_key: - self.ledger.get_credential_definition = async_mock.CoroutineMock( - return_value={"value": {}} - ) get_cached_key.return_value = None cred_offer = {"cred_def_id": CRED_DEF_ID, "schema_id": SCHEMA_ID} issuer = async_mock.MagicMock(BaseIssuer, autospec=True) From 12f4c542aecf17abfde1a75907cdf28471d7cd64 Mon Sep 17 00:00:00 2001 From: sklump Date: Fri, 19 Jun 2020 10:56:37 +0000 Subject: [PATCH 3/3] compare attrs vs schema not cred def: canonicalization Signed-off-by: sklump --- .../issue_credential/v1_0/manager.py | 13 +- .../v1_0/tests/test_manager.py | 146 +++++++----------- 2 files changed, 59 insertions(+), 100 deletions(-) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py index 19e2df3682..4847329956 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py @@ -244,17 +244,14 @@ async def _create(cred_def_id): # vet attributes ledger: BaseLedger = await self.context.inject(BaseLedger) async with ledger: - credential_definition = await ledger.get_credential_definition(cred_def_id) - cred_def_attrs = { - attr - for attr in credential_definition["value"]["primary"]["r"] - if attr != "master_secret" - } + schema_id = await ledger.credential_definition_id2schema_id() + schema = await ledger.get_schema(schema_id) + schema_attrs = {attr for attr in schema["attrNames"]} preview_attrs = {attr for attr in cred_preview.attr_dict()} - if preview_attrs != cred_def_attrs: + if preview_attrs != schema_attrs: raise CredentialManagerError( f"Preview attributes {preview_attrs} " - f"mismatch corresponding schema attributes {cred_def_attrs}" + f"mismatch corresponding schema attributes {schema_attrs}" ) credential_offer = None 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 10ad1b2262..dd393977af 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 @@ -52,7 +52,12 @@ "primary": { "n": "...", "s": "...", - "r": {"master_secret": "...", "number": "...", "remainder": "..."}, + "r": { + "master_secret": "...", + "legalName": "...", + "jurisdictionId": "...", + "incorporationDate": "...", + }, "rctxt": "...", "z": "...", }, @@ -108,6 +113,9 @@ async def setUp(self): return_value=REV_REG_DEF ) self.ledger.__aenter__ = async_mock.CoroutineMock(return_value=self.ledger) + self.ledger.credential_definition_id2schema_id = async_mock.CoroutineMock( + return_value=SCHEMA_ID + ) self.context.injector.bind_instance(BaseLedger, self.ledger) self.manager = CredentialManager(self.context) @@ -152,7 +160,11 @@ async def test_record_eq(self): async def test_prepare_send(self): connection_id = "test_conn_id" preview = CredentialPreview( - attributes=(CredAttrSpec(name="attr", value="value"),) + 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=SCHEMA_ID @@ -178,7 +190,11 @@ async def test_create_proposal(self): connection_id = "test_conn_id" comment = "comment" preview = CredentialPreview( - attributes=(CredAttrSpec(name="attr", value="value"),) + attributes=( + CredAttrSpec(name="legalName", value="value"), + CredAttrSpec(name="jurisdictionId", value="value"), + CredAttrSpec(name="incorporationDate", value="value"), + ) ) self.ledger.credential_definition_id2schema_id = async_mock.CoroutineMock( @@ -250,33 +266,15 @@ async def test_receive_proposal(self): comment = "comment" preview = CredentialPreview( - attributes=(CredAttrSpec(name="attr", value="value"),) + attributes=( + CredAttrSpec(name="legalName", value="value"), + CredAttrSpec(name="jurisdictionId", value="value"), + CredAttrSpec(name="incorporationDate", value="value"), + ) ) self.context.connection_record = async_mock.MagicMock() self.context.connection_record.connection_id = connection_id - self.ledger.credential_definition_id2schema_id = async_mock.CoroutineMock( - return_value=SCHEMA_ID - ) - self.ledger.get_credential_definition = async_mock.CoroutineMock( - return_value={ - "ver": "1.0", - "id": CRED_DEF_ID, - "schemaId": SCHEMA_ID, - "type": "CL", - "tag": "default", - "value": { - "primary": { - "n": "...", - "s": "...", - "r": {"master_secret": "...", "attr": "value"}, - "rctxt": "...", - }, - "revocation": None, - }, - } - ) - with async_mock.patch.object( V10CredentialExchange, "save", autospec=True ) as save_ex: @@ -312,7 +310,11 @@ async def test_create_free_offer(self): schema_id_parts = SCHEMA_ID.split(":") preview = CredentialPreview( - attributes=(CredAttrSpec(name="attr", value="value"),) + 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 @@ -327,24 +329,6 @@ async def test_create_free_offer(self): with async_mock.patch.object( V10CredentialExchange, "save", autospec=True ) as save_ex: - self.ledger.get_credential_definition = async_mock.CoroutineMock( - return_value={ - "ver": "1.0", - "id": CRED_DEF_ID, - "schemaId": SCHEMA_ID, - "type": "CL", - "tag": "default", - "value": { - "primary": { - "n": "...", - "s": "...", - "r": {"master_secret": "...", "attr": "value"}, - "rctxt": "...", - }, - "revocation": None, - }, - } - ) self.cache = BasicCache() self.context.injector.bind_instance(BaseCache, self.cache) @@ -401,7 +385,11 @@ async def test_create_free_offer_attr_mismatch(self): schema_id_parts = SCHEMA_ID.split(":") preview = CredentialPreview( - attributes=(CredAttrSpec(name="attr", value="value"),) + attributes=( + CredAttrSpec(name="legal name", value="value"), + CredAttrSpec(name="jurisdiction id", value="value"), + CredAttrSpec(name="incorporation date", value="value"), + ) ) proposal = CredentialProposal( credential_proposal=preview, cred_def_id=CRED_DEF_ID, schema_id=None @@ -416,29 +404,6 @@ async def test_create_free_offer_attr_mismatch(self): with async_mock.patch.object( V10CredentialExchange, "save", autospec=True ) as save_ex: - self.ledger.get_credential_definition = async_mock.CoroutineMock( - return_value={ - "ver": "1.0", - "id": CRED_DEF_ID, - "schemaId": SCHEMA_ID, - "type": "CL", - "tag": "default", - "value": { - "primary": { - "n": "...", - "s": "...", - "r": { - "master_secret": "...", - "attr": "value", - "extra": "cred-def-attr", - }, - "rctxt": "...", - }, - "revocation": None, - }, - } - ) - self.cache = BasicCache() self.context.injector.bind_instance(BaseCache, self.cache) @@ -476,36 +441,21 @@ async def test_create_free_offer_attr_mismatch(self): async def test_create_bound_offer(self): TEST_DID = "LjgpST2rjsoxYegQDRm7EL" schema_id_parts = SCHEMA_ID.split(":") - cred_def_id = f"{TEST_DID}:3:CL:18:tag" connection_id = "test_conn_id" comment = "comment" preview = CredentialPreview( - attributes=(CredAttrSpec(name="attr", value="value"),) + attributes=( + CredAttrSpec(name="legalName", value="value"), + CredAttrSpec(name="jurisdictionId", value="value"), + CredAttrSpec(name="incorporationDate", value="value"), + ) ) proposal = CredentialProposal(credential_proposal=preview) exchange = V10CredentialExchange( credential_proposal_dict=proposal.serialize(), role=V10CredentialExchange.ROLE_ISSUER, ) - self.ledger.get_credential_definition = async_mock.CoroutineMock( - return_value={ - "ver": "1.0", - "id": CRED_DEF_ID, - "schemaId": SCHEMA_ID, - "type": "CL", - "tag": "default", - "value": { - "primary": { - "n": "...", - "s": "...", - "r": {"master_secret": "...", "attr": "value"}, - "rctxt": "...", - }, - "revocation": None, - }, - } - ) with async_mock.patch.object( V10CredentialExchange, "save", autospec=True @@ -565,7 +515,11 @@ async def test_create_bound_offer_no_cred_def(self): comment = "comment" preview = CredentialPreview( - attributes=(CredAttrSpec(name="attr", value="value"),) + attributes=( + CredAttrSpec(name="legalName", value="value"), + CredAttrSpec(name="jurisdictionId", value="value"), + CredAttrSpec(name="incorporationDate", value="value"), + ) ) proposal = CredentialProposal(credential_proposal=preview) exchange = V10CredentialExchange( @@ -602,7 +556,11 @@ async def test_receive_offer_proposed(self): thread_id = "thread-id" preview = CredentialPreview( - attributes=(CredAttrSpec(name="attr", value="value"),) + attributes=( + CredAttrSpec(name="legalName", value="value"), + CredAttrSpec(name="jurisdictionId", value="value"), + CredAttrSpec(name="incorporationDate", value="value"), + ) ) proposal = CredentialProposal(credential_proposal=preview) @@ -650,7 +608,11 @@ async def test_receive_free_offer(self): connection_id = "test_conn_id" indy_offer = {"schema_id": SCHEMA_ID, "cred_def_id": CRED_DEF_ID} preview = CredentialPreview( - attributes=(CredAttrSpec(name="attr", value="value"),) + attributes=( + CredAttrSpec(name="legalName", value="value"), + CredAttrSpec(name="jurisdictionId", value="value"), + CredAttrSpec(name="incorporationDate", value="value"), + ) ) offer = CredentialOffer(