diff --git a/aries_cloudagent/ledger/base.py b/aries_cloudagent/ledger/base.py index bcbb8c54e1..f12b91015e 100644 --- a/aries_cloudagent/ledger/base.py +++ b/aries_cloudagent/ledger/base.py @@ -48,6 +48,10 @@ def backend(self) -> str: def read_only(self) -> bool: """Accessor for the ledger read-only flag.""" + @abstractmethod + async def is_ledger_read_only(self) -> bool: + """Check if ledger is read-only including TAA.""" + @abstractmethod async def get_key_for_did(self, did: str) -> str: """Fetch the verkey for a ledger DID. @@ -266,7 +270,7 @@ async def create_and_send_schema( LOGGER.warning("Schema already exists on ledger. Returning details.") schema_id, schema_def = schema_info else: - if self.read_only: + if await self.is_ledger_read_only(): raise LedgerError( "Error cannot write schema when ledger is in read only mode" ) @@ -461,7 +465,7 @@ async def create_and_send_credential_definition( except IndyIssuerError as err: raise LedgerError(err.message) from err - if self.read_only: + if await self.is_ledger_read_only(): raise LedgerError( "Error cannot write cred def when ledger is in read only mode" ) diff --git a/aries_cloudagent/ledger/indy.py b/aries_cloudagent/ledger/indy.py index 3ac2b19363..ed7ff43a7b 100644 --- a/aries_cloudagent/ledger/indy.py +++ b/aries_cloudagent/ledger/indy.py @@ -294,6 +294,18 @@ def read_only(self) -> bool: """Accessor for the ledger read-only flag.""" return self.pool.read_only + async def is_ledger_read_only(self) -> bool: + """Check if ledger is read-only including TAA.""" + if self.read_only: + return self.read_only + # if TAA is required and not accepted we should be in read-only mode + taa = await self.get_txn_author_agreement() + if taa["taa_required"]: + taa_acceptance = await self.get_latest_txn_author_acceptance() + if "mechanism" not in taa_acceptance: + return True + return self.read_only + async def __aenter__(self) -> "IndySdkLedger": """ Context manager entry. @@ -758,7 +770,7 @@ async def update_endpoint_for_did( ) if exist_endpoint_of_type != endpoint: - if self.pool.read_only: + if await self.is_ledger_read_only(): raise LedgerError( "Error cannot update endpoint when ledger is in read only mode" ) @@ -811,7 +823,7 @@ async def register_nym( alias: Human-friendly alias to assign to the DID. role: For permissioned ledgers, what role should the new DID have. """ - if self.pool.read_only: + if await self.is_ledger_read_only(): raise LedgerError( "Error cannot register nym when ledger is in read only mode" ) diff --git a/aries_cloudagent/ledger/indy_vdr.py b/aries_cloudagent/ledger/indy_vdr.py index 4dc98e75ba..061606a64e 100644 --- a/aries_cloudagent/ledger/indy_vdr.py +++ b/aries_cloudagent/ledger/indy_vdr.py @@ -283,6 +283,18 @@ def read_only(self) -> bool: """Accessor for the ledger read-only flag.""" return self.pool.read_only + async def is_ledger_read_only(self) -> bool: + """Check if ledger is read-only including TAA.""" + if self.read_only: + return self.read_only + # if TAA is required and not accepted we should be in read-only mode + taa = await self.get_txn_author_agreement() + if taa["taa_required"]: + taa_acceptance = await self.get_latest_txn_author_acceptance() + if "mechanism" not in taa_acceptance: + return True + return self.read_only + async def __aenter__(self) -> "IndyVdrLedger": """ Context manager entry. diff --git a/aries_cloudagent/ledger/tests/test_indy.py b/aries_cloudagent/ledger/tests/test_indy.py index 796ea1e887..bac9a63037 100644 --- a/aries_cloudagent/ledger/tests/test_indy.py +++ b/aries_cloudagent/ledger/tests/test_indy.py @@ -537,8 +537,10 @@ async def test_txn_endorse( @async_mock.patch("aries_cloudagent.storage.indy.IndySdkStorage.add_record") @async_mock.patch("indy.ledger.build_schema_request") @async_mock.patch("indy.ledger.append_request_endorser") + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") async def test_send_schema( self, + mock_is_ledger_read_only, mock_append_request_endorser, mock_build_schema_req, mock_add_record, @@ -550,6 +552,7 @@ async def test_send_schema( ): mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) + mock_is_ledger_read_only.return_value = False issuer = async_mock.MagicMock(IndyIssuer) ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) @@ -677,8 +680,10 @@ async def test_send_schema_already_exists( ) @async_mock.patch("aries_cloudagent.storage.indy.IndySdkStorage.add_record") @async_mock.patch("indy.ledger.build_schema_request") + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") async def test_send_schema_ledger_transaction_error_already_exists( self, + mock_is_ledger_read_only, mock_build_schema_req, mock_add_record, mock_check_existing, @@ -690,6 +695,7 @@ async def test_send_schema_ledger_transaction_error_already_exists( mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) + mock_is_ledger_read_only.return_value = False issuer = async_mock.MagicMock(IndyIssuer) issuer.create_schema.return_value = ("1", "{}") @@ -764,8 +770,10 @@ async def test_send_schema_ledger_read_only( @async_mock.patch( "aries_cloudagent.ledger.indy.IndySdkLedger.check_existing_schema" ) + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") async def test_send_schema_issuer_error( self, + mock_is_ledger_read_only, mock_check_existing, mock_close_pool, mock_open_ledger, @@ -775,6 +783,7 @@ async def test_send_schema_issuer_error( mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) + mock_is_ledger_read_only.return_value = False issuer = async_mock.MagicMock(IndyIssuer) issuer.create_schema = async_mock.CoroutineMock( @@ -850,8 +859,10 @@ async def test_send_schema_ledger_transaction_error( ) @async_mock.patch("aries_cloudagent.storage.indy.IndySdkStorage.add_record") @async_mock.patch("indy.ledger.build_schema_request") + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") async def test_send_schema_no_seq_no( self, + mock_is_ledger_read_only, mock_build_schema_req, mock_add_record, mock_fetch_schema_by_seq_no, @@ -863,6 +874,7 @@ async def test_send_schema_no_seq_no( mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) issuer = async_mock.MagicMock(IndyIssuer) + mock_is_ledger_read_only.return_value = False ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) issuer.create_schema.return_value = ("schema_issuer_did:name:1.0", "{}") mock_fetch_schema_by_id.return_value = None @@ -1143,8 +1155,10 @@ async def test_get_schema_by_wrong_seq_no( @async_mock.patch("aries_cloudagent.storage.indy.IndySdkStorage.find_all_records") @async_mock.patch("aries_cloudagent.storage.indy.IndySdkStorage.add_record") @async_mock.patch("indy.ledger.build_cred_def_request") + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") async def test_send_credential_definition( self, + mock_is_ledger_read_only, mock_build_cred_def, mock_add_record, mock_find_all_records, @@ -1157,6 +1171,7 @@ async def test_send_credential_definition( mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) mock_find_all_records.return_value = [] + mock_is_ledger_read_only.return_value = False mock_get_schema.return_value = {"seqNo": 999} cred_def_id = f"{self.test_did}:3:CL:999:default" @@ -1231,8 +1246,10 @@ async def test_send_credential_definition( @async_mock.patch("aries_cloudagent.storage.indy.IndySdkStorage.add_record") @async_mock.patch("indy.ledger.build_cred_def_request") @async_mock.patch("indy.ledger.append_request_endorser") + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") async def test_send_credential_definition_endorse_only( self, + mock_is_ledger_read_only, mock_append_request_endorser, mock_build_cred_def, mock_add_record, @@ -1246,6 +1263,7 @@ async def test_send_credential_definition_endorse_only( mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) mock_find_all_records.return_value = [] + mock_is_ledger_read_only.return_value = False mock_get_schema.return_value = {"seqNo": 999} cred_def_id = f"{self.test_did}:3:CL:999:default" @@ -2229,8 +2247,10 @@ async def test_get_endpoint_for_did_no_endpoint( @async_mock.patch("indy.ledger.build_get_attrib_request") @async_mock.patch("indy.ledger.build_attrib_request") @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger._submit") + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") async def test_update_endpoint_for_did( self, + mock_is_ledger_read_only, mock_submit, mock_build_attrib_req, mock_build_get_attrib_req, @@ -2240,6 +2260,7 @@ async def test_update_endpoint_for_did( mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) endpoint = ["http://old.aries.ca", "http://new.aries.ca"] + mock_is_ledger_read_only.return_value = False mock_submit.side_effect = [ json.dumps( { @@ -2283,8 +2304,10 @@ async def test_update_endpoint_for_did( @async_mock.patch("indy.ledger.build_get_attrib_request") @async_mock.patch("indy.ledger.build_attrib_request") @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger._submit") + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") async def test_update_endpoint_for_did_no_prior_endpoints( self, + mock_is_ledger_read_only, mock_submit, mock_build_attrib_req, mock_build_get_attrib_req, @@ -2294,6 +2317,7 @@ async def test_update_endpoint_for_did_no_prior_endpoints( mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) endpoint = "http://new.aries.ca" + mock_is_ledger_read_only.return_value = False ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) with async_mock.patch.object( IndySdkWallet, "get_public_did" @@ -2329,8 +2353,10 @@ async def test_update_endpoint_for_did_no_prior_endpoints( @async_mock.patch("indy.ledger.build_get_attrib_request") @async_mock.patch("indy.ledger.build_attrib_request") @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger._submit") + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") async def test_update_endpoint_of_type_profile_for_did( self, + mock_is_ledger_read_only, mock_submit, mock_build_attrib_req, mock_build_get_attrib_req, @@ -2341,6 +2367,7 @@ async def test_update_endpoint_of_type_profile_for_did( self.session.context.injector.bind_provider(BaseWallet, mock_wallet) endpoint = ["http://company.com/oldProfile", "http://company.com/newProfile"] endpoint_type = EndpointType.PROFILE + mock_is_ledger_read_only.return_value = False mock_submit.side_effect = [ json.dumps( { @@ -2354,6 +2381,11 @@ async def test_update_endpoint_of_type_profile_for_did( for i in range(len(endpoint)) ] ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) + # ledger = async_mock.patch.object( + # ledger, + # "is_ledger_read_only", + # async_mock.CoroutineMock(return_value=False), + # ) with async_mock.patch.object( IndySdkWallet, "get_public_did" ) as mock_wallet_get_public_did: @@ -2446,11 +2478,18 @@ async def test_update_endpoint_for_did_read_only( @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedgerPool.context_close") @async_mock.patch("indy.ledger.build_nym_request") @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger._submit") + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") async def test_register_nym( - self, mock_submit, mock_build_nym_req, mock_close, mock_open + self, + mock_is_ledger_read_only, + mock_submit, + mock_build_nym_req, + mock_close, + mock_open, ): mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) + mock_is_ledger_read_only.return_value = False with async_mock.patch.object( IndySdkWallet, "get_public_did" ) as mock_wallet_get_public_did, async_mock.patch.object( @@ -2518,12 +2557,19 @@ async def test_register_nym_read_only(self, mock_close, mock_open): @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedgerPool.context_open") @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedgerPool.context_close") - async def test_register_nym_no_public_did(self, mock_close, mock_open): + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") + async def test_register_nym_no_public_did( + self, + mock_is_ledger_read_only, + mock_close, + mock_open, + ): mock_wallet = async_mock.MagicMock( type="indy", get_local_did=async_mock.CoroutineMock(), replace_local_did_metadata=async_mock.CoroutineMock(), ) + mock_is_ledger_read_only.return_value = False self.session.context.injector.bind_provider(BaseWallet, mock_wallet) ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) with async_mock.patch.object( @@ -2543,14 +2589,21 @@ async def test_register_nym_no_public_did(self, mock_close, mock_open): @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedgerPool.context_close") @async_mock.patch("indy.ledger.build_nym_request") @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger._submit") + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") async def test_register_nym_ledger_x( - self, mock_submit, mock_build_nym_req, mock_close, mock_open + self, + mock_is_ledger_read_only, + mock_submit, + mock_build_nym_req, + mock_close, + mock_open, ): mock_wallet = async_mock.MagicMock() mock_build_nym_req.side_effect = IndyError( error_code=ErrorCode.CommonInvalidParam1, error_details={"message": "not today"}, ) + mock_is_ledger_read_only.return_value = False self.session.context.injector.bind_provider(BaseWallet, mock_wallet) ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) with async_mock.patch.object( @@ -2570,11 +2623,18 @@ async def test_register_nym_ledger_x( @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedgerPool.context_close") @async_mock.patch("indy.ledger.build_nym_request") @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger._submit") + @async_mock.patch("aries_cloudagent.ledger.indy.IndySdkLedger.is_ledger_read_only") async def test_register_nym_steward_register_others_did( - self, mock_submit, mock_build_nym_req, mock_close, mock_open + self, + mock_is_ledger_read_only, + mock_submit, + mock_build_nym_req, + mock_close, + mock_open, ): mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) + mock_is_ledger_read_only.return_value = False ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) with async_mock.patch.object( IndySdkWallet, "get_public_did" diff --git a/aries_cloudagent/ledger/tests/test_indy_vdr.py b/aries_cloudagent/ledger/tests/test_indy_vdr.py index f6ef98962a..be08412312 100644 --- a/aries_cloudagent/ledger/tests/test_indy_vdr.py +++ b/aries_cloudagent/ledger/tests/test_indy_vdr.py @@ -38,6 +38,8 @@ async def close(): with async_mock.patch.object(ledger.pool, "open", open), async_mock.patch.object( ledger.pool, "close", close + ), async_mock.patch.object( + ledger, "is_ledger_read_only", async_mock.CoroutineMock(return_value=False) ): yield ledger @@ -302,6 +304,10 @@ async def test_send_schema_ledger_read_only( ledger, "check_existing_schema", async_mock.CoroutineMock(return_value=False), + ), async_mock.patch.object( + ledger, + "is_ledger_read_only", + async_mock.CoroutineMock(return_value=True), ): with pytest.raises(LedgerError): schema_id, schema_def = await ledger.create_and_send_schema( diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py index 89fbf92081..07da03fd51 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/route_manager.py @@ -66,13 +66,14 @@ async def mediation_record_for_connection( or_default: bool = False, ): """Return relevant mediator for connection.""" - async with profile.session() as session: - mediation_metadata = await conn_record.metadata_get( - session, MediationManager.METADATA_KEY, {} - ) - mediation_id = ( - mediation_metadata.get(MediationManager.METADATA_ID) or mediation_id - ) + if conn_record.connection_id: + async with profile.session() as session: + mediation_metadata = await conn_record.metadata_get( + session, MediationManager.METADATA_KEY, {} + ) + mediation_id = ( + mediation_metadata.get(MediationManager.METADATA_ID) or mediation_id + ) mediation_record = await self.mediation_record_if_id( profile, mediation_id, or_default diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py index dcb315337b..3482a1a545 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_route_manager.py @@ -53,7 +53,7 @@ def mediation_route_manager(): @pytest.fixture def conn_record(): - record = ConnRecord() + record = ConnRecord(connection_id="12345") record.metadata_get = mock.CoroutineMock(return_value={}) record.metadata_set = mock.CoroutineMock() yield record diff --git a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py index 7b02b328f7..4bdfcc39a6 100644 --- a/aries_cloudagent/protocols/out_of_band/v1_0/manager.py +++ b/aries_cloudagent/protocols/out_of_band/v1_0/manager.py @@ -359,9 +359,10 @@ async def create_invitation( async with self.profile.session() as session: await oob_record.save(session, reason="Created new oob invitation") - await self._route_manager.route_invitation( - self.profile, conn_rec, mediation_record - ) + if conn_rec: + await self._route_manager.route_invitation( + self.profile, conn_rec, mediation_record + ) return InvitationRecord( # for return via admin API, not storage oob_id=oob_record.oob_id, diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index 637332d55a..964afe20b3 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -424,7 +424,8 @@ async def revoke(request: web.BaseRequest): body["notify"] = body.get("notify", context.settings.get("revocation.notify")) notify = body.get("notify") connection_id = body.get("connection_id") - notify_version = body.get("notify_version", "v1_0") + body["notify_version"] = body.get("notify_version", "v1_0") + notify_version = body["notify_version"] if notify and not connection_id: raise web.HTTPBadRequest(reason="connection_id must be set when notify is true")