Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a service for definitions endpoints #842

Merged
merged 75 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
0af2d9f
move to service
cl0ete Jun 19, 2024
bd1b29f
move to service and call service based on tenant
cl0ete Jun 19, 2024
797d1d3
move to service , call create from service
cl0ete Jun 19, 2024
5218065
move to service and call service here
cl0ete Jun 19, 2024
24aecd1
imports
cl0ete Jun 19, 2024
ee8efc1
moved create schema logic here
cl0ete Jun 19, 2024
c5d26d1
service for tenants to get schemas
cl0ete Jun 19, 2024
2082aaa
service for governance to get schemas
cl0ete Jun 19, 2024
b54599e
from schema_ids get full schemas
cl0ete Jun 19, 2024
550394d
moved bulk of create cred_def logic here
cl0ete Jun 19, 2024
324d95e
moved get cred_def logic here
cl0ete Jun 19, 2024
db01a95
import dataclass for service dependencies
cl0ete Jun 22, 2024
2a6480f
add service dependencies dataclass
cl0ete Jun 22, 2024
4a907ec
add schema publisher class
cl0ete Jun 22, 2024
600783b
add class to publish schema to trust registry
cl0ete Jun 22, 2024
62aea63
refactor create schema service
cl0ete Jun 22, 2024
ac5409f
add helper class cred def publisher
cl0ete Jun 22, 2024
33d2ec2
refactor create cred def service
cl0ete Jun 22, 2024
1e2d670
move to helper class
cl0ete Jun 22, 2024
5b7eb8a
formatting
cl0ete Jun 22, 2024
6e231ba
rework logging
cl0ete Jun 24, 2024
54d9f5c
redo logging not passing logger down
cl0ete Jun 24, 2024
1f6ce47
update names
cl0ete Jun 25, 2024
db0e3b5
move to definitions folder
cl0ete Jun 25, 2024
b06eb91
move to cred_def publisher class to its own module
cl0ete Jun 25, 2024
1f83bbe
move schema publisher class to own module
cl0ete Jun 25, 2024
14cb394
make assert public did a util function
cl0ete Jun 25, 2024
b4d9f89
add check endorser connection function
cl0ete Jun 25, 2024
8e1c438
add wait for transaction acked util function
cl0ete Jun 25, 2024
b974797
assert governance is calling function
cl0ete Jun 26, 2024
f983339
formatting
cl0ete Jun 26, 2024
a72fca1
add state to get connection connection must be completed
cl0ete Jun 27, 2024
e922622
formatting
cl0ete Jun 27, 2024
75685e2
formatting
cl0ete Jun 27, 2024
741f934
update doc strings
cl0ete Jun 27, 2024
900382f
fix indentation
cl0ete Jun 27, 2024
8c1624b
add return types
cl0ete Jun 27, 2024
7eb23f0
reorder logic
cl0ete Jun 27, 2024
f628a60
add return types
cl0ete Jun 27, 2024
0b7f618
add return types
cl0ete Jun 27, 2024
59509ea
handle exception if not governance
cl0ete Jun 27, 2024
ab6a5b7
call register schema directly
cl0ete Jun 27, 2024
992d845
raise exception if not governance role
cl0ete Jun 27, 2024
ed51325
formatting
cl0ete Jun 27, 2024
a85d702
update imports
cl0ete Jun 27, 2024
3a17748
formatting
cl0ete Jun 27, 2024
c8d424a
formatting
cl0ete Jun 27, 2024
7b13494
removed function calling it directly
cl0ete Jun 27, 2024
546c7fb
update tests
cl0ete Jun 27, 2024
359e97a
Refactor trust registry client code for better error handling and con…
cl0ete Jun 27, 2024
3c2a719
formatting
cl0ete Jun 27, 2024
36d8043
fix status code on exception
cl0ete Jun 27, 2024
23560de
formatting
cl0ete Jun 27, 2024
6f1934f
raise from error
cl0ete Jun 27, 2024
4dd3588
formatting
cl0ete Jul 1, 2024
e9fb74e
add default values
cl0ete Jul 1, 2024
24a6820
add logs
cl0ete Jul 1, 2024
20242df
move to service
cl0ete Jul 2, 2024
097ec8b
update imports
cl0ete Jul 2, 2024
ddb14c3
not used removed
cl0ete Jul 2, 2024
2408ba3
update function names
cl0ete Jul 2, 2024
ff36f89
remove unused imports
cl0ete Jul 2, 2024
06d2f40
move to schema pub class add check for governance agent
cl0ete Jul 2, 2024
1258a8f
removed arg build in func
cl0ete Jul 2, 2024
5815431
update arg and type use new field names
cl0ete Jul 2, 2024
5df635b
update imports
cl0ete Jul 2, 2024
6f1cfa2
move error handling here
cl0ete Jul 2, 2024
cfe7147
remove unused imports
cl0ete Jul 2, 2024
fe4fe7c
:art: init file
ff137 Jul 5, 2024
8848b13
:truck: move schema specific methods to own module
ff137 Jul 5, 2024
8afcc47
:art: rename module for clarity
ff137 Jul 5, 2024
adf9102
:art: update imports and method references
ff137 Jul 5, 2024
537776c
:art: rearrange if condition
ff137 Jul 5, 2024
6b757c5
:art: fit in max lines
ff137 Jul 5, 2024
9da5dfb
Merge branch 'development' into definitions-service
ff137 Jul 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
393 changes: 30 additions & 363 deletions app/routes/definitions.py

Large diffs are not rendered by default.

Empty file.
74 changes: 74 additions & 0 deletions app/services/definitions/credential_definition_publisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import asyncio
from logging import Logger

from aries_cloudcontroller import AcaPyClient

from app.exceptions import CloudApiException, handle_acapy_call
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 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 cred def: `{}` `{}`",
e.detail,
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
Dismissed Show dismissed Hide dismissed
else:
self._logger.error(
"Error while creating credential definition: `{}`", e.detail
)
raise CloudApiException(
Dismissed Show dismissed Hide dismissed
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
132 changes: 132 additions & 0 deletions app/services/definitions/credential_definitions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import asyncio
Dismissed Show dismissed Hide dismissed
from typing import List, Optional

from aries_cloudcontroller import AcaPyClient, CredentialDefinitionSendRequest

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.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
from app.util.transaction_acked import wait_for_transaction_ack
from shared.log_config import get_logger

logger = get_logger(__name__)


async def create_credential_definition(
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_credential_definitions(
Dismissed Show dismissed Hide dismissed
aries_controller: AcaPyClient,
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
"""
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
126 changes: 126 additions & 0 deletions app/services/definitions/schema_publisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from logging import Logger
from typing import List

from aries_cloudcontroller import (
AcaPyClient,
SchemaGetResult,
SchemaSendRequest,
TxnOrSchemaSendResult,
)

from app.exceptions import CloudApiException, handle_acapy_call
from app.models.definitions import CredentialSchema
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
) -> TxnOrSchemaSendResult:
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(
"An unhandled Exception was caught while publishing schema: {}",
e.detail,
)
raise CloudApiException("Error while creating schema.") from e
Dismissed Show dismissed Hide dismissed

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(
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(
logger=self._logger,
acapy_call=self._controller.wallet.get_public_did,
)

_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,
)

_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 with requested name and version"
)
schemas_created_ids = await handle_acapy_call(
logger=self._logger,
acapy_call=self._controller.schema.get_created_schemas,
schema_name=schema.schema_name,
schema_version=schema.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 not schemas:
raise CloudApiException("Could not publish schema.", 500)
if len(schemas) > 1:
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 "
f"names. Given: `{str(set(schema.attributes))}`. "
f"Found: `{str(set(_schema.var_schema.attr_names))}`."
)
raise CloudApiException(error_message, 409)

result = credential_schema_from_acapy(_schema.var_schema)
self._logger.info(
"Schema already exists on ledger. Returning schema definition: `{}`.",
result,
)
return result
Loading
Loading