From 0af2d9f271e6751ef03ec0931d293d0b57d7fb57 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 19 Jun 2024 17:09:26 +0200 Subject: [PATCH 01/74] move to service --- app/routes/definitions.py | 157 ++++---------------------------------- 1 file changed, 15 insertions(+), 142 deletions(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index cda5244a3..290101d67 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -1,11 +1,6 @@ -import asyncio from typing import List, Optional -from aries_cloudcontroller import ( - CredentialDefinitionSendRequest, - SchemaGetResult, - SchemaSendRequest, -) +from aries_cloudcontroller import SchemaSendRequest from fastapi import APIRouter, Depends, HTTPException from app.dependencies.acapy_clients import client_from_auth, get_governance_controller @@ -17,32 +12,25 @@ acapy_auth_verified, ) from app.dependencies.role import Role -from app.exceptions import ( - CloudApiException, - TrustRegistryException, - handle_acapy_call, - handle_model_with_validation, -) +from app.exceptions import handle_acapy_call, handle_model_with_validation from app.models.definitions import ( CreateCredentialDefinition, CreateSchema, CredentialDefinition, CredentialSchema, ) -from app.routes.trust_registry import ( - get_schema_by_id as get_trust_registry_schema_by_id, +from app.services.definitions import ( + create_cred_def, + create_schema_service, + get_cred_defs, + get_schemas_governance, + get_schemas_tenant, ) -from app.routes.trust_registry import get_schemas as get_trust_registry_schemas -from app.services import acapy_wallet -from app.services.revocation_registry import wait_for_active_registry -from app.services.trust_registry.schemas import register_schema -from app.services.trust_registry.util.issuer import assert_valid_issuer from app.util.definitions import ( credential_definition_from_acapy, credential_schema_from_acapy, ) -from app.util.retry_method import coroutine_with_retry, coroutine_with_retry_until_value -from shared import ACAPY_ENDORSER_ALIAS, REGISTRY_CREATION_TIMEOUT +from app.util.retry_method import coroutine_with_retry from shared.log_config import get_logger logger = get_logger(__name__) @@ -96,128 +84,13 @@ async def create_schema( schema_version=schema.version, ) async with get_governance_controller(governance_auth) as aries_controller: - try: - bound_logger.info("Publishing schema as governance") - result = await handle_acapy_call( - logger=bound_logger, - acapy_call=aries_controller.schema.publish_schema, - body=schema_send_request, - create_transaction_for_endorser=False, - ) - except CloudApiException as e: - bound_logger.info( - "An Exception was caught while trying to publish schema: `{}`", - e.detail, - ) - if e.status_code == 400 and "already exist" in e.detail: - bound_logger.info("Handling case of schema already existing on ledger") - bound_logger.debug("Fetching public DID for governance controller") - pub_did = await handle_acapy_call( - logger=bound_logger, - acapy_call=aries_controller.wallet.get_public_did, - ) - - _schema_id = f"{pub_did.result.did}:2:{schema.name}:{schema.version}" - bound_logger.debug( - "Fetching schema id `{}` which is associated with request", - _schema_id, - ) - _schema: SchemaGetResult = await handle_acapy_call( - logger=bound_logger, - acapy_call=aries_controller.schema.get_schema, - schema_id=_schema_id, - ) - # Edge case where the governance agent has changed its public did - # Then we need to retrieve the schema in a different way as constructing the schema ID the way above - # will not be correct due to different public did. - if _schema.var_schema is None: - bound_logger.debug( - "Schema not found. Governance agent may have changed public DID. " - "Fetching schemas created by governance agent with request name and version" - ) - schemas_created_ids = await handle_acapy_call( - logger=bound_logger, - acapy_call=aries_controller.schema.get_created_schemas, - schema_name=schema.name, - schema_version=schema.version, - ) - bound_logger.debug("Getting schemas associated with fetched ids") - schemas: List[SchemaGetResult] = [ - await handle_acapy_call( - logger=bound_logger, - acapy_call=aries_controller.schema.get_schema, - schema_id=schema_id, - ) - for schema_id in schemas_created_ids.schema_ids - if schema_id - ] - if schemas: - if len(schemas) > 1: - raise CloudApiException( # pylint: disable=W0707 - f"Multiple schemas with name {schema.name} and version {schema.version} exist." - + f"These are: `{str(schemas_created_ids.schema_ids)}`.", - 409, - ) - - bound_logger.debug("Using updated schema id with new DID") - _schema: SchemaGetResult = schemas[0] - else: - # if schema already exists, we should at least fetch 1, so this should never happen - raise CloudApiException( - "Could not publish schema.", 500 - ) # pylint: disable=W0707 - # Schema exists with different attributes - if set(_schema.var_schema.attr_names) != set(schema.attribute_names): - raise CloudApiException( - "Error creating schema: Schema already exists with different attribute names." - + f"Given: `{str(set(_schema.var_schema.attr_names))}`. " - f"Found: `{str(set(schema.attribute_names))}`.", - 409, - ) # pylint: disable=W0707 - - result = credential_schema_from_acapy(_schema.var_schema) - bound_logger.info( - "Schema already exists on ledger. Returning schema definition: `{}`.", - result, - ) - return result - else: - bound_logger.warning( - "An unhandled Exception was caught while publishing schema. The error message is: '{}'.", - e.detail, - ) - raise CloudApiException("Error while creating schema.") from e - - # Register the schema in the trust registry - try: - if result.sent and result.sent.schema_id: - bound_logger.debug("Registering schema after successful publish to ledger") - await register_schema(schema_id=result.sent.schema_id) - else: - bound_logger.error("No SchemaSendResult in `publish_schema` response.") - raise CloudApiException( - "An unexpected error occurred: could not publish schema." - ) - except TrustRegistryException as error: - # If status_code is 405 it means the schema already exists in the trust registry - # That's okay, because we've achieved our intended result: - # make sure the schema is registered in the trust registry - bound_logger.info( - "Caught TrustRegistryException when registering schema. " - "Got status code {} with message `{}`", - error.status_code, - error.detail, + schema_response = await create_schema_service( + logger=bound_logger, + aries_controller=aries_controller, + schema_request=schema_send_request, + schema=schema, ) - if error.status_code == 405: - bound_logger.info( - "Status code 405 indicates schema is already registered, so we can continue" - ) - else: - raise error - - result = credential_schema_from_acapy(result.sent.var_schema) - bound_logger.info("Successfully published and registered schema.") - return result + return schema_response @router.get( From bd1b29f51cfe79161c696ed241a463ee8200a5f6 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 19 Jun 2024 17:10:00 +0200 Subject: [PATCH 02/74] move to service and call service based on tenant --- app/routes/definitions.py | 68 +++++---------------------------------- 1 file changed, 8 insertions(+), 60 deletions(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index 290101d67..e5c0a9852 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -144,76 +144,24 @@ async def get_schemas( async with client_from_auth(auth) as aries_controller: if not is_governance: # regular tenant is calling endpoint - bound_logger.info("GET request received: Get created schemas") - - if not schema_id: # client is not filtering by schema_id, fetch all - trust_registry_schemas = await get_trust_registry_schemas() - else: # fetch specific id - trust_registry_schemas = [ - await get_trust_registry_schema_by_id(schema_id) - ] - - schema_ids = [schema.id for schema in trust_registry_schemas] - - else: # Governance is calling the endpoint - bound_logger.info( - "GET request received: Get schemas created by governance client" - ) - # Get all created schema ids that match the filter - bound_logger.debug("Fetching created schemas") - response = await handle_acapy_call( + schemas = await get_schemas_tenant( logger=bound_logger, - acapy_call=aries_controller.schema.get_created_schemas, + aries_controller=aries_controller, schema_id=schema_id, schema_issuer_did=schema_issuer_did, schema_name=schema_name, schema_version=schema_version, ) - # Initiate retrieving all schemas - schema_ids = response.schema_ids or [] - - # We now have schema_ids; the following logic is the same whether called by governance or tenant. - # Now fetch relevant schemas from ledger: - get_schema_futures = [ - handle_acapy_call( + else: # Governance is calling the endpoint + schemas = await get_schemas_governance( logger=bound_logger, - acapy_call=aries_controller.schema.get_schema, + aries_controller=aries_controller, schema_id=schema_id, + schema_issuer_did=schema_issuer_did, + schema_name=schema_name, + schema_version=schema_version, ) - for schema_id in schema_ids - ] - - # Wait for completion of retrieval and transform all schemas into response model (if a schema was returned) - if get_schema_futures: - bound_logger.debug("Fetching each of the created schemas") - schema_results: List[SchemaGetResult] = await asyncio.gather( - *get_schema_futures - ) - else: - bound_logger.debug("No created schema ids returned") - schema_results = [] - - # Stepping out of the aries_controller context, we can now translate the ACA-Py schema response to our custom model - schemas = [ - credential_schema_from_acapy(schema.var_schema) - for schema in schema_results - if schema.var_schema - ] - - if not is_governance: - # Apply post-filtering that could otherwise only be done in governance aca-py call - # todo: our fetch from trust registry method should be able to pre-filter these values - if schema_issuer_did: - schemas = [ - schema - for schema in schemas - if schema.id.split(":")[0] == schema_issuer_did - ] - if schema_name: - schemas = [schema for schema in schemas if schema.name == schema_name] - if schema_version: - schemas = [schema for schema in schemas if schema.version == schema_version] if schemas: bound_logger.info("Successfully fetched schemas.") From 797d1d3f17caf59d3987b05bf40654e78dca524f Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 19 Jun 2024 17:12:36 +0200 Subject: [PATCH 03/74] move to service , call create from service --- app/routes/definitions.py | 118 +------------------------------------- 1 file changed, 3 insertions(+), 115 deletions(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index e5c0a9852..f03aad66c 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -264,124 +264,12 @@ async def create_credential_definition( support_revocation = credential_definition.support_revocation async with client_from_auth(auth) as aries_controller: - # Assert the agent has a public did - bound_logger.debug("Asserting client has public DID") - try: - public_did = await acapy_wallet.assert_public_did(aries_controller) - except CloudApiException as e: - log_message = f"Asserting public DID failed: {e}" - - if e.status_code == 403: - bound_logger.info(log_message) - client_error_message = ( - "Wallet making this request has no public DID. " - "Only issuers with a public DID can make this request." - ) - else: - bound_logger.error(log_message) - client_error_message = ( - "Something went wrong while asserting if request is from a valid issuer. " - "Please try again." - ) - - raise CloudApiException(client_error_message, e.status_code) from e - - # Make sure we are allowed to issue this schema according to trust registry rules - bound_logger.debug("Asserting client is a valid issuer") - await assert_valid_issuer(public_did, credential_definition.schema_id) - - if support_revocation: - endorser_connection = await handle_acapy_call( - logger=bound_logger, - acapy_call=aries_controller.connection.get_connections, - alias=ACAPY_ENDORSER_ALIAS, - ) - has_connections = len(endorser_connection.results) > 0 - - if not has_connections: - bound_logger.error( - "Failed to create credential definition supporting revocation: no endorser connection found. " - "Issuer attempted to create a credential definition with support for revocation but does not " - "have an active connection with an endorser, which is required for this operation." - ) - - raise CloudApiException( - "Credential definition creation failed: An active endorser connection is required " - "to support revocation. Please establish a connection with an endorser and try again." - ) - - bound_logger.debug("Publishing credential definition") - request_body = handle_model_with_validation( + credential_definition_id = await create_cred_def( logger=bound_logger, - model_class=CredentialDefinitionSendRequest, - schema_id=credential_definition.schema_id, + aries_controller=aries_controller, + credential_definition=credential_definition, support_revocation=support_revocation, - tag=credential_definition.tag, - revocation_registry_size=32767, ) - try: - result = await handle_acapy_call( - logger=bound_logger, - acapy_call=aries_controller.credential_definition.publish_cred_def, - body=request_body, - ) - credential_definition_id = result.sent.credential_definition_id - except CloudApiException as e: - bound_logger.warning( - "An Exception was caught while publishing credential definition: `{}` `{}`", - e.detail, - e.status_code, - ) - if "already exists" in e.detail: - raise CloudApiException(status_code=409, detail=e.detail) from e - else: - raise CloudApiException( - detail=f"Error while creating credential definition: {e.detail}", - status_code=e.status_code, - ) from e - - # Wait for cred_def transaction to be acknowledged - if result.txn and result.txn.transaction_id: - bound_logger.debug( - "The publish credential definition response provides a transaction id. " - "Waiting for transaction to be in state `transaction_acked`" - ) - - try: - # Wait for transaction to be acknowledged and written to the ledger - await coroutine_with_retry_until_value( - coroutine_func=aries_controller.endorse_transaction.get_transaction, - args=(result.txn.transaction_id,), - field_name="state", - expected_value="transaction_acked", - logger=bound_logger, - max_attempts=10, - retry_delay=2, - ) - except asyncio.TimeoutError as e: - raise CloudApiException( - "Timeout waiting for endorser to accept the endorsement request.", - 504, - ) from e - - bound_logger.debug("Transaction has been acknowledged by the endorser") - - # Wait for revocation registry creation - if support_revocation: - try: - bound_logger.debug("Waiting for revocation registry creation") - await asyncio.wait_for( - wait_for_active_registry( - aries_controller, credential_definition_id - ), - timeout=REGISTRY_CREATION_TIMEOUT, - ) - except asyncio.TimeoutError as e: - bound_logger.error("Timeout waiting for revocation registry creation.") - raise CloudApiException( - "Timeout waiting for revocation registry creation.", - 504, - ) from e # ACA-Py only returns the id after creating a credential definition # We want consistent return types across all endpoints, so retrieving the credential From 5218065640de0bf2fd40e6a0dd9d0a9e17d14559 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 19 Jun 2024 17:13:57 +0200 Subject: [PATCH 04/74] move to service and call service here --- app/routes/definitions.py | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index f03aad66c..74061c978 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -338,46 +338,17 @@ async def get_credential_definitions( # Get all created credential definition ids that match the filter async with client_from_auth(auth) as aries_controller: - bound_logger.debug("Getting created credential definitions") - response = await handle_acapy_call( + credential_definitions = await get_cred_defs( logger=bound_logger, - acapy_call=aries_controller.credential_definition.get_created_cred_defs, + aries_controller=aries_controller, issuer_did=issuer_did, - cred_def_id=credential_definition_id, + credential_definition_id=credential_definition_id, schema_id=schema_id, schema_issuer_did=schema_issuer_did, schema_name=schema_name, schema_version=schema_version, ) - # Initiate retrieving all credential definitions - credential_definition_ids = response.credential_definition_ids or [] - get_credential_definition_futures = [ - handle_acapy_call( - logger=bound_logger, - acapy_call=aries_controller.credential_definition.get_cred_def, - cred_def_id=credential_definition_id, - ) - for credential_definition_id in credential_definition_ids - ] - - # Wait for completion of retrieval and transform all credential definitions - # into response model (if a credential definition was returned) - if get_credential_definition_futures: - bound_logger.debug("Getting definitions from fetched credential ids") - credential_definition_results = await asyncio.gather( - *get_credential_definition_futures - ) - else: - bound_logger.debug("No definition ids returned") - credential_definition_results = [] - - credential_definitions = [ - credential_definition_from_acapy(credential_definition.credential_definition) - for credential_definition in credential_definition_results - if credential_definition.credential_definition - ] - if credential_definitions: bound_logger.info("Successfully fetched credential definitions.") else: From 24aecd1f69c5aca1ef4e5365b2bd50accd84cc77 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 19 Jun 2024 17:14:16 +0200 Subject: [PATCH 05/74] imports --- app/services/definitions.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 app/services/definitions.py diff --git a/app/services/definitions.py b/app/services/definitions.py new file mode 100644 index 000000000..c94b0c114 --- /dev/null +++ b/app/services/definitions.py @@ -0,0 +1,37 @@ +import asyncio +from logging import Logger +from typing import List, Optional + +from aries_cloudcontroller import ( + AcaPyClient, + CredentialDefinitionSendRequest, + SchemaGetResult, + SchemaSendRequest, +) + +from app.exceptions import ( + CloudApiException, + TrustRegistryException, + handle_acapy_call, + handle_model_with_validation, +) +from app.models.definitions import ( + CreateCredentialDefinition, + CreateSchema, + CredentialDefinition, + CredentialSchema, +) +from app.routes.trust_registry import ( + get_schema_by_id as get_trust_registry_schema_by_id, +) +from app.routes.trust_registry import get_schemas as get_trust_registry_schemas +from app.services import acapy_wallet +from app.services.revocation_registry import wait_for_active_registry +from app.services.trust_registry.schemas import register_schema +from app.services.trust_registry.util.issuer import assert_valid_issuer +from app.util.definitions import ( + credential_definition_from_acapy, + credential_schema_from_acapy, +) +from app.util.retry_method import coroutine_with_retry_until_value +from shared import ACAPY_ENDORSER_ALIAS, REGISTRY_CREATION_TIMEOUT From ee8efc126fdedf0fbe87fdbe4100eb1b5e2dfd44 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 19 Jun 2024 17:14:48 +0200 Subject: [PATCH 06/74] moved create schema logic here --- app/services/definitions.py | 140 ++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/app/services/definitions.py b/app/services/definitions.py index c94b0c114..531593d67 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -35,3 +35,143 @@ ) from app.util.retry_method import coroutine_with_retry_until_value from shared import ACAPY_ENDORSER_ALIAS, REGISTRY_CREATION_TIMEOUT + + +async def create_schema_service( + logger: Logger, + aries_controller: AcaPyClient, + schema_request: SchemaSendRequest, + schema: CreateSchema, +) -> CredentialSchema: + """ + Create a schema and register it in the trust registry + """ + try: + logger.info("Publishing schema as governance") + result = await handle_acapy_call( + logger=logger, + acapy_call=aries_controller.schema.publish_schema, + body=schema_request, + create_transaction_for_endorser=False, + ) + + except CloudApiException as e: + logger.info( + "An Exception was caught while trying to publish schema: `{}`", + e.detail, + ) + + if e.status_code == 400 and "already exist" in e.detail: + logger.info("Handling case of schema already existing on ledger") + logger.debug("Fetching public DID for governance controller") + pub_did = await handle_acapy_call( + logger=logger, + acapy_call=aries_controller.wallet.get_public_did, + ) + + _schema_id = f"{pub_did.result.did}:2:{schema.name}:{schema.version}" + logger.debug( + "Fetching schema id `{}` which is associated with request", + _schema_id, + ) + _schema: SchemaGetResult = await handle_acapy_call( + logger=logger, + acapy_call=aries_controller.schema.get_schema, + schema_id=_schema_id, + ) + + # Edge case where the governance agent has changed its public did + # Then we need to retrieve the schema in a different way as constructing the schema ID the way above + # will not be correct due to different public did. + if _schema.var_schema is None: + logger.debug( + "Schema not found. Governance agent may have changed public DID. " + "Fetching schemas created by governance agent with request name and version" + ) + schemas_created_ids = await handle_acapy_call( + logger=logger, + acapy_call=aries_controller.schema.get_created_schemas, + schema_name=schema.name, + schema_version=schema.version, + ) + logger.debug("Getting schemas associated with fetched ids") + schemas: List[SchemaGetResult] = [ + await handle_acapy_call( + logger=logger, + acapy_call=aries_controller.schema.get_schema, + schema_id=schema_id, + ) + for schema_id in schemas_created_ids.schema_ids + if schema_id + ] + + if schemas: + if len(schemas) > 1: + raise CloudApiException( # pylint: disable=W0707 + f"Multiple schemas with name {schema.name} and version {schema.version} exist." + + f"These are: `{str(schemas_created_ids.schema_ids)}`.", + 409, + ) + + logger.debug("Using updated schema id with new DID") + _schema: SchemaGetResult = schemas[0] + else: + # if schema already exists, we should at least fetch 1, so this should never happen + raise CloudApiException( + "Could not publish schema.", 500 + ) # pylint: disable=W0707 + + # Schema exists with different attributes + if set(_schema.var_schema.attr_names) != set(schema.attribute_names): + raise CloudApiException( + "Error creating schema: Schema already exists with different attribute names." + + f"Given: `{str(set(_schema.var_schema.attr_names))}`. " + f"Found: `{str(set(schema.attribute_names))}`.", + 409, + ) # pylint: disable=W0707 + + result = credential_schema_from_acapy(_schema.var_schema) + logger.info( + "Schema already exists on ledger. Returning schema definition: `{}`.", + result, + ) + return result + + else: + logger.warning( + "An unhandled Exception was caught while publishing schema. The error message is: '{}'.", + e.detail, + ) + raise CloudApiException("Error while creating schema.") from e + + # Register the schema in the trust registry + try: + if result.sent and result.sent.schema_id: + logger.debug("Registering schema after successful publish to ledger") + await register_schema(schema_id=result.sent.schema_id) + else: + logger.error("No SchemaSendResult in `publish_schema` response.") + raise CloudApiException( + "An unexpected error occurred: could not publish schema." + ) + except TrustRegistryException as error: + # If status_code is 405 it means the schema already exists in the trust registry + # That's okay, because we've achieved our intended result: + # make sure the schema is registered in the trust registry + logger.info( + "Caught TrustRegistryException when registering schema. " + "Got status code {} with message `{}`", + error.status_code, + error.detail, + ) + if error.status_code == 405: + logger.info( + "Status code 405 indicates schema is already registered, so we can continue" + ) + else: + raise error + + result = credential_schema_from_acapy(result.sent.var_schema) + logger.info("Successfully published and registered schema.") + + return result From c5d26d1e392e117b0f4358be66748b9e6c690889 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 19 Jun 2024 17:15:20 +0200 Subject: [PATCH 07/74] service for tenants to get schemas --- app/services/definitions.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/app/services/definitions.py b/app/services/definitions.py index 531593d67..c2109d560 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -175,3 +175,37 @@ async def create_schema_service( logger.info("Successfully published and registered schema.") return result + + +async def get_schemas_tenant( + logger: Logger, + aries_controller: AcaPyClient, + schema_id: Optional[str], + schema_issuer_did: Optional[str], + schema_name: Optional[str], + schema_version: Optional[str], +) -> List[CredentialSchema]: + """ + Allows tenants to get all schemas created + """ + logger.info("GET request received: Get created schemas") + + if not schema_id: # client is not filtering by schema_id, fetch all + trust_registry_schemas = await get_trust_registry_schemas() + else: # fetch specific id + trust_registry_schemas = [await get_trust_registry_schema_by_id(schema_id)] + + schema_ids = [schema.id for schema in trust_registry_schemas] + + schemas = await schema_futures(logger, schema_ids, aries_controller) + + if schema_issuer_did: + schemas = [ + schema for schema in schemas if schema.id.split(":")[0] == schema_issuer_did + ] + if schema_name: + schemas = [schema for schema in schemas if schema.name == schema_name] + if schema_version: + schemas = [schema for schema in schemas if schema.version == schema_version] + + return schemas From 2082aaadeab0be2472558696496b7c2e285c5e90 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 19 Jun 2024 17:15:38 +0200 Subject: [PATCH 08/74] service for governance to get schemas --- app/services/definitions.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/app/services/definitions.py b/app/services/definitions.py index c2109d560..14727667f 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -209,3 +209,34 @@ async def get_schemas_tenant( schemas = [schema for schema in schemas if schema.version == schema_version] return schemas + + +async def get_schemas_governance( + logger: Logger, + aries_controller: AcaPyClient, + schema_id: Optional[str], + schema_issuer_did: Optional[str], + schema_name: Optional[str], + schema_version: Optional[str], +) -> List[CredentialSchema]: + """ + Governance agents gets all schemas created by itself + """ + logger.info("GET request received: Get schemas created by governance client") + # Get all created schema ids that match the filter + logger.debug("Fetching created schemas") + response = await handle_acapy_call( + logger=logger, + acapy_call=aries_controller.schema.get_created_schemas, + schema_id=schema_id, + schema_issuer_did=schema_issuer_did, + schema_name=schema_name, + schema_version=schema_version, + ) + + # Initiate retrieving all schemas + schema_ids = response.schema_ids or [] + + schemas = await schema_futures(logger, schema_ids, aries_controller) + + return schemas From b54599e4f751c9fe5a1cf0ae3edea2a4c5cc4635 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 19 Jun 2024 17:16:05 +0200 Subject: [PATCH 09/74] from schema_ids get full schemas --- app/services/definitions.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/app/services/definitions.py b/app/services/definitions.py index 14727667f..c23ec1de6 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -240,3 +240,39 @@ async def get_schemas_governance( schemas = await schema_futures(logger, schema_ids, aries_controller) return schemas + + +async def schema_futures( + logger: Logger, schema_ids: List[str], aries_controller: AcaPyClient +) -> List[CredentialSchema]: + """ + Get schemas with attributes from schema ids + """ + # We now have schema_ids; the following logic is the same whether called by governance or tenant. + # Now fetch relevant schemas from ledger: + get_schema_futures = [ + handle_acapy_call( + logger=logger, + acapy_call=aries_controller.schema.get_schema, + schema_id=schema_id, + ) + for schema_id in schema_ids + ] + + # Wait for completion of retrieval and transform all schemas into response model (if a schema was returned) + if get_schema_futures: + logger.debug("Fetching each of the created schemas") + schema_results: List[SchemaGetResult] = await asyncio.gather( + *get_schema_futures + ) + else: + logger.debug("No created schema ids returned") + schema_results = [] + + schemas = [ + credential_schema_from_acapy(schema.var_schema) + for schema in schema_results + if schema.var_schema + ] + + return schemas From 550394d549da94b34b8aac35f101fbfbbaaf08b8 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 19 Jun 2024 17:16:35 +0200 Subject: [PATCH 10/74] moved bulk of create cred_def logic here --- app/services/definitions.py | 128 ++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/app/services/definitions.py b/app/services/definitions.py index c23ec1de6..4064759d2 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -276,3 +276,131 @@ async def schema_futures( ] return schemas + + +async def create_cred_def( + logger: Logger, + aries_controller: AcaPyClient, + credential_definition: CreateCredentialDefinition, + support_revocation: bool, +) -> str: + """ + Create a credential definition + """ + + # Assert the agent has a public did + logger.debug("Asserting client has public DID") + try: + public_did = await acapy_wallet.assert_public_did(aries_controller) + except CloudApiException as e: + log_message = f"Asserting public DID failed: {e}" + + if e.status_code == 403: + logger.info(log_message) + client_error_message = ( + "Wallet making this request has no public DID. " + "Only issuers with a public DID can make this request." + ) + + else: + logger.error(log_message) + client_error_message = ( + "Something went wrong while asserting if request is from a valid issuer. " + "Please try again." + ) + raise CloudApiException(client_error_message, e.status_code) from e + + # Make sure we are allowed to issue this schema according to trust registry rules + logger.debug("Asserting client is a valid issuer") + await assert_valid_issuer(public_did, credential_definition.schema_id) + + if support_revocation: + endorser_connection = await handle_acapy_call( + logger=logger, + acapy_call=aries_controller.connection.get_connections, + alias=ACAPY_ENDORSER_ALIAS, + ) + has_connections = len(endorser_connection.results) > 0 + + if not has_connections: + logger.error( + "Failed to create credential definition supporting revocation: no endorser connection found. " + "Issuer attempted to create a credential definition with support for revocation but does not " + "have an active connection with an endorser, which is required for this operation." + ) + + raise CloudApiException( + "Credential definition creation failed: An active endorser connection is required " + "to support revocation. Please establish a connection with an endorser and try again." + ) + + logger.debug("Publishing credential definition") + request_body = handle_model_with_validation( + logger=logger, + model_class=CredentialDefinitionSendRequest, + schema_id=credential_definition.schema_id, + support_revocation=support_revocation, + tag=credential_definition.tag, + revocation_registry_size=32767, + ) + try: + result = await handle_acapy_call( + logger=logger, + acapy_call=aries_controller.credential_definition.publish_cred_def, + body=request_body, + ) + credential_definition_id = result.sent.credential_definition_id + except CloudApiException as e: + logger.warning( + "An Exception was caught while publishing credential definition: `{}` `{}`", + e.detail, + e.status_code, + ) + if "already exists" in e.detail: + raise CloudApiException(status_code=409, detail=e.detail) from e + else: + raise CloudApiException( + detail=f"Error while creating credential definition: {e.detail}", + status_code=e.status_code, + ) from e + + # Wait for cred_def transaction to be acknowledged + if result.txn and result.txn.transaction_id: + logger.debug( + "The publish credential definition response provides a transaction id. " + "Waiting for transaction to be in state `transaction_acked`" + ) + try: + # Wait for transaction to be acknowledged and written to the ledger + await coroutine_with_retry_until_value( + coroutine_func=aries_controller.endorse_transaction.get_transaction, + args=(result.txn.transaction_id,), + field_name="state", + expected_value="transaction_acked", + logger=logger, + max_attempts=10, + retry_delay=2, + ) + except asyncio.TimeoutError as e: + raise CloudApiException( + "Timeout waiting for endorser to accept the endorsement request.", + 504, + ) from e + logger.debug("Transaction has been acknowledged by the endorser") + + # Wait for revocation registry creation + if support_revocation: + try: + logger.debug("Waiting for revocation registry creation") + await asyncio.wait_for( + wait_for_active_registry(aries_controller, credential_definition_id), + timeout=REGISTRY_CREATION_TIMEOUT, + ) + except asyncio.TimeoutError as e: + logger.error("Timeout waiting for revocation registry creation.") + raise CloudApiException( + "Timeout waiting for revocation registry creation.", + 504, + ) from e + + return credential_definition_id From 324d95e80edb2549344dfb3f9ce9748c20576827 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 19 Jun 2024 17:16:58 +0200 Subject: [PATCH 11/74] moved get cred_def logic here --- app/services/definitions.py | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/app/services/definitions.py b/app/services/definitions.py index 4064759d2..67d7efd81 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -404,3 +404,60 @@ async def create_cred_def( ) from e return credential_definition_id + + +async def get_cred_defs( + logger: Logger, + aries_controller: AcaPyClient, + issuer_did: Optional[str], + credential_definition_id: Optional[str], + schema_id: Optional[str], + schema_issuer_did: Optional[str], + schema_name: Optional[str], + schema_version: Optional[str], +) -> List[CredentialDefinition]: + """ + Get credential definitions + """ + + logger.debug("Getting created credential definitions") + response = await handle_acapy_call( + logger=logger, + acapy_call=aries_controller.credential_definition.get_created_cred_defs, + issuer_did=issuer_did, + cred_def_id=credential_definition_id, + schema_id=schema_id, + schema_issuer_did=schema_issuer_did, + schema_name=schema_name, + schema_version=schema_version, + ) + + # Initiate retrieving all credential definitions + credential_definition_ids = response.credential_definition_ids or [] + get_credential_definition_futures = [ + handle_acapy_call( + logger=logger, + acapy_call=aries_controller.credential_definition.get_cred_def, + cred_def_id=credential_definition_id, + ) + for credential_definition_id in credential_definition_ids + ] + + # Wait for completion of retrieval and transform all credential definitions + # into response model (if a credential definition was returned) + if get_credential_definition_futures: + logger.debug("Getting definitions from fetched credential ids") + credential_definition_results = await asyncio.gather( + *get_credential_definition_futures + ) + else: + logger.debug("No definition ids returned") + credential_definition_results = [] + + credential_definitions = [ + credential_definition_from_acapy(credential_definition.credential_definition) + for credential_definition in credential_definition_results + if credential_definition.credential_definition + ] + + return credential_definitions From db01a9546e3c852451503ec28a948e0ab9ab1437 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Sat, 22 Jun 2024 13:13:10 +0200 Subject: [PATCH 12/74] import dataclass for service dependencies --- app/services/definitions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/definitions.py b/app/services/definitions.py index 67d7efd81..1ed60c631 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -1,4 +1,5 @@ import asyncio +from dataclasses import dataclass from logging import Logger from typing import List, Optional From 2a6480f1b91baa2de117ebea8d7f25af357da635 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Sat, 22 Jun 2024 13:14:40 +0200 Subject: [PATCH 13/74] add service dependencies dataclass --- app/services/definitions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/definitions.py b/app/services/definitions.py index 1ed60c631..31967ad31 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -38,10 +38,10 @@ from shared import ACAPY_ENDORSER_ALIAS, REGISTRY_CREATION_TIMEOUT -async def create_schema_service( - logger: Logger, - aries_controller: AcaPyClient, - schema_request: SchemaSendRequest, +@dataclass +class ServiceDependencies: + logger: Logger + aries_controller: AcaPyClient schema: CreateSchema, ) -> CredentialSchema: """ From 4a907ec50885b467b735f9626a392634ed9d4650 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Sat, 22 Jun 2024 13:15:30 +0200 Subject: [PATCH 14/74] add schema publisher class --- app/services/definitions.py | 131 +++++++++++++++++------------------- 1 file changed, 63 insertions(+), 68 deletions(-) diff --git a/app/services/definitions.py b/app/services/definitions.py index 31967ad31..ed5ddff55 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -42,85 +42,80 @@ class ServiceDependencies: logger: Logger aries_controller: AcaPyClient - schema: CreateSchema, -) -> CredentialSchema: - """ - Create a schema and register it in the trust registry - """ - try: - logger.info("Publishing schema as governance") + + +class SchemaPublisher: + def __init__(self, deps: ServiceDependencies): + self.deps = deps + + async def publish_schema(self, schema_request: SchemaSendRequest): result = await handle_acapy_call( - logger=logger, - acapy_call=aries_controller.schema.publish_schema, + logger=self.deps.logger, + acapy_call=self.deps.aries_controller.schema.publish_schema, body=schema_request, create_transaction_for_endorser=False, ) + return result + + async def handle_existing_schema(self, schema: CreateSchema): + self.deps.logger.info("Handling case of schema already existing on ledger") + self.deps.logger.debug("Fetching public DID for governance controller") + pub_did = await handle_acapy_call( + logger=self.deps.logger, + acapy_call=self.deps.aries_controller.wallet.get_public_did, + ) - except CloudApiException as e: - logger.info( - "An Exception was caught while trying to publish schema: `{}`", - e.detail, + _schema_id = f"{pub_did.result.did}:2:{schema.name}:{schema.version}" + self.deps.logger.debug( + "Fetching schema id `{}` which is associated with request", + _schema_id, ) - if e.status_code == 400 and "already exist" in e.detail: - logger.info("Handling case of schema already existing on ledger") - logger.debug("Fetching public DID for governance controller") - pub_did = await handle_acapy_call( - logger=logger, - acapy_call=aries_controller.wallet.get_public_did, - ) + _schema: SchemaGetResult = await handle_acapy_call( + logger=self.deps.logger, + acapy_call=self.deps.aries_controller.schema.get_schema, + schema_id=_schema_id, + ) - _schema_id = f"{pub_did.result.did}:2:{schema.name}:{schema.version}" - logger.debug( - "Fetching schema id `{}` which is associated with request", - _schema_id, + # Edge case where the governance agent has changed its public did + # Then we need to retrieve the schema in a different way as constructing the schema ID the way above + # will not be correct due to different public did. + if _schema.var_schema is None: + self.deps.logger.debug( + "Schema not found. Governance agent may have changed public DID. " + "Fetching schemas created by governance agent with request name and version" ) - _schema: SchemaGetResult = await handle_acapy_call( - logger=logger, - acapy_call=aries_controller.schema.get_schema, - schema_id=_schema_id, + schemas_created_ids = await handle_acapy_call( + logger=self.deps.logger, + acapy_call=self.deps.aries_controller.schema.get_created_schemas, + schema_name=schema.name, + schema_version=schema.version, ) - - # Edge case where the governance agent has changed its public did - # Then we need to retrieve the schema in a different way as constructing the schema ID the way above - # will not be correct due to different public did. - if _schema.var_schema is None: - logger.debug( - "Schema not found. Governance agent may have changed public DID. " - "Fetching schemas created by governance agent with request name and version" - ) - schemas_created_ids = await handle_acapy_call( - logger=logger, - acapy_call=aries_controller.schema.get_created_schemas, - schema_name=schema.name, - schema_version=schema.version, + self.deps.logger.debug("Getting schemas associated with fetched ids") + schemas: List[SchemaGetResult] = [ + await handle_acapy_call( + logger=self.deps.logger, + acapy_call=self.deps.aries_controller.schema.get_schema, + schema_id=schema_id, ) - logger.debug("Getting schemas associated with fetched ids") - schemas: List[SchemaGetResult] = [ - await handle_acapy_call( - logger=logger, - acapy_call=aries_controller.schema.get_schema, - schema_id=schema_id, + for schema_id in schemas_created_ids.schema_ids + if schema_id + ] + + if schemas: + if len(schemas) > 1: + raise CloudApiException( # pylint: disable=W0707 + f"Multiple schemas with name {schema.name} and version {schema.version} exist." + + f"These are: `{str(schemas_created_ids.schema_ids)}`.", + 409, ) - for schema_id in schemas_created_ids.schema_ids - if schema_id - ] - - if schemas: - if len(schemas) > 1: - raise CloudApiException( # pylint: disable=W0707 - f"Multiple schemas with name {schema.name} and version {schema.version} exist." - + f"These are: `{str(schemas_created_ids.schema_ids)}`.", - 409, - ) - - logger.debug("Using updated schema id with new DID") - _schema: SchemaGetResult = schemas[0] - else: - # if schema already exists, we should at least fetch 1, so this should never happen - raise CloudApiException( - "Could not publish schema.", 500 - ) # pylint: disable=W0707 + self.deps.logger.debug("Using updated schema id with new DID") + _schema: SchemaGetResult = schemas[0] + else: + # if schema already exists, we should at least fetch 1, so this should never happen + raise CloudApiException( + "Could not publish schema.", 500 + ) # pylint: disable=W0707 # Schema exists with different attributes if set(_schema.var_schema.attr_names) != set(schema.attribute_names): @@ -132,7 +127,7 @@ class ServiceDependencies: ) # pylint: disable=W0707 result = credential_schema_from_acapy(_schema.var_schema) - logger.info( + self.deps.logger.info( "Schema already exists on ledger. Returning schema definition: `{}`.", result, ) From 600783b9219c7660f1ca6c90d74af7da2e904a9a Mon Sep 17 00:00:00 2001 From: cl0ete Date: Sat, 22 Jun 2024 13:22:03 +0200 Subject: [PATCH 15/74] add class to publish schema to trust registry --- app/services/definitions.py | 54 ++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/app/services/definitions.py b/app/services/definitions.py index ed5ddff55..5dfa129df 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -133,39 +133,31 @@ async def handle_existing_schema(self, schema: CreateSchema): ) return result - else: - logger.warning( - "An unhandled Exception was caught while publishing schema. The error message is: '{}'.", - e.detail, - ) - raise CloudApiException("Error while creating schema.") from e - # Register the schema in the trust registry - try: - if result.sent and result.sent.schema_id: - logger.debug("Registering schema after successful publish to ledger") - await register_schema(schema_id=result.sent.schema_id) - else: - logger.error("No SchemaSendResult in `publish_schema` response.") - raise CloudApiException( - "An unexpected error occurred: could not publish schema." - ) - except TrustRegistryException as error: - # If status_code is 405 it means the schema already exists in the trust registry - # That's okay, because we've achieved our intended result: - # make sure the schema is registered in the trust registry - logger.info( - "Caught TrustRegistryException when registering schema. " - "Got status code {} with message `{}`", - error.status_code, - error.detail, - ) - if error.status_code == 405: - logger.info( - "Status code 405 indicates schema is already registered, so we can continue" +class SchemaRegistrar: + def __init__(self, deps: ServiceDependencies): + self.deps = deps + + async def register_schema(self, schema_id: str): + self.deps.logger.debug("Registering schema after successful publish to ledger") + try: + await register_schema(schema_id=schema_id) + except TrustRegistryException as error: + # If status_code is 405 it means the schema already exists in the trust registry + # That's okay, because we've achieved our intended result: + # make sure the schema is registered in the trust registry + self.deps.logger.info( + "Caught TrustRegistryException when registering schema. " + "Got status code {} with message `{}`", + error.status_code, + error.detail, ) - else: - raise error + if error.status_code == 405: + self.deps.logger.info( + "Status code 405 indicates schema is already registered, so we can continue" + ) + else: + raise error result = credential_schema_from_acapy(result.sent.var_schema) logger.info("Successfully published and registered schema.") From 62aea63dd11ec1ca84a6796530f499b53ec544f2 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Sat, 22 Jun 2024 13:22:40 +0200 Subject: [PATCH 16/74] refactor create schema service --- app/services/definitions.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/app/services/definitions.py b/app/services/definitions.py index 5dfa129df..f4ca4fc8d 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -159,9 +159,41 @@ async def register_schema(self, schema_id: str): else: raise error + +async def create_schema_service( + logger: Logger, + aries_controller: AcaPyClient, + schema_request: SchemaSendRequest, + schema: CreateSchema, +) -> CredentialSchema: + """ + Create a schema and register it in the trust registry + """ + deps = ServiceDependencies(logger, aries_controller) + publisher = SchemaPublisher(deps) + registrar = SchemaRegistrar(deps) + + try: + result = await publisher.publish_schema(schema_request) + except CloudApiException as e: + if "already exist" in e.detail and e.status_code == 400: + result = await publisher.handle_existing_schema(schema) + else: + logger.warning( + f"An unhandled Exception was caught while publishing schema: {e.detail}" + ) + raise CloudApiException("Error while creating schema.") from e + + if result.sent and result.sent.schema_id: + await registrar.register_schema(result.sent.schema_id) + else: + logger.error("No SchemaSendResult in `publish_schema` response.") + raise CloudApiException( + "An unexpected error occurred: could not publish schema." + ) + result = credential_schema_from_acapy(result.sent.var_schema) logger.info("Successfully published and registered schema.") - return result From ac5409fbb961c1d5af03eb2c3641fed62c753209 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Sat, 22 Jun 2024 13:23:28 +0200 Subject: [PATCH 17/74] add helper class cred def publisher --- app/services/definitions.py | 92 +++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/app/services/definitions.py b/app/services/definitions.py index f4ca4fc8d..af6d271e9 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -298,6 +298,98 @@ async def schema_futures( return schemas +class CredDefPublisher: + def __init__(self, deps: ServiceDependencies): + self.deps = deps + + async def assert_public_did(self): + public_did = await acapy_wallet.assert_public_did(self.deps.aries_controller) + return public_did + + async def check_endorser_connection(self): + endorser_connection = await handle_acapy_call( + logger=self.deps.logger, + acapy_call=self.deps.aries_controller.connection.get_connections, + alias=ACAPY_ENDORSER_ALIAS, + ) + has_connections = len(endorser_connection.results) > 0 + + if not has_connections: + self.deps.logger.error( + "Failed to create credential definition supporting revocation: no endorser connection found. " + "Issuer attempted to create a credential definition with support for revocation but does not " + "have an active connection with an endorser, which is required for this operation." + ) + + raise CloudApiException( + "Credential definition creation failed: An active endorser connection is required " + "to support revocation. Please establish a connection with an endorser and try again." + ) + + async def publish_credential_definition(self, request_body): + try: + result = await handle_acapy_call( + logger=self.deps.logger, + acapy_call=self.deps.aries_controller.credential_definition.publish_cred_def, + body=request_body, + ) + + except CloudApiException as e: + self.deps.logger.warning( + "An Exception was caught while publishing credential definition: `{}` `{}`", + e.detail, + e.status_code, + ) + if "already exists" in e.detail: + raise CloudApiException(status_code=409, detail=e.detail) from e + else: + raise CloudApiException( + detail=f"Error while creating credential definition: {e.detail}", + status_code=e.status_code, + ) from e + + return result + + async def wait_for_transaction_ack(self, transaction_id): + self.deps.logger.debug( + "The publish credential definition response provides a transaction id. " + "Waiting for transaction to be in state `transaction_acked`" + ) + try: + # Wait for transaction to be acknowledged and written to the ledger + await coroutine_with_retry_until_value( + coroutine_func=self.deps.aries_controller.endorse_transaction.get_transaction, + args=(transaction_id,), + field_name="state", + expected_value="transaction_acked", + logger=self.deps.logger, + max_attempts=10, + retry_delay=2, + ) + except asyncio.TimeoutError as e: + raise CloudApiException( + "Timeout waiting for endorser to accept the endorsement request.", + 504, + ) from e + self.deps.logger.debug("Transaction has been acknowledged by the endorser") + + async def wait_for_revocation_registry(self, credential_definition_id): + try: + self.deps.logger.debug("Waiting for revocation registry creation") + await asyncio.wait_for( + wait_for_active_registry( + self.deps.aries_controller, credential_definition_id + ), + timeout=REGISTRY_CREATION_TIMEOUT, + ) + except asyncio.TimeoutError as e: + self.deps.logger.error("Timeout waiting for revocation registry creation.") + raise CloudApiException( + "Timeout waiting for revocation registry creation.", + 504, + ) from e + + async def create_cred_def( logger: Logger, aries_controller: AcaPyClient, From 33d2ec2d254edc1be4d534788e6bc42fa720b0bc Mon Sep 17 00:00:00 2001 From: cl0ete Date: Sat, 22 Jun 2024 13:24:05 +0200 Subject: [PATCH 18/74] refactor create cred def service --- app/services/definitions.py | 89 +++++-------------------------------- 1 file changed, 10 insertions(+), 79 deletions(-) diff --git a/app/services/definitions.py b/app/services/definitions.py index af6d271e9..467cde8db 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -399,11 +399,12 @@ async def create_cred_def( """ Create a credential definition """ + deps = ServiceDependencies(logger, aries_controller) + publisher = CredDefPublisher(deps) - # Assert the agent has a public did - logger.debug("Asserting client has public DID") try: - public_did = await acapy_wallet.assert_public_did(aries_controller) + logger.debug("Asserting client has public DID") + public_did = await publisher.assert_public_did() except CloudApiException as e: log_message = f"Asserting public DID failed: {e}" @@ -422,31 +423,11 @@ async def create_cred_def( ) raise CloudApiException(client_error_message, e.status_code) from e - # Make sure we are allowed to issue this schema according to trust registry rules - logger.debug("Asserting client is a valid issuer") await assert_valid_issuer(public_did, credential_definition.schema_id) if support_revocation: - endorser_connection = await handle_acapy_call( - logger=logger, - acapy_call=aries_controller.connection.get_connections, - alias=ACAPY_ENDORSER_ALIAS, - ) - has_connections = len(endorser_connection.results) > 0 - - if not has_connections: - logger.error( - "Failed to create credential definition supporting revocation: no endorser connection found. " - "Issuer attempted to create a credential definition with support for revocation but does not " - "have an active connection with an endorser, which is required for this operation." - ) - - raise CloudApiException( - "Credential definition creation failed: An active endorser connection is required " - "to support revocation. Please establish a connection with an endorser and try again." - ) + await publisher.check_endorser_connection() - logger.debug("Publishing credential definition") request_body = handle_model_with_validation( logger=logger, model_class=CredentialDefinitionSendRequest, @@ -455,65 +436,15 @@ async def create_cred_def( tag=credential_definition.tag, revocation_registry_size=32767, ) - try: - result = await handle_acapy_call( - logger=logger, - acapy_call=aries_controller.credential_definition.publish_cred_def, - body=request_body, - ) - credential_definition_id = result.sent.credential_definition_id - except CloudApiException as e: - logger.warning( - "An Exception was caught while publishing credential definition: `{}` `{}`", - e.detail, - e.status_code, - ) - if "already exists" in e.detail: - raise CloudApiException(status_code=409, detail=e.detail) from e - else: - raise CloudApiException( - detail=f"Error while creating credential definition: {e.detail}", - status_code=e.status_code, - ) from e - # Wait for cred_def transaction to be acknowledged + result = await publisher.publish_credential_definition(request_body) + credential_definition_id = result.sent.credential_definition_id + if result.txn and result.txn.transaction_id: - logger.debug( - "The publish credential definition response provides a transaction id. " - "Waiting for transaction to be in state `transaction_acked`" - ) - try: - # Wait for transaction to be acknowledged and written to the ledger - await coroutine_with_retry_until_value( - coroutine_func=aries_controller.endorse_transaction.get_transaction, - args=(result.txn.transaction_id,), - field_name="state", - expected_value="transaction_acked", - logger=logger, - max_attempts=10, - retry_delay=2, - ) - except asyncio.TimeoutError as e: - raise CloudApiException( - "Timeout waiting for endorser to accept the endorsement request.", - 504, - ) from e - logger.debug("Transaction has been acknowledged by the endorser") + await publisher.wait_for_transaction_ack(result.txn.transaction_id) - # Wait for revocation registry creation if support_revocation: - try: - logger.debug("Waiting for revocation registry creation") - await asyncio.wait_for( - wait_for_active_registry(aries_controller, credential_definition_id), - timeout=REGISTRY_CREATION_TIMEOUT, - ) - except asyncio.TimeoutError as e: - logger.error("Timeout waiting for revocation registry creation.") - raise CloudApiException( - "Timeout waiting for revocation registry creation.", - 504, - ) from e + await publisher.wait_for_revocation_registry(credential_definition_id) return credential_definition_id From 1e2d6701059d26f02a3719a9cc77691ac754d7e6 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Sat, 22 Jun 2024 13:29:58 +0200 Subject: [PATCH 19/74] move to helper class --- app/services/definitions.py | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/app/services/definitions.py b/app/services/definitions.py index 467cde8db..0d05656f3 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -303,7 +303,26 @@ def __init__(self, deps: ServiceDependencies): self.deps = deps async def assert_public_did(self): - public_did = await acapy_wallet.assert_public_did(self.deps.aries_controller) + try: + self.deps.logger.debug("Asserting client has public DID") + public_did = await acapy_wallet.assert_public_did(self.deps.aries_controller) + except CloudApiException as e: + log_message = f"Asserting public DID failed: {e}" + + if e.status_code == 403: + self.deps.logger.info(log_message) + client_error_message = ( + "Wallet making this request has no public DID. " + "Only issuers with a public DID can make this request." + ) + + else: + self.deps.logger.error(log_message) + client_error_message = ( + "Something went wrong while asserting if request is from a valid issuer. " + "Please try again." + ) + raise CloudApiException(client_error_message, e.status_code) from e return public_did async def check_endorser_connection(self): @@ -402,26 +421,7 @@ async def create_cred_def( deps = ServiceDependencies(logger, aries_controller) publisher = CredDefPublisher(deps) - try: - logger.debug("Asserting client has public DID") - public_did = await publisher.assert_public_did() - except CloudApiException as e: - log_message = f"Asserting public DID failed: {e}" - - if e.status_code == 403: - logger.info(log_message) - client_error_message = ( - "Wallet making this request has no public DID. " - "Only issuers with a public DID can make this request." - ) - - else: - logger.error(log_message) - client_error_message = ( - "Something went wrong while asserting if request is from a valid issuer. " - "Please try again." - ) - raise CloudApiException(client_error_message, e.status_code) from e + public_did = await publisher.assert_public_did() await assert_valid_issuer(public_did, credential_definition.schema_id) From 5b7eb8a758dea8b2083130f6a5148a91cbb598b2 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Sat, 22 Jun 2024 13:30:14 +0200 Subject: [PATCH 20/74] formatting --- app/services/definitions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/services/definitions.py b/app/services/definitions.py index 0d05656f3..696f3992b 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -305,7 +305,9 @@ def __init__(self, deps: ServiceDependencies): async def assert_public_did(self): try: self.deps.logger.debug("Asserting client has public DID") - public_did = await acapy_wallet.assert_public_did(self.deps.aries_controller) + public_did = await acapy_wallet.assert_public_did( + self.deps.aries_controller + ) except CloudApiException as e: log_message = f"Asserting public DID failed: {e}" From 6e231bae557b38634149f19778b5e8656f677560 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Mon, 24 Jun 2024 13:44:36 +0200 Subject: [PATCH 21/74] rework logging --- app/routes/definitions.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index 74061c978..0e9a0e979 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -85,7 +85,6 @@ async def create_schema( ) async with get_governance_controller(governance_auth) as aries_controller: schema_response = await create_schema_service( - logger=bound_logger, aries_controller=aries_controller, schema_request=schema_send_request, schema=schema, @@ -141,11 +140,11 @@ async def get_schemas( "schema_version": schema_version, } ) + bound_logger.info("GET request received: Get created schemas") async with client_from_auth(auth) as aries_controller: if not is_governance: # regular tenant is calling endpoint schemas = await get_schemas_tenant( - logger=bound_logger, aries_controller=aries_controller, schema_id=schema_id, schema_issuer_did=schema_issuer_did, @@ -155,7 +154,6 @@ async def get_schemas( else: # Governance is calling the endpoint schemas = await get_schemas_governance( - logger=bound_logger, aries_controller=aries_controller, schema_id=schema_id, schema_issuer_did=schema_issuer_did, @@ -265,7 +263,6 @@ async def create_credential_definition( async with client_from_auth(auth) as aries_controller: credential_definition_id = await create_cred_def( - logger=bound_logger, aries_controller=aries_controller, credential_definition=credential_definition, support_revocation=support_revocation, @@ -339,7 +336,6 @@ async def get_credential_definitions( # Get all created credential definition ids that match the filter async with client_from_auth(auth) as aries_controller: credential_definitions = await get_cred_defs( - logger=bound_logger, aries_controller=aries_controller, issuer_did=issuer_did, credential_definition_id=credential_definition_id, From 54d9f5caa923f396afcf9068941475b3834a22c9 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Mon, 24 Jun 2024 13:49:33 +0200 Subject: [PATCH 22/74] redo logging not passing logger down --- app/services/definitions.py | 79 ++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 22 deletions(-) diff --git a/app/services/definitions.py b/app/services/definitions.py index 696f3992b..7fdb37493 100644 --- a/app/services/definitions.py +++ b/app/services/definitions.py @@ -36,6 +36,9 @@ ) from app.util.retry_method import coroutine_with_retry_until_value from shared import ACAPY_ENDORSER_ALIAS, REGISTRY_CREATION_TIMEOUT +from shared.log_config import get_logger + +logger = get_logger(__name__) @dataclass @@ -161,7 +164,6 @@ async def register_schema(self, schema_id: str): async def create_schema_service( - logger: Logger, aries_controller: AcaPyClient, schema_request: SchemaSendRequest, schema: CreateSchema, @@ -169,7 +171,9 @@ async def create_schema_service( """ Create a schema and register it in the trust registry """ - deps = ServiceDependencies(logger, aries_controller) + bound_logger = logger.bind(body=schema) + + deps = ServiceDependencies(bound_logger, aries_controller) publisher = SchemaPublisher(deps) registrar = SchemaRegistrar(deps) @@ -179,7 +183,7 @@ async def create_schema_service( if "already exist" in e.detail and e.status_code == 400: result = await publisher.handle_existing_schema(schema) else: - logger.warning( + bound_logger.warning( f"An unhandled Exception was caught while publishing schema: {e.detail}" ) raise CloudApiException("Error while creating schema.") from e @@ -187,18 +191,17 @@ async def create_schema_service( if result.sent and result.sent.schema_id: await registrar.register_schema(result.sent.schema_id) else: - logger.error("No SchemaSendResult in `publish_schema` response.") + bound_logger.error("No SchemaSendResult in `publish_schema` response.") raise CloudApiException( "An unexpected error occurred: could not publish schema." ) result = credential_schema_from_acapy(result.sent.var_schema) - logger.info("Successfully published and registered schema.") + bound_logger.info("Successfully published and registered schema.") return result async def get_schemas_tenant( - logger: Logger, aries_controller: AcaPyClient, schema_id: Optional[str], schema_issuer_did: Optional[str], @@ -208,7 +211,15 @@ async def get_schemas_tenant( """ Allows tenants to get all schemas created """ - logger.info("GET request received: Get created schemas") + bound_logger = logger.bind( + body={ + "schema_id": schema_id, + "schema_issuer_did": schema_issuer_did, + "schema_name": schema_name, + "schema_version": schema_version, + } + ) + bound_logger.debug("Fetching schemas from trust registry") if not schema_id: # client is not filtering by schema_id, fetch all trust_registry_schemas = await get_trust_registry_schemas() @@ -217,7 +228,8 @@ async def get_schemas_tenant( schema_ids = [schema.id for schema in trust_registry_schemas] - schemas = await schema_futures(logger, schema_ids, aries_controller) + bound_logger.debug("Getting schemas associated with fetched ids") + schemas = await schema_futures(schema_ids, aries_controller) if schema_issuer_did: schemas = [ @@ -232,7 +244,6 @@ async def get_schemas_tenant( async def get_schemas_governance( - logger: Logger, aries_controller: AcaPyClient, schema_id: Optional[str], schema_issuer_did: Optional[str], @@ -242,11 +253,18 @@ async def get_schemas_governance( """ Governance agents gets all schemas created by itself """ - logger.info("GET request received: Get schemas created by governance client") + bound_logger = logger.bind( + body={ + "schema_id": schema_id, + "schema_issuer_did": schema_issuer_did, + "schema_name": schema_name, + "schema_version": schema_version, + } + ) # Get all created schema ids that match the filter - logger.debug("Fetching created schemas") + bound_logger.debug("Fetching created schemas") response = await handle_acapy_call( - logger=logger, + logger=bound_logger, acapy_call=aries_controller.schema.get_created_schemas, schema_id=schema_id, schema_issuer_did=schema_issuer_did, @@ -257,17 +275,19 @@ async def get_schemas_governance( # Initiate retrieving all schemas schema_ids = response.schema_ids or [] - schemas = await schema_futures(logger, schema_ids, aries_controller) + bound_logger.debug("Getting schemas associated with fetched ids") + schemas = await schema_futures(schema_ids, aries_controller) return schemas async def schema_futures( - logger: Logger, schema_ids: List[str], aries_controller: AcaPyClient + schema_ids: List[str], aries_controller: AcaPyClient ) -> List[CredentialSchema]: """ Get schemas with attributes from schema ids """ + logger.debug("Fetching schemas from schema ids") # We now have schema_ids; the following logic is the same whether called by governance or tenant. # Now fetch relevant schemas from ledger: get_schema_futures = [ @@ -412,7 +432,6 @@ async def wait_for_revocation_registry(self, credential_definition_id): async def create_cred_def( - logger: Logger, aries_controller: AcaPyClient, credential_definition: CreateCredentialDefinition, support_revocation: bool, @@ -420,7 +439,14 @@ async def create_cred_def( """ Create a credential definition """ - deps = ServiceDependencies(logger, aries_controller) + bound_logger = logger.bind( + body={ + "schema_id": credential_definition.schema_id, + "tag": credential_definition.tag, + "support_revocation": credential_definition.support_revocation, + } + ) + deps = ServiceDependencies(bound_logger, aries_controller) publisher = CredDefPublisher(deps) public_did = await publisher.assert_public_did() @@ -452,7 +478,6 @@ async def create_cred_def( async def get_cred_defs( - logger: Logger, aries_controller: AcaPyClient, issuer_did: Optional[str], credential_definition_id: Optional[str], @@ -464,10 +489,20 @@ async def get_cred_defs( """ Get credential definitions """ + bound_logger = logger.bind( + body={ + "issuer_did": issuer_did, + "credential_definition_id": credential_definition_id, + "schema_id": schema_id, + "schema_issuer_did": schema_issuer_did, + "schema_name": schema_name, + "schema_version": schema_version, + } + ) + bound_logger.debug("Getting created credential definitions") - logger.debug("Getting created credential definitions") response = await handle_acapy_call( - logger=logger, + logger=bound_logger, acapy_call=aries_controller.credential_definition.get_created_cred_defs, issuer_did=issuer_did, cred_def_id=credential_definition_id, @@ -481,7 +516,7 @@ async def get_cred_defs( credential_definition_ids = response.credential_definition_ids or [] get_credential_definition_futures = [ handle_acapy_call( - logger=logger, + logger=bound_logger, acapy_call=aries_controller.credential_definition.get_cred_def, cred_def_id=credential_definition_id, ) @@ -491,12 +526,12 @@ async def get_cred_defs( # Wait for completion of retrieval and transform all credential definitions # into response model (if a credential definition was returned) if get_credential_definition_futures: - logger.debug("Getting definitions from fetched credential ids") + bound_logger.debug("Getting definitions from fetched credential ids") credential_definition_results = await asyncio.gather( *get_credential_definition_futures ) else: - logger.debug("No definition ids returned") + bound_logger.debug("No definition ids returned") credential_definition_results = [] credential_definitions = [ From 1f6ce47b03a98f1500134db6eef8bf4149a0ff01 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 25 Jun 2024 14:44:32 +0200 Subject: [PATCH 23/74] update names --- app/routes/definitions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index 0e9a0e979..b2fe32441 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -19,12 +19,12 @@ CredentialDefinition, CredentialSchema, ) -from app.services.definitions import ( +from app.services.definitions.definitions import ( create_cred_def, create_schema_service, get_cred_defs, - get_schemas_governance, - get_schemas_tenant, + get_schemas_as_governance, + get_schemas_as_tenant, ) from app.util.definitions import ( credential_definition_from_acapy, @@ -144,7 +144,7 @@ async def get_schemas( async with client_from_auth(auth) as aries_controller: if not is_governance: # regular tenant is calling endpoint - schemas = await get_schemas_tenant( + schemas = await get_schemas_as_tenant( aries_controller=aries_controller, schema_id=schema_id, schema_issuer_did=schema_issuer_did, @@ -153,7 +153,7 @@ async def get_schemas( ) else: # Governance is calling the endpoint - schemas = await get_schemas_governance( + schemas = await get_schemas_as_governance( aries_controller=aries_controller, schema_id=schema_id, schema_issuer_did=schema_issuer_did, From db0e3b5a73024c49a2f8c1ec0318baa4a281aca4 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 25 Jun 2024 14:45:10 +0200 Subject: [PATCH 24/74] move to definitions folder --- app/services/definitions.py | 543 ------------------------ app/services/definitions/definitions.py | 315 ++++++++++++++ 2 files changed, 315 insertions(+), 543 deletions(-) delete mode 100644 app/services/definitions.py create mode 100644 app/services/definitions/definitions.py diff --git a/app/services/definitions.py b/app/services/definitions.py deleted file mode 100644 index 7fdb37493..000000000 --- a/app/services/definitions.py +++ /dev/null @@ -1,543 +0,0 @@ -import asyncio -from dataclasses import dataclass -from logging import Logger -from typing import List, Optional - -from aries_cloudcontroller import ( - AcaPyClient, - CredentialDefinitionSendRequest, - SchemaGetResult, - SchemaSendRequest, -) - -from app.exceptions import ( - CloudApiException, - TrustRegistryException, - handle_acapy_call, - handle_model_with_validation, -) -from app.models.definitions import ( - CreateCredentialDefinition, - CreateSchema, - CredentialDefinition, - CredentialSchema, -) -from app.routes.trust_registry import ( - get_schema_by_id as get_trust_registry_schema_by_id, -) -from app.routes.trust_registry import get_schemas as get_trust_registry_schemas -from app.services import acapy_wallet -from app.services.revocation_registry import wait_for_active_registry -from app.services.trust_registry.schemas import register_schema -from app.services.trust_registry.util.issuer import assert_valid_issuer -from app.util.definitions import ( - credential_definition_from_acapy, - credential_schema_from_acapy, -) -from app.util.retry_method import coroutine_with_retry_until_value -from shared import ACAPY_ENDORSER_ALIAS, REGISTRY_CREATION_TIMEOUT -from shared.log_config import get_logger - -logger = get_logger(__name__) - - -@dataclass -class ServiceDependencies: - logger: Logger - aries_controller: AcaPyClient - - -class SchemaPublisher: - def __init__(self, deps: ServiceDependencies): - self.deps = deps - - async def publish_schema(self, schema_request: SchemaSendRequest): - result = await handle_acapy_call( - logger=self.deps.logger, - acapy_call=self.deps.aries_controller.schema.publish_schema, - body=schema_request, - create_transaction_for_endorser=False, - ) - return result - - async def handle_existing_schema(self, schema: CreateSchema): - self.deps.logger.info("Handling case of schema already existing on ledger") - self.deps.logger.debug("Fetching public DID for governance controller") - pub_did = await handle_acapy_call( - logger=self.deps.logger, - acapy_call=self.deps.aries_controller.wallet.get_public_did, - ) - - _schema_id = f"{pub_did.result.did}:2:{schema.name}:{schema.version}" - self.deps.logger.debug( - "Fetching schema id `{}` which is associated with request", - _schema_id, - ) - - _schema: SchemaGetResult = await handle_acapy_call( - logger=self.deps.logger, - acapy_call=self.deps.aries_controller.schema.get_schema, - schema_id=_schema_id, - ) - - # Edge case where the governance agent has changed its public did - # Then we need to retrieve the schema in a different way as constructing the schema ID the way above - # will not be correct due to different public did. - if _schema.var_schema is None: - self.deps.logger.debug( - "Schema not found. Governance agent may have changed public DID. " - "Fetching schemas created by governance agent with request name and version" - ) - schemas_created_ids = await handle_acapy_call( - logger=self.deps.logger, - acapy_call=self.deps.aries_controller.schema.get_created_schemas, - schema_name=schema.name, - schema_version=schema.version, - ) - self.deps.logger.debug("Getting schemas associated with fetched ids") - schemas: List[SchemaGetResult] = [ - await handle_acapy_call( - logger=self.deps.logger, - acapy_call=self.deps.aries_controller.schema.get_schema, - schema_id=schema_id, - ) - for schema_id in schemas_created_ids.schema_ids - if schema_id - ] - - if schemas: - if len(schemas) > 1: - raise CloudApiException( # pylint: disable=W0707 - f"Multiple schemas with name {schema.name} and version {schema.version} exist." - + f"These are: `{str(schemas_created_ids.schema_ids)}`.", - 409, - ) - self.deps.logger.debug("Using updated schema id with new DID") - _schema: SchemaGetResult = schemas[0] - else: - # if schema already exists, we should at least fetch 1, so this should never happen - raise CloudApiException( - "Could not publish schema.", 500 - ) # pylint: disable=W0707 - - # Schema exists with different attributes - if set(_schema.var_schema.attr_names) != set(schema.attribute_names): - raise CloudApiException( - "Error creating schema: Schema already exists with different attribute names." - + f"Given: `{str(set(_schema.var_schema.attr_names))}`. " - f"Found: `{str(set(schema.attribute_names))}`.", - 409, - ) # pylint: disable=W0707 - - result = credential_schema_from_acapy(_schema.var_schema) - self.deps.logger.info( - "Schema already exists on ledger. Returning schema definition: `{}`.", - result, - ) - return result - - -class SchemaRegistrar: - def __init__(self, deps: ServiceDependencies): - self.deps = deps - - async def register_schema(self, schema_id: str): - self.deps.logger.debug("Registering schema after successful publish to ledger") - try: - await register_schema(schema_id=schema_id) - except TrustRegistryException as error: - # If status_code is 405 it means the schema already exists in the trust registry - # That's okay, because we've achieved our intended result: - # make sure the schema is registered in the trust registry - self.deps.logger.info( - "Caught TrustRegistryException when registering schema. " - "Got status code {} with message `{}`", - error.status_code, - error.detail, - ) - if error.status_code == 405: - self.deps.logger.info( - "Status code 405 indicates schema is already registered, so we can continue" - ) - else: - raise error - - -async def create_schema_service( - aries_controller: AcaPyClient, - schema_request: SchemaSendRequest, - schema: CreateSchema, -) -> CredentialSchema: - """ - Create a schema and register it in the trust registry - """ - bound_logger = logger.bind(body=schema) - - deps = ServiceDependencies(bound_logger, aries_controller) - publisher = SchemaPublisher(deps) - registrar = SchemaRegistrar(deps) - - try: - result = await publisher.publish_schema(schema_request) - except CloudApiException as e: - if "already exist" in e.detail and e.status_code == 400: - result = await publisher.handle_existing_schema(schema) - else: - bound_logger.warning( - f"An unhandled Exception was caught while publishing schema: {e.detail}" - ) - raise CloudApiException("Error while creating schema.") from e - - if result.sent and result.sent.schema_id: - await registrar.register_schema(result.sent.schema_id) - else: - bound_logger.error("No SchemaSendResult in `publish_schema` response.") - raise CloudApiException( - "An unexpected error occurred: could not publish schema." - ) - - result = credential_schema_from_acapy(result.sent.var_schema) - bound_logger.info("Successfully published and registered schema.") - return result - - -async def get_schemas_tenant( - aries_controller: AcaPyClient, - schema_id: Optional[str], - schema_issuer_did: Optional[str], - schema_name: Optional[str], - schema_version: Optional[str], -) -> List[CredentialSchema]: - """ - Allows tenants to get all schemas created - """ - bound_logger = logger.bind( - body={ - "schema_id": schema_id, - "schema_issuer_did": schema_issuer_did, - "schema_name": schema_name, - "schema_version": schema_version, - } - ) - bound_logger.debug("Fetching schemas from trust registry") - - if not schema_id: # client is not filtering by schema_id, fetch all - trust_registry_schemas = await get_trust_registry_schemas() - else: # fetch specific id - trust_registry_schemas = [await get_trust_registry_schema_by_id(schema_id)] - - schema_ids = [schema.id for schema in trust_registry_schemas] - - bound_logger.debug("Getting schemas associated with fetched ids") - schemas = await schema_futures(schema_ids, aries_controller) - - if schema_issuer_did: - schemas = [ - schema for schema in schemas if schema.id.split(":")[0] == schema_issuer_did - ] - if schema_name: - schemas = [schema for schema in schemas if schema.name == schema_name] - if schema_version: - schemas = [schema for schema in schemas if schema.version == schema_version] - - return schemas - - -async def get_schemas_governance( - aries_controller: AcaPyClient, - schema_id: Optional[str], - schema_issuer_did: Optional[str], - schema_name: Optional[str], - schema_version: Optional[str], -) -> List[CredentialSchema]: - """ - Governance agents gets all schemas created by itself - """ - bound_logger = logger.bind( - body={ - "schema_id": schema_id, - "schema_issuer_did": schema_issuer_did, - "schema_name": schema_name, - "schema_version": schema_version, - } - ) - # Get all created schema ids that match the filter - bound_logger.debug("Fetching created schemas") - response = await handle_acapy_call( - logger=bound_logger, - acapy_call=aries_controller.schema.get_created_schemas, - schema_id=schema_id, - schema_issuer_did=schema_issuer_did, - schema_name=schema_name, - schema_version=schema_version, - ) - - # Initiate retrieving all schemas - schema_ids = response.schema_ids or [] - - bound_logger.debug("Getting schemas associated with fetched ids") - schemas = await schema_futures(schema_ids, aries_controller) - - return schemas - - -async def schema_futures( - schema_ids: List[str], aries_controller: AcaPyClient -) -> List[CredentialSchema]: - """ - Get schemas with attributes from schema ids - """ - logger.debug("Fetching schemas from schema ids") - # We now have schema_ids; the following logic is the same whether called by governance or tenant. - # Now fetch relevant schemas from ledger: - get_schema_futures = [ - handle_acapy_call( - logger=logger, - acapy_call=aries_controller.schema.get_schema, - schema_id=schema_id, - ) - for schema_id in schema_ids - ] - - # Wait for completion of retrieval and transform all schemas into response model (if a schema was returned) - if get_schema_futures: - logger.debug("Fetching each of the created schemas") - schema_results: List[SchemaGetResult] = await asyncio.gather( - *get_schema_futures - ) - else: - logger.debug("No created schema ids returned") - schema_results = [] - - schemas = [ - credential_schema_from_acapy(schema.var_schema) - for schema in schema_results - if schema.var_schema - ] - - return schemas - - -class CredDefPublisher: - def __init__(self, deps: ServiceDependencies): - self.deps = deps - - async def assert_public_did(self): - try: - self.deps.logger.debug("Asserting client has public DID") - public_did = await acapy_wallet.assert_public_did( - self.deps.aries_controller - ) - except CloudApiException as e: - log_message = f"Asserting public DID failed: {e}" - - if e.status_code == 403: - self.deps.logger.info(log_message) - client_error_message = ( - "Wallet making this request has no public DID. " - "Only issuers with a public DID can make this request." - ) - - else: - self.deps.logger.error(log_message) - client_error_message = ( - "Something went wrong while asserting if request is from a valid issuer. " - "Please try again." - ) - raise CloudApiException(client_error_message, e.status_code) from e - return public_did - - async def check_endorser_connection(self): - endorser_connection = await handle_acapy_call( - logger=self.deps.logger, - acapy_call=self.deps.aries_controller.connection.get_connections, - alias=ACAPY_ENDORSER_ALIAS, - ) - has_connections = len(endorser_connection.results) > 0 - - if not has_connections: - self.deps.logger.error( - "Failed to create credential definition supporting revocation: no endorser connection found. " - "Issuer attempted to create a credential definition with support for revocation but does not " - "have an active connection with an endorser, which is required for this operation." - ) - - raise CloudApiException( - "Credential definition creation failed: An active endorser connection is required " - "to support revocation. Please establish a connection with an endorser and try again." - ) - - async def publish_credential_definition(self, request_body): - try: - result = await handle_acapy_call( - logger=self.deps.logger, - acapy_call=self.deps.aries_controller.credential_definition.publish_cred_def, - body=request_body, - ) - - except CloudApiException as e: - self.deps.logger.warning( - "An Exception was caught while publishing credential definition: `{}` `{}`", - e.detail, - e.status_code, - ) - if "already exists" in e.detail: - raise CloudApiException(status_code=409, detail=e.detail) from e - else: - raise CloudApiException( - detail=f"Error while creating credential definition: {e.detail}", - status_code=e.status_code, - ) from e - - return result - - async def wait_for_transaction_ack(self, transaction_id): - self.deps.logger.debug( - "The publish credential definition response provides a transaction id. " - "Waiting for transaction to be in state `transaction_acked`" - ) - try: - # Wait for transaction to be acknowledged and written to the ledger - await coroutine_with_retry_until_value( - coroutine_func=self.deps.aries_controller.endorse_transaction.get_transaction, - args=(transaction_id,), - field_name="state", - expected_value="transaction_acked", - logger=self.deps.logger, - max_attempts=10, - retry_delay=2, - ) - except asyncio.TimeoutError as e: - raise CloudApiException( - "Timeout waiting for endorser to accept the endorsement request.", - 504, - ) from e - self.deps.logger.debug("Transaction has been acknowledged by the endorser") - - async def wait_for_revocation_registry(self, credential_definition_id): - try: - self.deps.logger.debug("Waiting for revocation registry creation") - await asyncio.wait_for( - wait_for_active_registry( - self.deps.aries_controller, credential_definition_id - ), - timeout=REGISTRY_CREATION_TIMEOUT, - ) - except asyncio.TimeoutError as e: - self.deps.logger.error("Timeout waiting for revocation registry creation.") - raise CloudApiException( - "Timeout waiting for revocation registry creation.", - 504, - ) from e - - -async def create_cred_def( - aries_controller: AcaPyClient, - credential_definition: CreateCredentialDefinition, - support_revocation: bool, -) -> str: - """ - Create a credential definition - """ - bound_logger = logger.bind( - body={ - "schema_id": credential_definition.schema_id, - "tag": credential_definition.tag, - "support_revocation": credential_definition.support_revocation, - } - ) - deps = ServiceDependencies(bound_logger, aries_controller) - publisher = CredDefPublisher(deps) - - public_did = await publisher.assert_public_did() - - await assert_valid_issuer(public_did, credential_definition.schema_id) - - if support_revocation: - await publisher.check_endorser_connection() - - request_body = handle_model_with_validation( - logger=logger, - model_class=CredentialDefinitionSendRequest, - schema_id=credential_definition.schema_id, - support_revocation=support_revocation, - tag=credential_definition.tag, - revocation_registry_size=32767, - ) - - result = await publisher.publish_credential_definition(request_body) - credential_definition_id = result.sent.credential_definition_id - - if result.txn and result.txn.transaction_id: - await publisher.wait_for_transaction_ack(result.txn.transaction_id) - - if support_revocation: - await publisher.wait_for_revocation_registry(credential_definition_id) - - return credential_definition_id - - -async def get_cred_defs( - aries_controller: AcaPyClient, - issuer_did: Optional[str], - credential_definition_id: Optional[str], - schema_id: Optional[str], - schema_issuer_did: Optional[str], - schema_name: Optional[str], - schema_version: Optional[str], -) -> List[CredentialDefinition]: - """ - Get credential definitions - """ - bound_logger = logger.bind( - body={ - "issuer_did": issuer_did, - "credential_definition_id": credential_definition_id, - "schema_id": schema_id, - "schema_issuer_did": schema_issuer_did, - "schema_name": schema_name, - "schema_version": schema_version, - } - ) - bound_logger.debug("Getting created credential definitions") - - response = await handle_acapy_call( - logger=bound_logger, - acapy_call=aries_controller.credential_definition.get_created_cred_defs, - issuer_did=issuer_did, - cred_def_id=credential_definition_id, - schema_id=schema_id, - schema_issuer_did=schema_issuer_did, - schema_name=schema_name, - schema_version=schema_version, - ) - - # Initiate retrieving all credential definitions - credential_definition_ids = response.credential_definition_ids or [] - get_credential_definition_futures = [ - handle_acapy_call( - logger=bound_logger, - acapy_call=aries_controller.credential_definition.get_cred_def, - cred_def_id=credential_definition_id, - ) - for credential_definition_id in credential_definition_ids - ] - - # Wait for completion of retrieval and transform all credential definitions - # into response model (if a credential definition was returned) - if get_credential_definition_futures: - bound_logger.debug("Getting definitions from fetched credential ids") - credential_definition_results = await asyncio.gather( - *get_credential_definition_futures - ) - else: - bound_logger.debug("No definition ids returned") - credential_definition_results = [] - - credential_definitions = [ - credential_definition_from_acapy(credential_definition.credential_definition) - for credential_definition in credential_definition_results - if credential_definition.credential_definition - ] - - return credential_definitions diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py new file mode 100644 index 000000000..9fb8d4cf2 --- /dev/null +++ b/app/services/definitions/definitions.py @@ -0,0 +1,315 @@ +import asyncio +from typing import List, Optional + +from aries_cloudcontroller import ( + AcaPyClient, + CredentialDefinitionSendRequest, + SchemaGetResult, + SchemaSendRequest, +) + +from app.exceptions import ( + CloudApiException, + handle_acapy_call, + handle_model_with_validation, +) +from app.models.definitions import ( + CreateCredentialDefinition, + CreateSchema, + CredentialDefinition, + CredentialSchema, +) +from app.routes.trust_registry import ( + get_schema_by_id as get_trust_registry_schema_by_id, +) +from app.routes.trust_registry import get_schemas as get_trust_registry_schemas +from app.services.definitions.credential_definition_publisher import ( + CredentialDefinitionPublisher, +) +from app.services.definitions.schema_publisher import SchemaPublisher +from app.services.trust_registry.util.issuer import assert_valid_issuer +from app.util.assert_public_did import assert_public_did +from app.util.definitions import ( + credential_definition_from_acapy, + credential_schema_from_acapy, +) +from app.util.transaction_acked import wait_for_transaction_ack +from shared.log_config import get_logger + +logger = get_logger(__name__) + + +async def create_schema_service( + aries_controller: AcaPyClient, + schema_request: SchemaSendRequest, + schema: CreateSchema, +) -> CredentialSchema: + """ + Create a schema and register it in the trust registry + """ + bound_logger = logger.bind(body=schema) + + publisher = SchemaPublisher(controller=aries_controller, logger=logger) + + try: + result = await publisher.publish_schema(schema_request) + except CloudApiException as e: + if "already exist" in e.detail and e.status_code == 400: + result = await publisher.handle_existing_schema(schema) + else: + bound_logger.warning( + f"An unhandled Exception was caught while publishing schema: {e.detail}" + ) + raise CloudApiException("Error while creating schema.") from e + + if result.sent and result.sent.schema_id: + await publisher.register_schema(result.sent.schema_id) + else: + bound_logger.error("No SchemaSendResult in `publish_schema` response.") + raise CloudApiException( + "An unexpected error occurred: could not publish schema." + ) + + result = credential_schema_from_acapy(result.sent.var_schema) + bound_logger.info("Successfully published and registered schema.") + return result + + +async def get_schemas_as_tenant( + aries_controller: AcaPyClient, + schema_id: Optional[str], + schema_issuer_did: Optional[str], + schema_name: Optional[str], + schema_version: Optional[str], +) -> List[CredentialSchema]: + """ + Allows tenants to get all schemas created + """ + bound_logger = logger.bind( + body={ + "schema_id": schema_id, + "schema_issuer_did": schema_issuer_did, + "schema_name": schema_name, + "schema_version": schema_version, + } + ) + bound_logger.debug("Fetching schemas from trust registry") + + if not schema_id: # client is not filtering by schema_id, fetch all + trust_registry_schemas = await get_trust_registry_schemas() + else: # fetch specific id + trust_registry_schemas = [await get_trust_registry_schema_by_id(schema_id)] + + schema_ids = [schema.id for schema in trust_registry_schemas] + + bound_logger.debug("Getting schemas associated with fetched ids") + schemas = await get_schemas_by_id( + aries_controller=aries_controller, + schema_ids=schema_ids, + ) + + if schema_issuer_did: + schemas = [ + schema for schema in schemas if schema.id.split(":")[0] == schema_issuer_did + ] + if schema_name: + schemas = [schema for schema in schemas if schema.name == schema_name] + if schema_version: + schemas = [schema for schema in schemas if schema.version == schema_version] + + return schemas + + +async def get_schemas_as_governance( + aries_controller: AcaPyClient, + schema_id: Optional[str], + schema_issuer_did: Optional[str], + schema_name: Optional[str], + schema_version: Optional[str], +) -> List[CredentialSchema]: + """ + Governance agents gets all schemas created by itself + """ + bound_logger = logger.bind( + body={ + "schema_id": schema_id, + "schema_issuer_did": schema_issuer_did, + "schema_name": schema_name, + "schema_version": schema_version, + } + ) + + # Get all created schema ids that match the filter + bound_logger.debug("Fetching created schemas") + response = await handle_acapy_call( + logger=bound_logger, + acapy_call=aries_controller.schema.get_created_schemas, + schema_id=schema_id, + schema_issuer_did=schema_issuer_did, + schema_name=schema_name, + schema_version=schema_version, + ) + + # Initiate retrieving all schemas + schema_ids = response.schema_ids or [] + + bound_logger.debug("Getting schemas associated with fetched ids") + schemas = await get_schemas_by_id( + aries_controller=aries_controller, + schema_ids=schema_ids, + ) + + return schemas + + +async def get_schemas_by_id( + aries_controller: AcaPyClient, + schema_ids: List[str], +) -> List[CredentialSchema]: + """ + Get schemas with attributes from schema ids + """ + logger.debug("Fetching schemas from schema ids") + # We now have schema_ids; the following logic is the same whether called by governance or tenant. + # Now fetch relevant schemas from ledger: + get_schema_futures = [ + handle_acapy_call( + logger=logger, + acapy_call=aries_controller.schema.get_schema, + schema_id=schema_id, + ) + for schema_id in schema_ids + ] + + # Wait for completion of retrieval and transform all schemas into response model (if a schema was returned) + if get_schema_futures: + logger.debug("Fetching each of the created schemas") + schema_results: List[SchemaGetResult] = await asyncio.gather( + *get_schema_futures + ) + else: + logger.debug("No created schema ids returned") + schema_results = [] + + schemas = [ + credential_schema_from_acapy(schema.var_schema) + for schema in schema_results + if schema.var_schema + ] + + return schemas + + +async def create_cred_def( + aries_controller: AcaPyClient, + credential_definition: CreateCredentialDefinition, + support_revocation: bool, +) -> str: + """ + Create a credential definition + """ + bound_logger = logger.bind( + body={ + "schema_id": credential_definition.schema_id, + "tag": credential_definition.tag, + "support_revocation": credential_definition.support_revocation, + } + ) + publisher = CredentialDefinitionPublisher( + controller=aries_controller, logger=bound_logger + ) + + public_did = await assert_public_did(aries_controller) + + await assert_valid_issuer(public_did, credential_definition.schema_id) + + if support_revocation: + await publisher.check_endorser_connection() + + request_body = handle_model_with_validation( + logger=logger, + model_class=CredentialDefinitionSendRequest, + schema_id=credential_definition.schema_id, + support_revocation=support_revocation, + tag=credential_definition.tag, + revocation_registry_size=32767, + ) + + result = await publisher.publish_credential_definition(request_body) + credential_definition_id = result.sent.credential_definition_id + + if result.txn and result.txn.transaction_id: + await wait_for_transaction_ack( + aries_controller=aries_controller, transaction_id=result.txn.transaction_id + ) + + if support_revocation: + await publisher.wait_for_revocation_registry(credential_definition_id) + + return credential_definition_id + + +async def get_cred_defs( + aries_controller: AcaPyClient, + issuer_did: Optional[str], + credential_definition_id: Optional[str], + schema_id: Optional[str], + schema_issuer_did: Optional[str], + schema_name: Optional[str], + schema_version: Optional[str], +) -> List[CredentialDefinition]: + """ + Get credential definitions + """ + bound_logger = logger.bind( + body={ + "issuer_did": issuer_did, + "credential_definition_id": credential_definition_id, + "schema_id": schema_id, + "schema_issuer_did": schema_issuer_did, + "schema_name": schema_name, + "schema_version": schema_version, + } + ) + bound_logger.debug("Getting created credential definitions") + + response = await handle_acapy_call( + logger=bound_logger, + acapy_call=aries_controller.credential_definition.get_created_cred_defs, + issuer_did=issuer_did, + cred_def_id=credential_definition_id, + schema_id=schema_id, + schema_issuer_did=schema_issuer_did, + schema_name=schema_name, + schema_version=schema_version, + ) + + # Initiate retrieving all credential definitions + credential_definition_ids = response.credential_definition_ids or [] + get_credential_definition_futures = [ + handle_acapy_call( + logger=bound_logger, + acapy_call=aries_controller.credential_definition.get_cred_def, + cred_def_id=credential_definition_id, + ) + for credential_definition_id in credential_definition_ids + ] + + # Wait for completion of retrieval and transform all credential definitions + # into response model (if a credential definition was returned) + if get_credential_definition_futures: + bound_logger.debug("Getting definitions from fetched credential ids") + credential_definition_results = await asyncio.gather( + *get_credential_definition_futures + ) + else: + bound_logger.debug("No definition ids returned") + credential_definition_results = [] + + credential_definitions = [ + credential_definition_from_acapy(credential_definition.credential_definition) + for credential_definition in credential_definition_results + if credential_definition.credential_definition + ] + + return credential_definitions From b06eb91470f4623a63b0fac8da4be41fa935460a Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 25 Jun 2024 14:46:52 +0200 Subject: [PATCH 25/74] move to cred_def publisher class to its own module --- .../credential_definition_publisher.py | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 app/services/definitions/credential_definition_publisher.py diff --git a/app/services/definitions/credential_definition_publisher.py b/app/services/definitions/credential_definition_publisher.py new file mode 100644 index 000000000..4dc3d8e85 --- /dev/null +++ b/app/services/definitions/credential_definition_publisher.py @@ -0,0 +1,94 @@ +import asyncio +from logging import Logger + +from aries_cloudcontroller import AcaPyClient + +from app.exceptions import CloudApiException, handle_acapy_call +from app.services import acapy_wallet +from app.services.revocation_registry import wait_for_active_registry +from app.util.check_endorser_connection import check_endorser_connection +from shared import REGISTRY_CREATION_TIMEOUT + + +class CredentialDefinitionPublisher: + def __init__(self, controller: AcaPyClient, logger: Logger): + self._logger = logger + self._controller = controller + + async def assert_public_did(self): + try: + self._logger.debug("Asserting client has public DID") + public_did = await acapy_wallet.assert_public_did(self._controller) + except CloudApiException as e: + log_message = f"Asserting public DID failed: {e}" + + if e.status_code == 403: + self._logger.info(log_message) + client_error_message = ( + "Wallet making this request has no public DID. " + "Only issuers with a public DID can make this request." + ) + + else: + self._logger.error(log_message) + client_error_message = ( + "Something went wrong while asserting if request is from a valid issuer. " + "Please try again." + ) + raise CloudApiException(client_error_message, e.status_code) from e + return public_did + + async def check_endorser_connection(self): + has_connections = await check_endorser_connection( + aries_controller=self._controller + ) + + if not has_connections: + self._logger.error( + "Failed to create credential definition supporting revocation: no endorser connection found. " + "Issuer attempted to create a credential definition with support for revocation but does not " + "have an active connection with an endorser, which is required for this operation." + ) + + raise CloudApiException( + "Credential definition creation failed: An active endorser connection is required " + "to support revocation. Please establish a connection with an endorser and try again." + ) + + async def publish_credential_definition(self, request_body): + try: + result = await handle_acapy_call( + logger=self._logger, + acapy_call=self._controller.credential_definition.publish_cred_def, + body=request_body, + ) + + except CloudApiException as e: + self._logger.warning( + "An Exception was caught while publishing credential definition: `{}` `{}`", + e.detail, + e.status_code, + ) + if "already exists" in e.detail: + raise CloudApiException(status_code=409, detail=e.detail) from e + else: + raise CloudApiException( + detail=f"Error while creating credential definition: {e.detail}", + status_code=e.status_code, + ) from e + + return result + + async def wait_for_revocation_registry(self, credential_definition_id): + try: + self._logger.debug("Waiting for revocation registry creation") + await asyncio.wait_for( + wait_for_active_registry(self._controller, credential_definition_id), + timeout=REGISTRY_CREATION_TIMEOUT, + ) + except asyncio.TimeoutError as e: + self._logger.error("Timeout waiting for revocation registry creation.") + raise CloudApiException( + "Timeout waiting for revocation registry creation.", + 504, + ) from e From 1f83bbeaff025c6ab9fad7b09b79518043584846 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 25 Jun 2024 14:47:12 +0200 Subject: [PATCH 26/74] move schema publisher class to own module --- app/services/definitions/schema_publisher.py | 125 +++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 app/services/definitions/schema_publisher.py diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py new file mode 100644 index 000000000..19b54b316 --- /dev/null +++ b/app/services/definitions/schema_publisher.py @@ -0,0 +1,125 @@ +from logging import Logger +from typing import List + +from aries_cloudcontroller import AcaPyClient, SchemaGetResult, SchemaSendRequest + +from app.exceptions import CloudApiException, TrustRegistryException, handle_acapy_call +from app.models.definitions import CreateSchema +from app.services.trust_registry.schemas import register_schema +from app.util.definitions import credential_schema_from_acapy + + +class SchemaPublisher: + def __init__( + self, + controller: AcaPyClient, + logger: Logger, + ): + self._logger = logger + self._controller = controller + + async def publish_schema(self, schema_request: SchemaSendRequest): + result = await handle_acapy_call( + logger=self._logger, + acapy_call=self._controller.schema.publish_schema, + body=schema_request, + create_transaction_for_endorser=False, + ) + return result + + async def handle_existing_schema(self, schema: CreateSchema): + self._logger.info("Handling case of schema already existing on ledger") + self._logger.debug("Fetching public DID for governance controller") + pub_did = await handle_acapy_call( + logger=self._logger, + acapy_call=self._controller.wallet.get_public_did, + ) + + _schema_id = f"{pub_did.result.did}:2:{schema.name}:{schema.version}" + self._logger.debug( + "Fetching schema id `{}` which is associated with request", + _schema_id, + ) + + _schema: SchemaGetResult = await handle_acapy_call( + logger=self._logger, + acapy_call=self._controller.schema.get_schema, + schema_id=_schema_id, + ) + + # Edge case where the governance agent has changed its public did + # Then we need to retrieve the schema in a different way as constructing the schema ID the way above + # will not be correct due to different public did. + if _schema.var_schema is None: + self._logger.debug( + "Schema not found. Governance agent may have changed public DID. " + "Fetching schemas created by governance agent with request name and version" + ) + schemas_created_ids = await handle_acapy_call( + logger=self._logger, + acapy_call=self._controller.schema.get_created_schemas, + schema_name=schema.name, + schema_version=schema.version, + ) + self._logger.debug("Getting schemas associated with fetched ids") + schemas: List[SchemaGetResult] = [ + await handle_acapy_call( + logger=self._logger, + acapy_call=self._controller.schema.get_schema, + schema_id=schema_id, + ) + for schema_id in schemas_created_ids.schema_ids + if schema_id + ] + + if schemas: + if len(schemas) > 1: + raise CloudApiException( # pylint: disable=W0707 + f"Multiple schemas with name {schema.name} and version {schema.version} exist." + + f"These are: `{str(schemas_created_ids.schema_ids)}`.", + 409, + ) + self._logger.debug("Using updated schema id with new DID") + _schema: SchemaGetResult = schemas[0] + else: + # if schema already exists, we should at least fetch 1, so this should never happen + raise CloudApiException( + "Could not publish schema.", 500 + ) # pylint: disable=W0707 + + # Schema exists with different attributes + if set(_schema.var_schema.attr_names) != set(schema.attribute_names): + raise CloudApiException( + "Error creating schema: Schema already exists with different attribute names." + + f"Given: `{str(set(_schema.var_schema.attr_names))}`. " + f"Found: `{str(set(schema.attribute_names))}`.", + 409, + ) # pylint: disable=W0707 + + result = credential_schema_from_acapy(_schema.var_schema) + self._logger.info( + "Schema already exists on ledger. Returning schema definition: `{}`.", + result, + ) + return result + + async def register_schema(self, schema_id: str): + self._logger.debug("Registering schema after successful publish to ledger") + try: + await register_schema(schema_id=schema_id) + except TrustRegistryException as error: + # If status_code is 405 it means the schema already exists in the trust registry + # That's okay, because we've achieved our intended result: + # make sure the schema is registered in the trust registry + self._logger.info( + "Caught TrustRegistryException when registering schema. " + "Got status code {} with message `{}`", + error.status_code, + error.detail, + ) + if error.status_code == 405: + self._logger.info( + "Status code 405 indicates schema is already registered, so we can continue" + ) + else: + raise error From 14cb394f101a1b7890717c6a01c6928078157364 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 25 Jun 2024 14:48:39 +0200 Subject: [PATCH 27/74] make assert public did a util function --- app/util/assert_public_did.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 app/util/assert_public_did.py diff --git a/app/util/assert_public_did.py b/app/util/assert_public_did.py new file mode 100644 index 000000000..597363dec --- /dev/null +++ b/app/util/assert_public_did.py @@ -0,0 +1,35 @@ +from aries_cloudcontroller import AcaPyClient + +from app.exceptions import CloudApiException +from app.services import acapy_wallet +from shared.log_config import get_logger + +logger = get_logger(__name__) + + +async def assert_public_did(aries_controller: AcaPyClient) -> str: + """ + Assert tenant has a public DID and return it. + """ + try: + logger.debug("Asserting client has public DID") + public_did = await acapy_wallet.assert_public_did(aries_controller) + except CloudApiException as e: + log_message = f"Asserting public DID failed: {e}" + + if e.status_code == 403: + logger.info(log_message) + client_error_message = ( + "Wallet making this request has no public DID. " + "Only issuers with a public DID can make this request." + ) + + else: + logger.error(log_message) + client_error_message = ( + "Something went wrong while asserting if request is from a valid issuer. " + "Please try again." + ) + + raise CloudApiException(client_error_message, e.status_code) from e + return public_did From b4d9f896175157b01c168bf43c35b322f83b1487 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 25 Jun 2024 14:51:04 +0200 Subject: [PATCH 28/74] add check endorser connection function --- app/util/check_endorser_connection.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 app/util/check_endorser_connection.py diff --git a/app/util/check_endorser_connection.py b/app/util/check_endorser_connection.py new file mode 100644 index 000000000..a273ca556 --- /dev/null +++ b/app/util/check_endorser_connection.py @@ -0,0 +1,23 @@ +from aries_cloudcontroller import AcaPyClient + +from app.exceptions import handle_acapy_call +from shared import ACAPY_ENDORSER_ALIAS +from shared.log_config import get_logger + +logger = get_logger(__name__) + + +async def check_endorser_connection(aries_controller: AcaPyClient) -> bool: + """ + Check if tenant has an active connection with the endorser. + """ + logger.debug("Get connection by endorser alias") + endorser_connection = await handle_acapy_call( + logger=logger, + acapy_call=aries_controller.connection.get_connections, + alias=ACAPY_ENDORSER_ALIAS, + ) + + has_connections = len(endorser_connection.results) > 0 + + return has_connections From 8e1c438a60458685f0de308d0939d3ae2257bf23 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 25 Jun 2024 14:51:40 +0200 Subject: [PATCH 29/74] add wait for transaction acked util function --- app/util/transaction_acked.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 app/util/transaction_acked.py diff --git a/app/util/transaction_acked.py b/app/util/transaction_acked.py new file mode 100644 index 000000000..66a2cc80a --- /dev/null +++ b/app/util/transaction_acked.py @@ -0,0 +1,34 @@ +import asyncio + +from aries_cloudcontroller import AcaPyClient + +from app.exceptions import CloudApiException +from app.util.retry_method import coroutine_with_retry_until_value +from shared.log_config import get_logger + +logger = get_logger(__name__) + + +async def wait_for_transaction_ack(aries_controller: AcaPyClient, transaction_id: str): + """ + Wait for the transaction to be acknowledged by the endorser. + """ + bound_logger = logger.bind(transaction_id=transaction_id) + bound_logger.debug("Waiting for transaction to be acknowledged by the endorser") + try: + # Wait for transaction to be acknowledged and written to the ledger + await coroutine_with_retry_until_value( + coroutine_func=aries_controller.endorse_transaction.get_transaction, + args=(transaction_id,), + field_name="state", + expected_value="transaction_acked", + logger=bound_logger, + max_attempts=10, + retry_delay=2, + ) + except asyncio.TimeoutError as e: + raise CloudApiException( + "Timeout waiting for endorser to accept the endorsement request.", + 504, + ) from e + bound_logger.debug("Transaction has been acknowledged by the endorser") From b974797ca87f59c918aaef61f0d3ca913a4bbf6a Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 26 Jun 2024 10:43:28 +0200 Subject: [PATCH 30/74] assert governance is calling function --- app/services/definitions/definitions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py index 9fb8d4cf2..5b30e9439 100644 --- a/app/services/definitions/definitions.py +++ b/app/services/definitions/definitions.py @@ -34,6 +34,7 @@ credential_schema_from_acapy, ) from app.util.transaction_acked import wait_for_transaction_ack +from shared.constants import GOVERNANCE_AGENT_URL from shared.log_config import get_logger logger = get_logger(__name__) @@ -139,6 +140,9 @@ async def get_schemas_as_governance( } ) + logger.debug("Asserting governance agent is host being called") + assert aries_controller.configuration.host == GOVERNANCE_AGENT_URL + # Get all created schema ids that match the filter bound_logger.debug("Fetching created schemas") response = await handle_acapy_call( From f983339e13ccdc5bfbc3a8c948c1e5564510a980 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Wed, 26 Jun 2024 10:43:50 +0200 Subject: [PATCH 31/74] formatting --- app/services/definitions/definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py index 5b30e9439..191881c41 100644 --- a/app/services/definitions/definitions.py +++ b/app/services/definitions/definitions.py @@ -142,7 +142,7 @@ async def get_schemas_as_governance( logger.debug("Asserting governance agent is host being called") assert aries_controller.configuration.host == GOVERNANCE_AGENT_URL - + # Get all created schema ids that match the filter bound_logger.debug("Fetching created schemas") response = await handle_acapy_call( From a72fca150d317203b1c03b6e43c6654a60cd968a Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 08:15:38 +0200 Subject: [PATCH 32/74] add state to get connection connection must be completed --- app/util/check_endorser_connection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/util/check_endorser_connection.py b/app/util/check_endorser_connection.py index a273ca556..e00a151a1 100644 --- a/app/util/check_endorser_connection.py +++ b/app/util/check_endorser_connection.py @@ -16,6 +16,7 @@ async def check_endorser_connection(aries_controller: AcaPyClient) -> bool: logger=logger, acapy_call=aries_controller.connection.get_connections, alias=ACAPY_ENDORSER_ALIAS, + state="completed", ) has_connections = len(endorser_connection.results) > 0 From e922622aeb197d7a5c08ec0a1a51f34886270e4a Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 08:16:04 +0200 Subject: [PATCH 33/74] formatting --- app/util/assert_public_did.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/util/assert_public_did.py b/app/util/assert_public_did.py index 597363dec..9c77f54b3 100644 --- a/app/util/assert_public_did.py +++ b/app/util/assert_public_did.py @@ -23,7 +23,6 @@ async def assert_public_did(aries_controller: AcaPyClient) -> str: "Wallet making this request has no public DID. " "Only issuers with a public DID can make this request." ) - else: logger.error(log_message) client_error_message = ( From 75685e2e1b87e7b351f7555ffacda07df8fc4cfa Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 08:16:18 +0200 Subject: [PATCH 34/74] formatting --- app/services/definitions/credential_definition_publisher.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/services/definitions/credential_definition_publisher.py b/app/services/definitions/credential_definition_publisher.py index 4dc3d8e85..b62f4b488 100644 --- a/app/services/definitions/credential_definition_publisher.py +++ b/app/services/definitions/credential_definition_publisher.py @@ -28,7 +28,6 @@ async def assert_public_did(self): "Wallet making this request has no public DID. " "Only issuers with a public DID can make this request." ) - else: self._logger.error(log_message) client_error_message = ( @@ -49,7 +48,6 @@ async def check_endorser_connection(self): "Issuer attempted to create a credential definition with support for revocation but does not " "have an active connection with an endorser, which is required for this operation." ) - raise CloudApiException( "Credential definition creation failed: An active endorser connection is required " "to support revocation. Please establish a connection with an endorser and try again." @@ -62,7 +60,6 @@ async def publish_credential_definition(self, request_body): acapy_call=self._controller.credential_definition.publish_cred_def, body=request_body, ) - except CloudApiException as e: self._logger.warning( "An Exception was caught while publishing credential definition: `{}` `{}`", From 741f93494fcc87ad51b01036cad19c845c14b477 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 08:16:54 +0200 Subject: [PATCH 35/74] update doc strings --- app/services/definitions/definitions.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py index 191881c41..0fab459f8 100644 --- a/app/services/definitions/definitions.py +++ b/app/services/definitions/definitions.py @@ -84,7 +84,7 @@ async def get_schemas_as_tenant( schema_version: Optional[str], ) -> List[CredentialSchema]: """ - Allows tenants to get all schemas created + Allows tenants to get all schemas from trust registry """ bound_logger = logger.bind( body={ @@ -171,11 +171,12 @@ async def get_schemas_by_id( schema_ids: List[str], ) -> List[CredentialSchema]: """ - Get schemas with attributes from schema ids + Fetch schemas with attributes using schema IDs. + The following logic applies to both governance and tenant calls. + Retrieve the relevant schemas from the ledger: """ logger.debug("Fetching schemas from schema ids") - # We now have schema_ids; the following logic is the same whether called by governance or tenant. - # Now fetch relevant schemas from ledger: + get_schema_futures = [ handle_acapy_call( logger=logger, From 900382fbca0170147bf14a563ad2337ae5afd822 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 08:18:13 +0200 Subject: [PATCH 36/74] fix indentation --- app/services/definitions/schema_publisher.py | 28 ++++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index 19b54b316..4da180dfd 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -90,18 +90,24 @@ async def handle_existing_schema(self, schema: CreateSchema): # Schema exists with different attributes if set(_schema.var_schema.attr_names) != set(schema.attribute_names): raise CloudApiException( - "Error creating schema: Schema already exists with different attribute names." - + f"Given: `{str(set(_schema.var_schema.attr_names))}`. " - f"Found: `{str(set(schema.attribute_names))}`.", - 409, - ) # pylint: disable=W0707 - - result = credential_schema_from_acapy(_schema.var_schema) - self._logger.info( - "Schema already exists on ledger. Returning schema definition: `{}`.", - result, + # Schema exists with different attributes + if set(_schema.var_schema.attr_names) != set(schema.attribute_names): + error_message = ( + "Error creating schema: Schema already exists with different attribute names. " + f"Given: `{str(set(schema.attribute_names))}`. " + f"Found: `{str(set(_schema.var_schema.attr_names))}`." + ) + raise CloudApiException( + error_message, + 409, ) - return result + + result = credential_schema_from_acapy(_schema.var_schema) + self._logger.info( + "Schema already exists on ledger. Returning schema definition: `{}`.", + result, + ) + return result async def register_schema(self, schema_id: str): self._logger.debug("Registering schema after successful publish to ledger") From 8c1624ba9df113a089565d1f016777c2c988bf83 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 08:18:39 +0200 Subject: [PATCH 37/74] add return types --- app/services/definitions/schema_publisher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index 4da180dfd..b41c7608b 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -109,7 +109,7 @@ async def handle_existing_schema(self, schema: CreateSchema): ) return result - async def register_schema(self, schema_id: str): + async def register_schema(self, schema_id: str) -> None: self._logger.debug("Registering schema after successful publish to ledger") try: await register_schema(schema_id=schema_id) From 7eb23f072c442e9b450cde006d6e132bcb97f4be Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 08:19:52 +0200 Subject: [PATCH 38/74] reorder logic --- app/services/definitions/schema_publisher.py | 24 ++++++++------------ 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index b41c7608b..a8da91443 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -72,24 +72,20 @@ async def handle_existing_schema(self, schema: CreateSchema): if schema_id ] - if schemas: - if len(schemas) > 1: - raise CloudApiException( # pylint: disable=W0707 - f"Multiple schemas with name {schema.name} and version {schema.version} exist." - + f"These are: `{str(schemas_created_ids.schema_ids)}`.", - 409, - ) - self._logger.debug("Using updated schema id with new DID") - _schema: SchemaGetResult = schemas[0] - else: + if not schemas: # if schema already exists, we should at least fetch 1, so this should never happen raise CloudApiException( "Could not publish schema.", 500 - ) # pylint: disable=W0707 - - # Schema exists with different attributes - if set(_schema.var_schema.attr_names) != set(schema.attribute_names): + ) + if len(schemas) > 1: raise CloudApiException( + f"Multiple schemas with name {schema.name} and version {schema.version} exist." + f"These are: `{str(schemas_created_ids.schema_ids)}`.", + 409, + ) + self._logger.debug("Using updated schema id with new DID") + _schema: SchemaGetResult = schemas[0] + # Schema exists with different attributes if set(_schema.var_schema.attr_names) != set(schema.attribute_names): error_message = ( From f628a6032a2d23384c0d0c635263da60ba7b94d1 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 08:20:11 +0200 Subject: [PATCH 39/74] add return types --- app/services/definitions/schema_publisher.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index a8da91443..d6095e17c 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -2,9 +2,9 @@ from typing import List from aries_cloudcontroller import AcaPyClient, SchemaGetResult, SchemaSendRequest - +from aries_cloudcontroller.models.txn_or_schema_send_result import TxnOrSchemaSendResult from app.exceptions import CloudApiException, TrustRegistryException, handle_acapy_call -from app.models.definitions import CreateSchema +from app.models.definitions import CreateSchema, CredentialSchema from app.services.trust_registry.schemas import register_schema from app.util.definitions import credential_schema_from_acapy @@ -18,7 +18,7 @@ def __init__( self._logger = logger self._controller = controller - async def publish_schema(self, schema_request: SchemaSendRequest): + async def publish_schema(self, schema_request: SchemaSendRequest) -> TxnOrSchemaSendResult: result = await handle_acapy_call( logger=self._logger, acapy_call=self._controller.schema.publish_schema, @@ -27,7 +27,7 @@ async def publish_schema(self, schema_request: SchemaSendRequest): ) return result - async def handle_existing_schema(self, schema: CreateSchema): + async def handle_existing_schema(self, schema: CreateSchema) -> CredentialSchema: self._logger.info("Handling case of schema already existing on ledger") self._logger.debug("Fetching public DID for governance controller") pub_did = await handle_acapy_call( From 0b7f6183f9e9e61a977647d54c2d4f7fceda096c Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 08:20:23 +0200 Subject: [PATCH 40/74] add return types --- app/util/transaction_acked.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/util/transaction_acked.py b/app/util/transaction_acked.py index 66a2cc80a..f091d61b4 100644 --- a/app/util/transaction_acked.py +++ b/app/util/transaction_acked.py @@ -9,7 +9,7 @@ logger = get_logger(__name__) -async def wait_for_transaction_ack(aries_controller: AcaPyClient, transaction_id: str): +async def wait_for_transaction_ack(aries_controller: AcaPyClient, transaction_id: str) -> None: """ Wait for the transaction to be acknowledged by the endorser. """ From 59509ea9291536811c93b739b8141e7a4f95929b Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 15:59:03 +0200 Subject: [PATCH 41/74] handle exception if not governance --- app/routes/definitions.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index b2fe32441..d9d07ebc2 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -13,6 +13,7 @@ ) from app.dependencies.role import Role from app.exceptions import handle_acapy_call, handle_model_with_validation +from app.exceptions.cloudapi_exception import CloudApiException from app.models.definitions import ( CreateCredentialDefinition, CreateSchema, @@ -153,13 +154,20 @@ async def get_schemas( ) else: # Governance is calling the endpoint - schemas = await get_schemas_as_governance( - aries_controller=aries_controller, - schema_id=schema_id, - schema_issuer_did=schema_issuer_did, - schema_name=schema_name, - schema_version=schema_version, - ) + try: + schemas = await get_schemas_as_governance( + aries_controller=aries_controller, + schema_id=schema_id, + schema_issuer_did=schema_issuer_did, + schema_name=schema_name, + schema_version=schema_version, + ) + except CloudApiException as e: + bound_logger.error("Failed to get schemas. Error: {}", e) + raise HTTPException( + status_code=e.status_code, + detail=e.detail, + ) if schemas: bound_logger.info("Successfully fetched schemas.") From ab6a5b7e091014919c399b69d278208b77bef84b Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:00:13 +0200 Subject: [PATCH 42/74] call register schema directly --- app/services/definitions/definitions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py index 0fab459f8..cba2412ea 100644 --- a/app/services/definitions/definitions.py +++ b/app/services/definitions/definitions.py @@ -27,6 +27,7 @@ CredentialDefinitionPublisher, ) from app.services.definitions.schema_publisher import SchemaPublisher +from app.services.trust_registry.schemas import register_schema from app.services.trust_registry.util.issuer import assert_valid_issuer from app.util.assert_public_did import assert_public_did from app.util.definitions import ( @@ -64,7 +65,7 @@ async def create_schema_service( raise CloudApiException("Error while creating schema.") from e if result.sent and result.sent.schema_id: - await publisher.register_schema(result.sent.schema_id) + await register_schema(schema_id=result.sent.schema_id) else: bound_logger.error("No SchemaSendResult in `publish_schema` response.") raise CloudApiException( From 992d845f2fc4503305b2b7e5484300e0887193fa Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:01:10 +0200 Subject: [PATCH 43/74] raise exception if not governance role --- app/services/definitions/definitions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py index cba2412ea..d8fefbb81 100644 --- a/app/services/definitions/definitions.py +++ b/app/services/definitions/definitions.py @@ -142,7 +142,11 @@ async def get_schemas_as_governance( ) logger.debug("Asserting governance agent is host being called") - assert aries_controller.configuration.host == GOVERNANCE_AGENT_URL + if aries_controller.configuration.host != GOVERNANCE_AGENT_URL: + raise CloudApiException( + "Only governance agents are allowed to access this endpoint.", + status_code=403 + ) # Get all created schema ids that match the filter bound_logger.debug("Fetching created schemas") From ed513256ef94adc33a7a73c37b869368c3a445ff Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:02:50 +0200 Subject: [PATCH 44/74] formatting --- app/services/definitions/definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py index d8fefbb81..9f0b852a3 100644 --- a/app/services/definitions/definitions.py +++ b/app/services/definitions/definitions.py @@ -145,7 +145,7 @@ async def get_schemas_as_governance( if aries_controller.configuration.host != GOVERNANCE_AGENT_URL: raise CloudApiException( "Only governance agents are allowed to access this endpoint.", - status_code=403 + status_code=403, ) # Get all created schema ids that match the filter From a85d7024a4c9608044fdc95af6d292d8c3be34b3 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:07:27 +0200 Subject: [PATCH 45/74] update imports --- app/services/definitions/schema_publisher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index d6095e17c..b0cdf2a33 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -3,9 +3,9 @@ from aries_cloudcontroller import AcaPyClient, SchemaGetResult, SchemaSendRequest from aries_cloudcontroller.models.txn_or_schema_send_result import TxnOrSchemaSendResult -from app.exceptions import CloudApiException, TrustRegistryException, handle_acapy_call + +from app.exceptions import CloudApiException, handle_acapy_call from app.models.definitions import CreateSchema, CredentialSchema -from app.services.trust_registry.schemas import register_schema from app.util.definitions import credential_schema_from_acapy From 3a17748286b9e56851ac14eae592203539d0a382 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:07:39 +0200 Subject: [PATCH 46/74] formatting --- app/services/definitions/schema_publisher.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index b0cdf2a33..131d1de6e 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -18,7 +18,9 @@ def __init__( self._logger = logger self._controller = controller - async def publish_schema(self, schema_request: SchemaSendRequest) -> TxnOrSchemaSendResult: + async def publish_schema( + self, schema_request: SchemaSendRequest + ) -> TxnOrSchemaSendResult: result = await handle_acapy_call( logger=self._logger, acapy_call=self._controller.schema.publish_schema, From c8d424a54270762d46aed1ced598a8d908400a38 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:08:10 +0200 Subject: [PATCH 47/74] formatting --- app/services/definitions/schema_publisher.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index 131d1de6e..e3892e4a2 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -76,9 +76,7 @@ async def handle_existing_schema(self, schema: CreateSchema) -> CredentialSchema if not schemas: # if schema already exists, we should at least fetch 1, so this should never happen - raise CloudApiException( - "Could not publish schema.", 500 - ) + raise CloudApiException("Could not publish schema.", 500) if len(schemas) > 1: raise CloudApiException( f"Multiple schemas with name {schema.name} and version {schema.version} exist." From 7b1349403d503751532fa5af6b01022f5486df05 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:08:38 +0200 Subject: [PATCH 48/74] removed function calling it directly --- app/services/definitions/schema_publisher.py | 21 -------------------- 1 file changed, 21 deletions(-) diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index e3892e4a2..c77c8280e 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -104,24 +104,3 @@ async def handle_existing_schema(self, schema: CreateSchema) -> CredentialSchema result, ) return result - - async def register_schema(self, schema_id: str) -> None: - self._logger.debug("Registering schema after successful publish to ledger") - try: - await register_schema(schema_id=schema_id) - except TrustRegistryException as error: - # If status_code is 405 it means the schema already exists in the trust registry - # That's okay, because we've achieved our intended result: - # make sure the schema is registered in the trust registry - self._logger.info( - "Caught TrustRegistryException when registering schema. " - "Got status code {} with message `{}`", - error.status_code, - error.detail, - ) - if error.status_code == 405: - self._logger.info( - "Status code 405 indicates schema is already registered, so we can continue" - ) - else: - raise error From 546c7fbaffb566bad31f2205608f36324633bd24 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:10:29 +0200 Subject: [PATCH 49/74] update tests --- app/tests/services/test_trust_registry.py | 6 ++++-- trustregistry/tests/e2e/test_schema.py | 2 +- trustregistry/tests/test_registry_schema.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/tests/services/test_trust_registry.py b/app/tests/services/test_trust_registry.py index 4505a1afe..0b0f589e5 100644 --- a/app/tests/services/test_trust_registry.py +++ b/app/tests/services/test_trust_registry.py @@ -250,7 +250,7 @@ async def test_register_schema( json={"schema_id": schema_id}, ) - mock_async_client.post = AsyncMock(return_value=Response(500)) + mock_async_client.post = AsyncMock(side_effect=HTTPException(500)) with pytest.raises(TrustRegistryException): await register_schema(schema_id=schema_id) @@ -319,7 +319,9 @@ async def test_remove_schema_by_id( TRUST_REGISTRY_URL + f"/registry/schemas/{schema_id}" ) - mock_async_client.delete = AsyncMock(return_value=Response(500, text="The error")) + mock_async_client.delete = AsyncMock( + side_effect=HTTPException(status_code=500, detail="The error") + ) with pytest.raises( TrustRegistryException, match="Error removing schema from trust registry" ): diff --git a/trustregistry/tests/e2e/test_schema.py b/trustregistry/tests/e2e/test_schema.py index f5c486bb3..814202e3b 100644 --- a/trustregistry/tests/e2e/test_schema.py +++ b/trustregistry/tests/e2e/test_schema.py @@ -45,7 +45,7 @@ async def test_register_schema(): f"{TRUST_REGISTRY_URL}/registry/schemas", json=payload, ) - assert response.status_code == 405 + assert response.status_code == 409 assert "Schema already exists" in response.json()["detail"] diff --git a/trustregistry/tests/test_registry_schema.py b/trustregistry/tests/test_registry_schema.py index aa7fd15a3..b7a45f582 100644 --- a/trustregistry/tests/test_registry_schema.py +++ b/trustregistry/tests/test_registry_schema.py @@ -57,7 +57,7 @@ async def test_register_schema_x(): await registry_schemas.register_schema(schema_id) mock_crud.assert_called_once() - assert ex.value.status_code == 405 + assert ex.value.status_code == 409 @pytest.mark.anyio From 359e97a7d699c8f58bb8f14491481a45ae90495d Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:12:15 +0200 Subject: [PATCH 50/74] Refactor trust registry client code for better error handling and consistency --- app/services/trust_registry/schemas.py | 123 +++++++++++++------------ 1 file changed, 63 insertions(+), 60 deletions(-) diff --git a/app/services/trust_registry/schemas.py b/app/services/trust_registry/schemas.py index 6af323ed6..c9858fc4d 100644 --- a/app/services/trust_registry/schemas.py +++ b/app/services/trust_registry/schemas.py @@ -1,5 +1,7 @@ from typing import List, Optional +from fastapi import HTTPException + from app.exceptions import TrustRegistryException from shared.constants import TRUST_REGISTRY_URL from shared.log_config import get_logger @@ -20,21 +22,21 @@ async def register_schema(schema_id: str) -> None: """ bound_logger = logger.bind(body={"schema_id": schema_id}) bound_logger.info("Registering schema on trust registry") - async with RichAsyncClient(raise_status_error=False) as client: - schema_res = await client.post( - f"{TRUST_REGISTRY_URL}/registry/schemas", json={"schema_id": schema_id} - ) - - if schema_res.is_error: - bound_logger.error( - "Error registering schema. Got status code {} with message `{}`.", - schema_res.status_code, - schema_res.text, - ) - raise TrustRegistryException( - f"Error registering schema `{schema_id}`. Error: `{schema_res.text}`.", - schema_res.status_code, - ) + async with RichAsyncClient() as client: + try: + await client.post( + f"{TRUST_REGISTRY_URL}/registry/schemas", json={"schema_id": schema_id} + ) + except HTTPException as e: + bound_logger.error( + "Error registering schema. Got status code {} with message `{}`.", + e.status_code, + e.detail, + ) + raise TrustRegistryException( + f"Error registering schema `{schema_id}`. Error: `{e.detail}`.", + e.status_code, + ) bound_logger.info("Successfully registered schema on trust registry.") @@ -49,18 +51,18 @@ async def fetch_schemas() -> List[Schema]: A list of schemas """ logger.info("Fetching all schemas from trust registry") - async with RichAsyncClient(raise_status_error=False) as client: - schemas_res = await client.get(f"{TRUST_REGISTRY_URL}/registry/schemas") - - if schemas_res.is_error: - logger.error( - "Error fetching schemas. Got status code {} with message `{}`.", - schemas_res.status_code, - schemas_res.text, - ) - raise TrustRegistryException( - f"Unable to fetch schemas: `{schemas_res.text}`.", schemas_res.status_code - ) + async with RichAsyncClient() as client: + try: + schemas_res = await client.get(f"{TRUST_REGISTRY_URL}/registry/schemas") + except HTTPException as e: + logger.error( + "Error fetching schemas. Got status code {} with message `{}`.", + e.status_code, + e.detail, + ) + raise TrustRegistryException( + f"Unable to fetch schemas: `{e.detail}`.", e.status_code + ) result = [Schema.model_validate(schema) for schema in schemas_res.json()] logger.info("Successfully fetched schemas from trust registry.") @@ -79,24 +81,25 @@ async def get_schema_by_id(schema_id: str) -> Optional[Schema]: bound_logger = logger.bind(body={"schema_id": schema_id}) bound_logger.info("Fetching schema from trust registry") - async with RichAsyncClient(raise_status_error=False) as client: - schema_response = await client.get( - f"{TRUST_REGISTRY_URL}/registry/schemas/{schema_id}" - ) - - if schema_response.status_code == 404: - bound_logger.info("Bad request: Schema not found.") - return None - if schema_response.is_error: - logger.error( - "Error fetching schema. Got status code {} with message `{}`.", - schema_response.status_code, - schema_response.text, - ) - raise TrustRegistryException( - f"Unable to fetch schema: `{schema_response.text}`.", - schema_response.status_code, - ) + async with RichAsyncClient() as client: + try: + schema_response = await client.get( + f"{TRUST_REGISTRY_URL}/registry/schemas/{schema_id}" + ) + except HTTPException as e: + if e.status_code == 404: + bound_logger.info("Bad request: Schema not found.") + return None + else: + bound_logger.error( + "Error fetching schema. Got status code {} with message `{}`.", + e.status_code, + e.detail, + ) + raise TrustRegistryException( + f"Unable to fetch schema: `{e.detail}`.", + e.status_code, + ) result = Schema.model_validate(schema_response.json()) logger.info("Successfully fetched schema from trust registry.") @@ -114,20 +117,20 @@ async def remove_schema_by_id(schema_id: str) -> None: """ bound_logger = logger.bind(body={"schema_id": schema_id}) bound_logger.info("Removing schema from trust registry") - async with RichAsyncClient(raise_status_error=False) as client: - remove_response = await client.delete( - f"{TRUST_REGISTRY_URL}/registry/schemas/{schema_id}" - ) - - if remove_response.is_error: - bound_logger.error( - "Error removing schema. Got status code {} with message `{}`.", - remove_response.status_code, - remove_response.text, - ) - raise TrustRegistryException( - f"Error removing schema from trust registry: `{remove_response.text}`.", - remove_response.status_code, - ) + async with RichAsyncClient() as client: + try: + await client.delete( + f"{TRUST_REGISTRY_URL}/registry/schemas/{schema_id}" + ) + except HTTPException as e: + bound_logger.error( + "Error removing schema. Got status code {} with message `{}`.", + e.status_code, + e.detail, + ) + raise TrustRegistryException( + f"Error removing schema from trust registry: `{e.detail}`.", + e.status_code, + ) bound_logger.info("Successfully removed schema from trust registry.") From 3c2a71953886446f5ca94df24fd309127ab9592e Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:17:03 +0200 Subject: [PATCH 51/74] formatting --- app/util/transaction_acked.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/util/transaction_acked.py b/app/util/transaction_acked.py index f091d61b4..823fbe69d 100644 --- a/app/util/transaction_acked.py +++ b/app/util/transaction_acked.py @@ -9,7 +9,9 @@ logger = get_logger(__name__) -async def wait_for_transaction_ack(aries_controller: AcaPyClient, transaction_id: str) -> None: +async def wait_for_transaction_ack( + aries_controller: AcaPyClient, transaction_id: str +) -> None: """ Wait for the transaction to be acknowledged by the endorser. """ From 36d8043c95154a8a3e79cc1734f36e206080ae7c Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:17:28 +0200 Subject: [PATCH 52/74] fix status code on exception --- trustregistry/registry/registry_schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trustregistry/registry/registry_schemas.py b/trustregistry/registry/registry_schemas.py index 868755476..fc2c7936c 100644 --- a/trustregistry/registry/registry_schemas.py +++ b/trustregistry/registry/registry_schemas.py @@ -46,7 +46,7 @@ async def register_schema( ) except crud.SchemaAlreadyExistsException as e: bound_logger.info("Bad request: Schema already exists.") - raise HTTPException(status_code=405, detail="Schema already exists.") from e + raise HTTPException(status_code=409, detail="Schema already exists.") from e return create_schema_res From 23560dec70e0b3464c5248f0d897b3dfd7f8cb3a Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:18:07 +0200 Subject: [PATCH 53/74] formatting --- app/services/trust_registry/schemas.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/services/trust_registry/schemas.py b/app/services/trust_registry/schemas.py index c9858fc4d..916ab874a 100644 --- a/app/services/trust_registry/schemas.py +++ b/app/services/trust_registry/schemas.py @@ -119,9 +119,7 @@ async def remove_schema_by_id(schema_id: str) -> None: bound_logger.info("Removing schema from trust registry") async with RichAsyncClient() as client: try: - await client.delete( - f"{TRUST_REGISTRY_URL}/registry/schemas/{schema_id}" - ) + await client.delete(f"{TRUST_REGISTRY_URL}/registry/schemas/{schema_id}") except HTTPException as e: bound_logger.error( "Error removing schema. Got status code {} with message `{}`.", From 6f1934fffa4f7b98c028c2dac46f67fadfe5f1f9 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Thu, 27 Jun 2024 16:27:48 +0200 Subject: [PATCH 54/74] raise from error --- app/routes/definitions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index d9d07ebc2..839485279 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -167,7 +167,7 @@ async def get_schemas( raise HTTPException( status_code=e.status_code, detail=e.detail, - ) + ) from e if schemas: bound_logger.info("Successfully fetched schemas.") From 4dd3588d0f658b3d078049d53edf7822b87caa88 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Mon, 1 Jul 2024 09:11:26 +0200 Subject: [PATCH 55/74] formatting --- app/routes/definitions.py | 5 +---- app/services/definitions/schema_publisher.py | 11 ++--------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index 839485279..1f032239a 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -164,10 +164,7 @@ async def get_schemas( ) except CloudApiException as e: bound_logger.error("Failed to get schemas. Error: {}", e) - raise HTTPException( - status_code=e.status_code, - detail=e.detail, - ) from e + raise if schemas: bound_logger.info("Successfully fetched schemas.") diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index c77c8280e..ae98f3486 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -10,11 +10,7 @@ class SchemaPublisher: - def __init__( - self, - controller: AcaPyClient, - logger: Logger, - ): + def __init__(self, controller: AcaPyClient, logger: Logger): self._logger = logger self._controller = controller @@ -93,10 +89,7 @@ async def handle_existing_schema(self, schema: CreateSchema) -> CredentialSchema f"Given: `{str(set(schema.attribute_names))}`. " f"Found: `{str(set(_schema.var_schema.attr_names))}`." ) - raise CloudApiException( - error_message, - 409, - ) + raise CloudApiException(error_message, 409) result = credential_schema_from_acapy(_schema.var_schema) self._logger.info( From e9fb74e7d98d81cd9a97f35cf496fb45638a5e36 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Mon, 1 Jul 2024 09:16:39 +0200 Subject: [PATCH 56/74] add default values --- app/services/definitions/definitions.py | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py index 9f0b852a3..3be9dedbf 100644 --- a/app/services/definitions/definitions.py +++ b/app/services/definitions/definitions.py @@ -79,10 +79,10 @@ async def create_schema_service( async def get_schemas_as_tenant( aries_controller: AcaPyClient, - schema_id: Optional[str], - schema_issuer_did: Optional[str], - schema_name: Optional[str], - schema_version: Optional[str], + schema_id: Optional[str] = None, + schema_issuer_did: Optional[str] = None, + schema_name: Optional[str] = None, + schema_version: Optional[str] = None, ) -> List[CredentialSchema]: """ Allows tenants to get all schemas from trust registry @@ -124,10 +124,10 @@ async def get_schemas_as_tenant( async def get_schemas_as_governance( aries_controller: AcaPyClient, - schema_id: Optional[str], - schema_issuer_did: Optional[str], - schema_name: Optional[str], - schema_version: Optional[str], + schema_id: Optional[str] = None, + schema_issuer_did: Optional[str] = None, + schema_name: Optional[str] = None, + schema_version: Optional[str] = None, ) -> List[CredentialSchema]: """ Governance agents gets all schemas created by itself @@ -261,12 +261,12 @@ async def create_cred_def( async def get_cred_defs( aries_controller: AcaPyClient, - issuer_did: Optional[str], - credential_definition_id: Optional[str], - schema_id: Optional[str], - schema_issuer_did: Optional[str], - schema_name: Optional[str], - schema_version: Optional[str], + issuer_did: Optional[str] = None, + credential_definition_id: Optional[str] = None, + schema_id: Optional[str] = None, + schema_issuer_did: Optional[str] = None, + schema_name: Optional[str] = None, + schema_version: Optional[str] = None, ) -> List[CredentialDefinition]: """ Get credential definitions From 24a6820e9a02edf8ac6e3c1d09c4eca17b0a0f74 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Mon, 1 Jul 2024 09:16:51 +0200 Subject: [PATCH 57/74] add logs --- app/services/definitions/credential_definition_publisher.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/services/definitions/credential_definition_publisher.py b/app/services/definitions/credential_definition_publisher.py index b62f4b488..c34b8776c 100644 --- a/app/services/definitions/credential_definition_publisher.py +++ b/app/services/definitions/credential_definition_publisher.py @@ -67,8 +67,12 @@ async def publish_credential_definition(self, request_body): e.status_code, ) if "already exists" in e.detail: + self._logger.info("Credential definition already exists") raise CloudApiException(status_code=409, detail=e.detail) from e else: + self._logger.error( + "Error while creating credential definition: `{}`", e.detail + ) raise CloudApiException( detail=f"Error while creating credential definition: {e.detail}", status_code=e.status_code, From 20242df4e8e671bdb6a145dd384e121f733f9ceb Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 2 Jul 2024 11:02:12 +0200 Subject: [PATCH 58/74] move to service --- app/routes/definitions.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index 1f032239a..616b118b0 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -77,17 +77,9 @@ async def create_schema( bound_logger = logger.bind(body=schema) bound_logger.info("POST request received: Create schema (publish and register)") - schema_send_request = handle_model_with_validation( - logger=bound_logger, - model_class=SchemaSendRequest, - attributes=schema.attribute_names, - schema_name=schema.name, - schema_version=schema.version, - ) async with get_governance_controller(governance_auth) as aries_controller: schema_response = await create_schema_service( aries_controller=aries_controller, - schema_request=schema_send_request, schema=schema, ) return schema_response From 097ec8be2417d4f2080d14cb28e72c1c3e38ecc0 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 2 Jul 2024 11:03:10 +0200 Subject: [PATCH 59/74] update imports --- app/routes/definitions.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index 616b118b0..db71ef97e 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -21,9 +21,13 @@ CredentialSchema, ) from app.services.definitions.definitions import ( - create_cred_def, - create_schema_service, - get_cred_defs, + create_credential_definition as create_cred_def, +) +from app.services.definitions.definitions import create_schema as create_schema_service +from app.services.definitions.definitions import ( + get_credential_definitions as get_cred_defs, +) +from app.services.definitions.definitions import ( get_schemas_as_governance, get_schemas_as_tenant, ) From ddb14c3d54321071b590d9edc4e26ab8f9ac217d Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 2 Jul 2024 11:04:05 +0200 Subject: [PATCH 60/74] not used removed --- .../credential_definition_publisher.py | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/app/services/definitions/credential_definition_publisher.py b/app/services/definitions/credential_definition_publisher.py index c34b8776c..9d76c0d60 100644 --- a/app/services/definitions/credential_definition_publisher.py +++ b/app/services/definitions/credential_definition_publisher.py @@ -15,28 +15,6 @@ def __init__(self, controller: AcaPyClient, logger: Logger): self._logger = logger self._controller = controller - async def assert_public_did(self): - try: - self._logger.debug("Asserting client has public DID") - public_did = await acapy_wallet.assert_public_did(self._controller) - except CloudApiException as e: - log_message = f"Asserting public DID failed: {e}" - - if e.status_code == 403: - self._logger.info(log_message) - client_error_message = ( - "Wallet making this request has no public DID. " - "Only issuers with a public DID can make this request." - ) - else: - self._logger.error(log_message) - client_error_message = ( - "Something went wrong while asserting if request is from a valid issuer. " - "Please try again." - ) - raise CloudApiException(client_error_message, e.status_code) from e - return public_did - async def check_endorser_connection(self): has_connections = await check_endorser_connection( aries_controller=self._controller From 2408ba33df9866962984a153d7da4a5ecaf31bc9 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 2 Jul 2024 11:16:41 +0200 Subject: [PATCH 61/74] update function names --- app/services/definitions/definitions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py index 3be9dedbf..a3095a9a9 100644 --- a/app/services/definitions/definitions.py +++ b/app/services/definitions/definitions.py @@ -41,7 +41,7 @@ logger = get_logger(__name__) -async def create_schema_service( +async def create_schema( aries_controller: AcaPyClient, schema_request: SchemaSendRequest, schema: CreateSchema, @@ -210,7 +210,7 @@ async def get_schemas_by_id( return schemas -async def create_cred_def( +async def create_credential_definition( aries_controller: AcaPyClient, credential_definition: CreateCredentialDefinition, support_revocation: bool, @@ -259,7 +259,7 @@ async def create_cred_def( return credential_definition_id -async def get_cred_defs( +async def get_credential_definitions( aries_controller: AcaPyClient, issuer_did: Optional[str] = None, credential_definition_id: Optional[str] = None, From ff36f89f2e496a057d45173a4b328584ce6c6d4c Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 2 Jul 2024 11:17:05 +0200 Subject: [PATCH 62/74] remove unused imports --- app/services/definitions/definitions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py index a3095a9a9..5be2f5e66 100644 --- a/app/services/definitions/definitions.py +++ b/app/services/definitions/definitions.py @@ -27,7 +27,6 @@ CredentialDefinitionPublisher, ) from app.services.definitions.schema_publisher import SchemaPublisher -from app.services.trust_registry.schemas import register_schema from app.services.trust_registry.util.issuer import assert_valid_issuer from app.util.assert_public_did import assert_public_did from app.util.definitions import ( From 06d2f402799fefb678f08f67300e2b2e426e2453 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 2 Jul 2024 11:20:16 +0200 Subject: [PATCH 63/74] move to schema pub class add check for governance agent --- app/services/definitions/definitions.py | 31 +++++++++++-------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py index 5be2f5e66..dffcb2d62 100644 --- a/app/services/definitions/definitions.py +++ b/app/services/definitions/definitions.py @@ -49,28 +49,25 @@ async def create_schema( Create a schema and register it in the trust registry """ bound_logger = logger.bind(body=schema) - publisher = SchemaPublisher(controller=aries_controller, logger=logger) - try: - result = await publisher.publish_schema(schema_request) - except CloudApiException as e: - if "already exist" in e.detail and e.status_code == 400: - result = await publisher.handle_existing_schema(schema) - else: - bound_logger.warning( - f"An unhandled Exception was caught while publishing schema: {e.detail}" - ) - raise CloudApiException("Error while creating schema.") from e - - if result.sent and result.sent.schema_id: - await register_schema(schema_id=result.sent.schema_id) - else: - bound_logger.error("No SchemaSendResult in `publish_schema` response.") + logger.debug("Asserting governance agent is host being called") + if aries_controller.configuration.host != GOVERNANCE_AGENT_URL: raise CloudApiException( - "An unexpected error occurred: could not publish schema." + "Only governance agents are allowed to access this endpoint.", + status_code=403, ) + schema_request = handle_model_with_validation( + logger=bound_logger, + model_class=SchemaSendRequest, + attributes=schema.attribute_names, + schema_name=schema.name, + schema_version=schema.version, + ) + + result = await publisher.publish_schema(schema_request) + result = credential_schema_from_acapy(result.sent.var_schema) bound_logger.info("Successfully published and registered schema.") return result From 1258a8f266da540e7f32f6e15a7fb83e733c2072 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 2 Jul 2024 11:20:46 +0200 Subject: [PATCH 64/74] removed arg build in func --- app/services/definitions/definitions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py index dffcb2d62..03ba050b9 100644 --- a/app/services/definitions/definitions.py +++ b/app/services/definitions/definitions.py @@ -42,7 +42,6 @@ async def create_schema( aries_controller: AcaPyClient, - schema_request: SchemaSendRequest, schema: CreateSchema, ) -> CredentialSchema: """ From 58154314152625c478377f4baedc50ca31920a89 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 2 Jul 2024 11:26:25 +0200 Subject: [PATCH 65/74] update arg and type use new field names --- app/services/definitions/schema_publisher.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index ae98f3486..f892e3775 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -25,7 +25,9 @@ async def publish_schema( ) return result - async def handle_existing_schema(self, schema: CreateSchema) -> CredentialSchema: + async def _handle_existing_schema( + self, schema: SchemaSendRequest + ) -> CredentialSchema: self._logger.info("Handling case of schema already existing on ledger") self._logger.debug("Fetching public DID for governance controller") pub_did = await handle_acapy_call( @@ -33,7 +35,9 @@ async def handle_existing_schema(self, schema: CreateSchema) -> CredentialSchema acapy_call=self._controller.wallet.get_public_did, ) - _schema_id = f"{pub_did.result.did}:2:{schema.name}:{schema.version}" + _schema_id = ( + f"{pub_did.result.did}:2:{schema.schema_name}:{schema.schema_version}" + ) self._logger.debug( "Fetching schema id `{}` which is associated with request", _schema_id, @@ -56,8 +60,8 @@ async def handle_existing_schema(self, schema: CreateSchema) -> CredentialSchema schemas_created_ids = await handle_acapy_call( logger=self._logger, acapy_call=self._controller.schema.get_created_schemas, - schema_name=schema.name, - schema_version=schema.version, + schema_name=schema.schema_name, + schema_version=schema.schema_version, ) self._logger.debug("Getting schemas associated with fetched ids") schemas: List[SchemaGetResult] = [ @@ -75,7 +79,7 @@ async def handle_existing_schema(self, schema: CreateSchema) -> CredentialSchema raise CloudApiException("Could not publish schema.", 500) if len(schemas) > 1: raise CloudApiException( - f"Multiple schemas with name {schema.name} and version {schema.version} exist." + f"Multiple schemas with name {schema.schema_name} and version {schema.schema_version} exist." f"These are: `{str(schemas_created_ids.schema_ids)}`.", 409, ) @@ -83,10 +87,10 @@ async def handle_existing_schema(self, schema: CreateSchema) -> CredentialSchema _schema: SchemaGetResult = schemas[0] # Schema exists with different attributes - if set(_schema.var_schema.attr_names) != set(schema.attribute_names): + if set(_schema.var_schema.attr_names) != set(schema.attributes): error_message = ( "Error creating schema: Schema already exists with different attribute names. " - f"Given: `{str(set(schema.attribute_names))}`. " + f"Given: `{str(set(schema.attributes))}`. " f"Found: `{str(set(_schema.var_schema.attr_names))}`." ) raise CloudApiException(error_message, 409) From 5df635bbdb68a04e51382522d4ea4d7c87218d44 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 2 Jul 2024 11:26:39 +0200 Subject: [PATCH 66/74] update imports --- app/services/definitions/schema_publisher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index f892e3775..5d4a43581 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -5,7 +5,8 @@ from aries_cloudcontroller.models.txn_or_schema_send_result import TxnOrSchemaSendResult from app.exceptions import CloudApiException, handle_acapy_call -from app.models.definitions import CreateSchema, CredentialSchema +from app.models.definitions import CredentialSchema +from app.services.trust_registry.schemas import register_schema from app.util.definitions import credential_schema_from_acapy From 6f1cfa24ec1b85e6c54906307f377ee822f181e3 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 2 Jul 2024 11:28:08 +0200 Subject: [PATCH 67/74] move error handling here --- app/services/definitions/schema_publisher.py | 29 ++++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index 5d4a43581..1604f6fab 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -18,12 +18,29 @@ def __init__(self, controller: AcaPyClient, logger: Logger): async def publish_schema( self, schema_request: SchemaSendRequest ) -> TxnOrSchemaSendResult: - result = await handle_acapy_call( - logger=self._logger, - acapy_call=self._controller.schema.publish_schema, - body=schema_request, - create_transaction_for_endorser=False, - ) + try: + result = await handle_acapy_call( + logger=self._logger, + acapy_call=self._controller.schema.publish_schema, + body=schema_request, + create_transaction_for_endorser=False, + ) + except CloudApiException as e: + if "already exist" in e.detail and e.status_code == 400: + result = await self._handle_existing_schema(schema_request) + else: + self._logger.warning( + f"An unhandled Exception was caught while publishing schema: {e.detail}" + ) + raise CloudApiException("Error while creating schema.") from e + + if result.sent and result.sent.schema_id: + await register_schema(schema_id=result.sent.schema_id) + else: + self._logger.error("No SchemaSendResult in `publish_schema` response.") + raise CloudApiException( + "An unexpected error occurred: could not publish schema." + ) return result async def _handle_existing_schema( From cfe7147ec03b40fe92f3da133b4de28d10bf61e6 Mon Sep 17 00:00:00 2001 From: cl0ete Date: Tue, 2 Jul 2024 11:33:36 +0200 Subject: [PATCH 68/74] remove unused imports --- app/routes/definitions.py | 3 +-- app/services/definitions/credential_definition_publisher.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index db71ef97e..db2c69a5c 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -1,6 +1,5 @@ from typing import List, Optional -from aries_cloudcontroller import SchemaSendRequest from fastapi import APIRouter, Depends, HTTPException from app.dependencies.acapy_clients import client_from_auth, get_governance_controller @@ -12,7 +11,7 @@ acapy_auth_verified, ) from app.dependencies.role import Role -from app.exceptions import handle_acapy_call, handle_model_with_validation +from app.exceptions import handle_acapy_call from app.exceptions.cloudapi_exception import CloudApiException from app.models.definitions import ( CreateCredentialDefinition, diff --git a/app/services/definitions/credential_definition_publisher.py b/app/services/definitions/credential_definition_publisher.py index 9d76c0d60..14079944e 100644 --- a/app/services/definitions/credential_definition_publisher.py +++ b/app/services/definitions/credential_definition_publisher.py @@ -4,7 +4,6 @@ from aries_cloudcontroller import AcaPyClient from app.exceptions import CloudApiException, handle_acapy_call -from app.services import acapy_wallet from app.services.revocation_registry import wait_for_active_registry from app.util.check_endorser_connection import check_endorser_connection from shared import REGISTRY_CREATION_TIMEOUT From fe4fe7cb1b5801f28761f827171ff32787a91fbb Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 5 Jul 2024 16:13:10 +0300 Subject: [PATCH 69/74] :art: init file --- app/services/definitions/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/services/definitions/__init__.py diff --git a/app/services/definitions/__init__.py b/app/services/definitions/__init__.py new file mode 100644 index 000000000..e69de29bb From 8848b131a0a0b2d8a6d26e8644a19c7b44866090 Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 5 Jul 2024 16:13:23 +0300 Subject: [PATCH 70/74] :truck: move schema specific methods to own module --- app/services/definitions/definitions.py | 196 +----------------------- app/services/definitions/schemas.py | 186 ++++++++++++++++++++++ 2 files changed, 190 insertions(+), 192 deletions(-) create mode 100644 app/services/definitions/schemas.py diff --git a/app/services/definitions/definitions.py b/app/services/definitions/definitions.py index 03ba050b9..21aff6e90 100644 --- a/app/services/definitions/definitions.py +++ b/app/services/definitions/definitions.py @@ -1,210 +1,22 @@ import asyncio from typing import List, Optional -from aries_cloudcontroller import ( - AcaPyClient, - CredentialDefinitionSendRequest, - SchemaGetResult, - SchemaSendRequest, -) +from aries_cloudcontroller import AcaPyClient, CredentialDefinitionSendRequest -from app.exceptions import ( - CloudApiException, - handle_acapy_call, - handle_model_with_validation, -) -from app.models.definitions import ( - CreateCredentialDefinition, - CreateSchema, - CredentialDefinition, - CredentialSchema, -) -from app.routes.trust_registry import ( - get_schema_by_id as get_trust_registry_schema_by_id, -) -from app.routes.trust_registry import get_schemas as get_trust_registry_schemas +from app.exceptions import handle_acapy_call, handle_model_with_validation +from app.models.definitions import CreateCredentialDefinition, CredentialDefinition from app.services.definitions.credential_definition_publisher import ( CredentialDefinitionPublisher, ) -from app.services.definitions.schema_publisher import SchemaPublisher from app.services.trust_registry.util.issuer import assert_valid_issuer from app.util.assert_public_did import assert_public_did -from app.util.definitions import ( - credential_definition_from_acapy, - credential_schema_from_acapy, -) +from app.util.definitions import credential_definition_from_acapy from app.util.transaction_acked import wait_for_transaction_ack -from shared.constants import GOVERNANCE_AGENT_URL from shared.log_config import get_logger logger = get_logger(__name__) -async def create_schema( - aries_controller: AcaPyClient, - schema: CreateSchema, -) -> CredentialSchema: - """ - Create a schema and register it in the trust registry - """ - bound_logger = logger.bind(body=schema) - publisher = SchemaPublisher(controller=aries_controller, logger=logger) - - logger.debug("Asserting governance agent is host being called") - if aries_controller.configuration.host != GOVERNANCE_AGENT_URL: - raise CloudApiException( - "Only governance agents are allowed to access this endpoint.", - status_code=403, - ) - - schema_request = handle_model_with_validation( - logger=bound_logger, - model_class=SchemaSendRequest, - attributes=schema.attribute_names, - schema_name=schema.name, - schema_version=schema.version, - ) - - result = await publisher.publish_schema(schema_request) - - result = credential_schema_from_acapy(result.sent.var_schema) - bound_logger.info("Successfully published and registered schema.") - return result - - -async def get_schemas_as_tenant( - aries_controller: AcaPyClient, - schema_id: Optional[str] = None, - schema_issuer_did: Optional[str] = None, - schema_name: Optional[str] = None, - schema_version: Optional[str] = None, -) -> List[CredentialSchema]: - """ - Allows tenants to get all schemas from trust registry - """ - bound_logger = logger.bind( - body={ - "schema_id": schema_id, - "schema_issuer_did": schema_issuer_did, - "schema_name": schema_name, - "schema_version": schema_version, - } - ) - bound_logger.debug("Fetching schemas from trust registry") - - if not schema_id: # client is not filtering by schema_id, fetch all - trust_registry_schemas = await get_trust_registry_schemas() - else: # fetch specific id - trust_registry_schemas = [await get_trust_registry_schema_by_id(schema_id)] - - schema_ids = [schema.id for schema in trust_registry_schemas] - - bound_logger.debug("Getting schemas associated with fetched ids") - schemas = await get_schemas_by_id( - aries_controller=aries_controller, - schema_ids=schema_ids, - ) - - if schema_issuer_did: - schemas = [ - schema for schema in schemas if schema.id.split(":")[0] == schema_issuer_did - ] - if schema_name: - schemas = [schema for schema in schemas if schema.name == schema_name] - if schema_version: - schemas = [schema for schema in schemas if schema.version == schema_version] - - return schemas - - -async def get_schemas_as_governance( - aries_controller: AcaPyClient, - schema_id: Optional[str] = None, - schema_issuer_did: Optional[str] = None, - schema_name: Optional[str] = None, - schema_version: Optional[str] = None, -) -> List[CredentialSchema]: - """ - Governance agents gets all schemas created by itself - """ - bound_logger = logger.bind( - body={ - "schema_id": schema_id, - "schema_issuer_did": schema_issuer_did, - "schema_name": schema_name, - "schema_version": schema_version, - } - ) - - logger.debug("Asserting governance agent is host being called") - if aries_controller.configuration.host != GOVERNANCE_AGENT_URL: - raise CloudApiException( - "Only governance agents are allowed to access this endpoint.", - status_code=403, - ) - - # Get all created schema ids that match the filter - bound_logger.debug("Fetching created schemas") - response = await handle_acapy_call( - logger=bound_logger, - acapy_call=aries_controller.schema.get_created_schemas, - schema_id=schema_id, - schema_issuer_did=schema_issuer_did, - schema_name=schema_name, - schema_version=schema_version, - ) - - # Initiate retrieving all schemas - schema_ids = response.schema_ids or [] - - bound_logger.debug("Getting schemas associated with fetched ids") - schemas = await get_schemas_by_id( - aries_controller=aries_controller, - schema_ids=schema_ids, - ) - - return schemas - - -async def get_schemas_by_id( - aries_controller: AcaPyClient, - schema_ids: List[str], -) -> List[CredentialSchema]: - """ - Fetch schemas with attributes using schema IDs. - The following logic applies to both governance and tenant calls. - Retrieve the relevant schemas from the ledger: - """ - logger.debug("Fetching schemas from schema ids") - - get_schema_futures = [ - handle_acapy_call( - logger=logger, - acapy_call=aries_controller.schema.get_schema, - schema_id=schema_id, - ) - for schema_id in schema_ids - ] - - # Wait for completion of retrieval and transform all schemas into response model (if a schema was returned) - if get_schema_futures: - logger.debug("Fetching each of the created schemas") - schema_results: List[SchemaGetResult] = await asyncio.gather( - *get_schema_futures - ) - else: - logger.debug("No created schema ids returned") - schema_results = [] - - schemas = [ - credential_schema_from_acapy(schema.var_schema) - for schema in schema_results - if schema.var_schema - ] - - return schemas - - async def create_credential_definition( aries_controller: AcaPyClient, credential_definition: CreateCredentialDefinition, diff --git a/app/services/definitions/schemas.py b/app/services/definitions/schemas.py new file mode 100644 index 000000000..355de1829 --- /dev/null +++ b/app/services/definitions/schemas.py @@ -0,0 +1,186 @@ +import asyncio +from typing import List, Optional + +from aries_cloudcontroller import AcaPyClient, SchemaGetResult, SchemaSendRequest + +from app.exceptions import ( + CloudApiException, + handle_acapy_call, + handle_model_with_validation, +) +from app.models.definitions import CreateSchema, CredentialSchema +from app.routes.trust_registry import ( + get_schema_by_id as get_trust_registry_schema_by_id, +) +from app.routes.trust_registry import get_schemas as get_trust_registry_schemas +from app.services.definitions.schema_publisher import SchemaPublisher +from app.util.definitions import credential_schema_from_acapy +from shared.constants import GOVERNANCE_AGENT_URL +from shared.log_config import get_logger + +logger = get_logger(__name__) + + +async def create_schema( + aries_controller: AcaPyClient, + schema: CreateSchema, +) -> CredentialSchema: + """ + Create a schema and register it in the trust registry + """ + bound_logger = logger.bind(body=schema) + publisher = SchemaPublisher(controller=aries_controller, logger=logger) + + logger.debug("Asserting governance agent is host being called") + if aries_controller.configuration.host != GOVERNANCE_AGENT_URL: + raise CloudApiException( + "Only governance agents are allowed to access this endpoint.", + status_code=403, + ) + + schema_request = handle_model_with_validation( + logger=bound_logger, + model_class=SchemaSendRequest, + attributes=schema.attribute_names, + schema_name=schema.name, + schema_version=schema.version, + ) + + result = await publisher.publish_schema(schema_request) + + result = credential_schema_from_acapy(result.sent.var_schema) + bound_logger.info("Successfully published and registered schema.") + return result + + +async def get_schemas_as_tenant( + aries_controller: AcaPyClient, + schema_id: Optional[str] = None, + schema_issuer_did: Optional[str] = None, + schema_name: Optional[str] = None, + schema_version: Optional[str] = None, +) -> List[CredentialSchema]: + """ + Allows tenants to get all schemas from trust registry + """ + bound_logger = logger.bind( + body={ + "schema_id": schema_id, + "schema_issuer_did": schema_issuer_did, + "schema_name": schema_name, + "schema_version": schema_version, + } + ) + bound_logger.debug("Fetching schemas from trust registry") + + if not schema_id: # client is not filtering by schema_id, fetch all + trust_registry_schemas = await get_trust_registry_schemas() + else: # fetch specific id + trust_registry_schemas = [await get_trust_registry_schema_by_id(schema_id)] + + schema_ids = [schema.id for schema in trust_registry_schemas] + + bound_logger.debug("Getting schemas associated with fetched ids") + schemas = await get_schemas_by_id( + aries_controller=aries_controller, + schema_ids=schema_ids, + ) + + if schema_issuer_did: + schemas = [ + schema for schema in schemas if schema.id.split(":")[0] == schema_issuer_did + ] + if schema_name: + schemas = [schema for schema in schemas if schema.name == schema_name] + if schema_version: + schemas = [schema for schema in schemas if schema.version == schema_version] + + return schemas + + +async def get_schemas_as_governance( + aries_controller: AcaPyClient, + schema_id: Optional[str] = None, + schema_issuer_did: Optional[str] = None, + schema_name: Optional[str] = None, + schema_version: Optional[str] = None, +) -> List[CredentialSchema]: + """ + Governance agents gets all schemas created by itself + """ + bound_logger = logger.bind( + body={ + "schema_id": schema_id, + "schema_issuer_did": schema_issuer_did, + "schema_name": schema_name, + "schema_version": schema_version, + } + ) + + logger.debug("Asserting governance agent is host being called") + if aries_controller.configuration.host != GOVERNANCE_AGENT_URL: + raise CloudApiException( + "Only governance agents are allowed to access this endpoint.", + status_code=403, + ) + + # Get all created schema ids that match the filter + bound_logger.debug("Fetching created schemas") + response = await handle_acapy_call( + logger=bound_logger, + acapy_call=aries_controller.schema.get_created_schemas, + schema_id=schema_id, + schema_issuer_did=schema_issuer_did, + schema_name=schema_name, + schema_version=schema_version, + ) + + # Initiate retrieving all schemas + schema_ids = response.schema_ids or [] + + bound_logger.debug("Getting schemas associated with fetched ids") + schemas = await get_schemas_by_id( + aries_controller=aries_controller, + schema_ids=schema_ids, + ) + + return schemas + + +async def get_schemas_by_id( + aries_controller: AcaPyClient, + schema_ids: List[str], +) -> List[CredentialSchema]: + """ + Fetch schemas with attributes using schema IDs. + The following logic applies to both governance and tenant calls. + Retrieve the relevant schemas from the ledger: + """ + logger.debug("Fetching schemas from schema ids") + + get_schema_futures = [ + handle_acapy_call( + logger=logger, + acapy_call=aries_controller.schema.get_schema, + schema_id=schema_id, + ) + for schema_id in schema_ids + ] + + # Wait for completion of retrieval and transform all schemas into response model (if a schema was returned) + if get_schema_futures: + logger.debug("Fetching each of the created schemas") + schema_results: List[SchemaGetResult] = await asyncio.gather( + *get_schema_futures + ) + else: + logger.debug("No created schema ids returned") + schema_results = [] + + schemas = [ + credential_schema_from_acapy(schema.var_schema) + for schema in schema_results + if schema.var_schema + ] + + return schemas From 8afcc479dd2f6cc4543e9ad0d9ba28a2eba5304e Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 5 Jul 2024 16:13:50 +0300 Subject: [PATCH 71/74] :art: rename module for clarity --- .../definitions/{definitions.py => credential_definitions.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/services/definitions/{definitions.py => credential_definitions.py} (100%) diff --git a/app/services/definitions/definitions.py b/app/services/definitions/credential_definitions.py similarity index 100% rename from app/services/definitions/definitions.py rename to app/services/definitions/credential_definitions.py From adf91020ff1107e92b2efbfb1d350e8235ce1009 Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 5 Jul 2024 16:15:56 +0300 Subject: [PATCH 72/74] :art: update imports and method references --- app/routes/definitions.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/app/routes/definitions.py b/app/routes/definitions.py index db2c69a5c..8835a4a91 100644 --- a/app/routes/definitions.py +++ b/app/routes/definitions.py @@ -2,6 +2,8 @@ from fastapi import APIRouter, Depends, HTTPException +import app.services.definitions.credential_definitions as cred_def_service +import app.services.definitions.schemas as schemas_service from app.dependencies.acapy_clients import client_from_auth, get_governance_controller from app.dependencies.auth import ( AcaPyAuth, @@ -19,17 +21,6 @@ CredentialDefinition, CredentialSchema, ) -from app.services.definitions.definitions import ( - create_credential_definition as create_cred_def, -) -from app.services.definitions.definitions import create_schema as create_schema_service -from app.services.definitions.definitions import ( - get_credential_definitions as get_cred_defs, -) -from app.services.definitions.definitions import ( - get_schemas_as_governance, - get_schemas_as_tenant, -) from app.util.definitions import ( credential_definition_from_acapy, credential_schema_from_acapy, @@ -81,7 +72,7 @@ async def create_schema( bound_logger.info("POST request received: Create schema (publish and register)") async with get_governance_controller(governance_auth) as aries_controller: - schema_response = await create_schema_service( + schema_response = await schemas_service.create_schema( aries_controller=aries_controller, schema=schema, ) @@ -140,7 +131,7 @@ async def get_schemas( async with client_from_auth(auth) as aries_controller: if not is_governance: # regular tenant is calling endpoint - schemas = await get_schemas_as_tenant( + schemas = await schemas_service.get_schemas_as_tenant( aries_controller=aries_controller, schema_id=schema_id, schema_issuer_did=schema_issuer_did, @@ -150,7 +141,7 @@ async def get_schemas( else: # Governance is calling the endpoint try: - schemas = await get_schemas_as_governance( + schemas = await schemas_service.get_schemas_as_governance( aries_controller=aries_controller, schema_id=schema_id, schema_issuer_did=schema_issuer_did, @@ -262,7 +253,7 @@ async def create_credential_definition( support_revocation = credential_definition.support_revocation async with client_from_auth(auth) as aries_controller: - credential_definition_id = await create_cred_def( + credential_definition_id = await cred_def_service.create_credential_definition( aries_controller=aries_controller, credential_definition=credential_definition, support_revocation=support_revocation, @@ -335,7 +326,7 @@ async def get_credential_definitions( # Get all created credential definition ids that match the filter async with client_from_auth(auth) as aries_controller: - credential_definitions = await get_cred_defs( + credential_definitions = await cred_def_service.get_credential_definitions( aries_controller=aries_controller, issuer_did=issuer_did, credential_definition_id=credential_definition_id, From 537776c9f56db9f0e228e447d08d92606df87e16 Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 5 Jul 2024 16:17:04 +0300 Subject: [PATCH 73/74] :art: rearrange if condition --- app/services/definitions/schemas.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/definitions/schemas.py b/app/services/definitions/schemas.py index 355de1829..d6bd92efc 100644 --- a/app/services/definitions/schemas.py +++ b/app/services/definitions/schemas.py @@ -73,10 +73,10 @@ async def get_schemas_as_tenant( ) bound_logger.debug("Fetching schemas from trust registry") - if not schema_id: # client is not filtering by schema_id, fetch all - trust_registry_schemas = await get_trust_registry_schemas() - else: # fetch specific id + if schema_id: # fetch specific id trust_registry_schemas = [await get_trust_registry_schema_by_id(schema_id)] + else: # client is not filtering by schema_id, fetch all + trust_registry_schemas = await get_trust_registry_schemas() schema_ids = [schema.id for schema in trust_registry_schemas] From 6b757c54cc4986453b4216b12b360208d532e718 Mon Sep 17 00:00:00 2001 From: ff137 Date: Fri, 5 Jul 2024 16:26:10 +0300 Subject: [PATCH 74/74] :art: fit in max lines --- .../credential_definition_publisher.py | 14 +++++---- app/services/definitions/schema_publisher.py | 31 +++++++++++-------- app/services/definitions/schemas.py | 3 +- app/util/assert_public_did.py | 4 +-- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/app/services/definitions/credential_definition_publisher.py b/app/services/definitions/credential_definition_publisher.py index 14079944e..698d8c2d7 100644 --- a/app/services/definitions/credential_definition_publisher.py +++ b/app/services/definitions/credential_definition_publisher.py @@ -21,13 +21,15 @@ async def check_endorser_connection(self): if not has_connections: self._logger.error( - "Failed to create credential definition supporting revocation: no endorser connection found. " - "Issuer attempted to create a credential definition with support for revocation but does not " - "have an active connection with an endorser, which is required for this operation." + "Failed to create credential definition supporting revocation: " + "no endorser connection found. Issuer attempted to create a credential " + "definition with support for revocation but does not have an active " + "connection with an endorser, which is required for this operation." ) raise CloudApiException( - "Credential definition creation failed: An active endorser connection is required " - "to support revocation. Please establish a connection with an endorser and try again." + "Credential definition creation failed: An active endorser connection " + "is required to support revocation. Please establish a connection with " + "an endorser and try again." ) async def publish_credential_definition(self, request_body): @@ -39,7 +41,7 @@ async def publish_credential_definition(self, request_body): ) except CloudApiException as e: self._logger.warning( - "An Exception was caught while publishing credential definition: `{}` `{}`", + "An Exception was caught while publishing cred def: `{}` `{}`", e.detail, e.status_code, ) diff --git a/app/services/definitions/schema_publisher.py b/app/services/definitions/schema_publisher.py index 1604f6fab..9fa350fa4 100644 --- a/app/services/definitions/schema_publisher.py +++ b/app/services/definitions/schema_publisher.py @@ -1,8 +1,12 @@ from logging import Logger from typing import List -from aries_cloudcontroller import AcaPyClient, SchemaGetResult, SchemaSendRequest -from aries_cloudcontroller.models.txn_or_schema_send_result import TxnOrSchemaSendResult +from aries_cloudcontroller import ( + AcaPyClient, + SchemaGetResult, + SchemaSendRequest, + TxnOrSchemaSendResult, +) from app.exceptions import CloudApiException, handle_acapy_call from app.models.definitions import CredentialSchema @@ -30,7 +34,8 @@ async def publish_schema( result = await self._handle_existing_schema(schema_request) else: self._logger.warning( - f"An unhandled Exception was caught while publishing schema: {e.detail}" + "An unhandled Exception was caught while publishing schema: {}", + e.detail, ) raise CloudApiException("Error while creating schema.") from e @@ -68,12 +73,12 @@ async def _handle_existing_schema( ) # Edge case where the governance agent has changed its public did - # Then we need to retrieve the schema in a different way as constructing the schema ID the way above - # will not be correct due to different public did. + # Then we need to retrieve the schema in a different way as constructing + # the schema ID the way above will not be correct due to different public did. if _schema.var_schema is None: self._logger.debug( "Schema not found. Governance agent may have changed public DID. " - "Fetching schemas created by governance agent with request name and version" + "Fetching schemas created by governance with requested name and version" ) schemas_created_ids = await handle_acapy_call( logger=self._logger, @@ -93,22 +98,22 @@ async def _handle_existing_schema( ] if not schemas: - # if schema already exists, we should at least fetch 1, so this should never happen raise CloudApiException("Could not publish schema.", 500) if len(schemas) > 1: - raise CloudApiException( - f"Multiple schemas with name {schema.schema_name} and version {schema.schema_version} exist." - f"These are: `{str(schemas_created_ids.schema_ids)}`.", - 409, + error_message = ( + f"Multiple schemas with name {schema.schema_name} " + f"and version {schema.schema_version} exist." + f"These are: `{str(schemas_created_ids.schema_ids)}`." ) + raise CloudApiException(error_message, 409) self._logger.debug("Using updated schema id with new DID") _schema: SchemaGetResult = schemas[0] # Schema exists with different attributes if set(_schema.var_schema.attr_names) != set(schema.attributes): error_message = ( - "Error creating schema: Schema already exists with different attribute names. " - f"Given: `{str(set(schema.attributes))}`. " + "Error creating schema: Schema already exists with different attribute " + f"names. Given: `{str(set(schema.attributes))}`. " f"Found: `{str(set(_schema.var_schema.attr_names))}`." ) raise CloudApiException(error_message, 409) diff --git a/app/services/definitions/schemas.py b/app/services/definitions/schemas.py index d6bd92efc..9056589a2 100644 --- a/app/services/definitions/schemas.py +++ b/app/services/definitions/schemas.py @@ -167,7 +167,7 @@ async def get_schemas_by_id( for schema_id in schema_ids ] - # Wait for completion of retrieval and transform all schemas into response model (if a schema was returned) + # Wait for completion of futures if get_schema_futures: logger.debug("Fetching each of the created schemas") schema_results: List[SchemaGetResult] = await asyncio.gather( @@ -177,6 +177,7 @@ async def get_schemas_by_id( logger.debug("No created schema ids returned") schema_results = [] + # transform all schemas into response model (if schemas returned) schemas = [ credential_schema_from_acapy(schema.var_schema) for schema in schema_results diff --git a/app/util/assert_public_did.py b/app/util/assert_public_did.py index 9c77f54b3..6a43baab1 100644 --- a/app/util/assert_public_did.py +++ b/app/util/assert_public_did.py @@ -26,8 +26,8 @@ async def assert_public_did(aries_controller: AcaPyClient) -> str: else: logger.error(log_message) client_error_message = ( - "Something went wrong while asserting if request is from a valid issuer. " - "Please try again." + "Something went wrong while asserting if request is from a valid " + "issuer. Please try again." ) raise CloudApiException(client_error_message, e.status_code) from e