From 4b159981b5d079e46fe7de8e11549e9ae3c77edb Mon Sep 17 00:00:00 2001 From: jamshale Date: Fri, 29 Dec 2023 19:11:14 +0000 Subject: [PATCH 1/3] Improve api documentation and error handling Signed-off-by: jamshale --- aries_cloudagent/anoncreds/base.py | 5 +- .../anoncreds/default/legacy_indy/registry.py | 12 +- aries_cloudagent/anoncreds/issuer.py | 4 +- .../anoncreds/models/anoncreds_cred_def.py | 30 ++- .../anoncreds/models/anoncreds_revocation.py | 46 +++- .../anoncreds/models/anoncreds_schema.py | 22 +- aries_cloudagent/anoncreds/registry.py | 9 + aries_cloudagent/anoncreds/routes.py | 239 ++++++++++++------ .../anoncreds/tests/test_routes.py | 32 ++- 9 files changed, 278 insertions(+), 121 deletions(-) diff --git a/aries_cloudagent/anoncreds/base.py b/aries_cloudagent/anoncreds/base.py index 6627beab8a..e2214f4027 100644 --- a/aries_cloudagent/anoncreds/base.py +++ b/aries_cloudagent/anoncreds/base.py @@ -103,7 +103,10 @@ def supported_identifiers_regex(self) -> Pattern: async def supports(self, identifier: str) -> bool: """Determine whether this registry supports the given identifier.""" - return bool(self.supported_identifiers_regex.match(identifier)) + try: + return bool(self.supported_identifiers_regex.match(identifier)) + except TypeError: + return False @abstractmethod async def setup(self, context: InjectionContext): diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 9f1f95ed16..e3168c8974 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -636,6 +636,12 @@ async def register_revocation_list( profile, rev_list, rev_reg_def.type, rev_reg_entry ) + seq_no = None + try: + seq_no = rev_entry_res["result"]["txnMetadata"]["seqNo"] + except KeyError: + LOGGER.warning("Failed to parse sequence number from ledger response") + return RevListResult( job_id=None, revocation_list_state=RevListState( @@ -643,8 +649,10 @@ async def register_revocation_list( revocation_list=rev_list, ), registration_metadata={}, - revocation_list_metadata={ - "seqNo": rev_entry_res["result"]["txnMetadata"]["seqNo"], + revocation_list_metadata={} + if seq_no is None + else { + "seqNo": seq_no, }, ) diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 2e99cef3b4..9a5333f577 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -6,8 +6,6 @@ from time import time from typing import Optional, Sequence -from aries_askar import AskarError - from anoncreds import ( AnoncredsError, Credential, @@ -15,7 +13,7 @@ CredentialOffer, Schema, ) - +from aries_askar import AskarError from ..askar.profile_anon import ( AskarAnoncredsProfile, diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index aad704980b..70dc3619c5 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -1,14 +1,19 @@ """Anoncreds cred def OpenAPI validators.""" from typing import Optional -from typing_extensions import Literal from anoncreds import CredentialDefinition from marshmallow import EXCLUDE, fields from marshmallow.validate import OneOf +from typing_extensions import Literal from ...messaging.models.base import BaseModel, BaseModelSchema -from ...messaging.valid import NUM_STR_WHOLE_VALIDATE, NUM_STR_WHOLE_EXAMPLE - +from ...messaging.valid import ( + INDY_CRED_DEF_ID_EXAMPLE, + INDY_OR_KEY_DID_EXAMPLE, + INDY_SCHEMA_ID_EXAMPLE, + NUM_STR_WHOLE_EXAMPLE, + NUM_STR_WHOLE_VALIDATE, +) NUM_STR_WHOLE = {"validate": NUM_STR_WHOLE_VALIDATE, "example": NUM_STR_WHOLE_EXAMPLE} @@ -239,12 +244,18 @@ class Meta: issuer_id = fields.Str( description="Issuer Identifier of the credential definition or schema", data_key="issuerId", + example=INDY_OR_KEY_DID_EXAMPLE, + ) + schema_id = fields.Str( + data_key="schemaId", + description="Schema identifier", + example=INDY_SCHEMA_ID_EXAMPLE, ) - schema_id = fields.Str(data_key="schemaId", description="Schema identifier") type = fields.Str(validate=OneOf(["CL"])) tag = fields.Str( description="""The tag value passed in by the Issuer to - an AnonCred's Credential Definition create and store implementation.""" + an AnonCred's Credential Definition create and store implementation.""", + example="default", ) value = fields.Nested(CredDefValueSchema()) @@ -303,7 +314,9 @@ class Meta: ) ) credential_definition_id = fields.Str( - description="credential definition id", allow_none=True + description="credential definition id", + allow_none=True, + example=INDY_CRED_DEF_ID_EXAMPLE, ) credential_definition = fields.Nested( CredDefSchema(), description="credential definition" @@ -403,7 +416,10 @@ class Meta: model_class = GetCredDefResult unknown = EXCLUDE - credential_definition_id = fields.Str(description="credential definition id") + credential_definition_id = fields.Str( + description="credential definition id", + example=INDY_CRED_DEF_ID_EXAMPLE, + ) credential_definition = fields.Nested( CredDefSchema(), description="credential definition" ) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py index 6e5bb830b0..3adde8bfa6 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py @@ -1,10 +1,18 @@ """Anoncreds cred def OpenAPI validators.""" from typing import Any, Dict, List, Optional -from typing_extensions import Literal from anoncreds import RevocationRegistryDefinition, RevocationStatusList from marshmallow import EXCLUDE, fields from marshmallow.validate import OneOf +from typing_extensions import Literal + +from aries_cloudagent.messaging.valid import ( + INDY_CRED_DEF_ID_EXAMPLE, + INDY_ISO8601_DATETIME_EXAMPLE, + INDY_OR_KEY_DID_EXAMPLE, + INDY_RAW_PUBLIC_KEY_EXAMPLE, + INDY_REV_REG_ID_EXAMPLE, +) from ...messaging.models.base import BaseModel, BaseModelSchema @@ -52,10 +60,17 @@ class Meta: model_class = RevRegDefValue unknown = EXCLUDE - public_keys = fields.Dict(data_key="publicKeys") - max_cred_num = fields.Int(data_key="maxCredNum") - tails_location = fields.Str(data_key="tailsLocation") - tails_hash = fields.Str(data_key="tailsHash") + public_keys = fields.Dict( + data_key="publicKeys", example=INDY_RAW_PUBLIC_KEY_EXAMPLE + ) + max_cred_num = fields.Int(data_key="maxCredNum", example=666) + tails_location = fields.Str( + data_key="tailsLocation", + example="https://tails-server.com/hash/7Qen9RDyemMuV7xGQvp7NjwMSpyHieJyBakycxN7dX7P", + ) + tails_hash = fields.Str( + data_key="tailsHash", example="7Qen9RDyemMuV7xGQvp7NjwMSpyHieJyBakycxN7dX7P" + ) class RevRegDef(BaseModel): @@ -116,13 +131,17 @@ class Meta: issuer_id = fields.Str( description="Issuer Identifier of the credential definition or schema", data_key="issuerId", + example=INDY_OR_KEY_DID_EXAMPLE, ) type = fields.Str(data_key="revocDefType") cred_def_id = fields.Str( description="Credential definition identifier", data_key="credDefId", + example=INDY_CRED_DEF_ID_EXAMPLE, + ) + tag = fields.Str( + description="tag for the revocation registry definition", example="default" ) - tag = fields.Str(description="tag for the revocation registry definition") value = fields.Nested(RevRegDefValueSchema()) @@ -184,7 +203,8 @@ class Meta: ) ) revocation_registry_definition_id = fields.Str( - description="revocation registry definition id" + description="revocation registry definition id", + example=INDY_REV_REG_ID_EXAMPLE, ) revocation_registry_definition = fields.Nested( RevRegDefSchema(), description="revocation registry definition" @@ -362,20 +382,28 @@ class Meta: issuer_id = fields.Str( description="Issuer Identifier of the credential definition or schema", data_key="issuerId", + example=INDY_OR_KEY_DID_EXAMPLE, ) rev_reg_def_id = fields.Str( - description="", + description="The ID of the revocation registry definition", data_key="revRegDefId", + example=INDY_REV_REG_ID_EXAMPLE, ) revocation_list = fields.List( fields.Int(), description="Bit list representing revoked credentials", data_key="revocationList", + example=[0, 1, 1, 0], + ) + current_accumulator = fields.Str( + description="The current accumalator value", + example="21 118...1FB", + data_key="currentAccumulator", ) - current_accumulator = fields.Str(data_key="currentAccumulator") timestamp = fields.Int( description="Timestamp at which revocation list is applicable", required=False, + example=INDY_ISO8601_DATETIME_EXAMPLE, ) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index b70639d5c1..9528eb5ab7 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -2,10 +2,14 @@ from typing import Any, Dict, List, Optional +from anoncreds import Schema from marshmallow import EXCLUDE, fields from marshmallow.validate import OneOf -from anoncreds import Schema +from aries_cloudagent.messaging.valid import ( + INDY_OR_KEY_DID_EXAMPLE, + INDY_SCHEMA_ID_EXAMPLE, +) from ...messaging.models.base import BaseModel, BaseModelSchema @@ -60,6 +64,7 @@ class Meta: issuer_id = fields.Str( description="Issuer Identifier of the credential definition or schema", data_key="issuerId", + example=INDY_OR_KEY_DID_EXAMPLE, ) attr_names = fields.List( fields.Str( @@ -69,10 +74,8 @@ class Meta: description="Schema attribute names", data_key="attrNames", ) - name = fields.Str( - description="Schema name", - ) - version = fields.Str(description="Schema version") + name = fields.Str(description="Schema name", example="Example schema") + version = fields.Str(description="Schema version", example="1.0") class GetSchemaResult(BaseModel): @@ -127,7 +130,9 @@ class Meta: unknown = EXCLUDE schema_value = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") - schema_id = fields.Str(data_key="schemaId", description="Schema identifier") + schema_id = fields.Str( + description="Schema identifier", example=INDY_SCHEMA_ID_EXAMPLE + ) resolution_metadata = fields.Dict() schema_metadata = fields.Dict() @@ -179,7 +184,10 @@ class Meta: ] ) ) - schema_id = fields.Str(description="Schema identifier") + schema_id = fields.Str( + description="Schema identifier", + example=INDY_SCHEMA_ID_EXAMPLE, + ) schema_value = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") diff --git a/aries_cloudagent/anoncreds/registry.py b/aries_cloudagent/anoncreds/registry.py index 9a47203c05..e205bc0150 100644 --- a/aries_cloudagent/anoncreds/registry.py +++ b/aries_cloudagent/anoncreds/registry.py @@ -53,6 +53,10 @@ async def _resolver_for_identifier(self, identifier: str) -> BaseAnonCredsResolv for resolver in self.resolvers if await resolver.supports(identifier) ] + if len(resolvers) == 0: + raise AnonCredsResolutionError( + f"No resolver available for identifier {identifier}" + ) if len(resolvers) > 1: raise AnonCredsResolutionError( f"More than one resolver found for identifier {identifier}" @@ -67,6 +71,11 @@ async def _registrar_for_identifier( for registrar in self.registrars if await registrar.supports(identifier) ] + if len(registrars) == 0: + raise AnonCredsRegistrationError( + f"No registrar available for identifier {identifier}" + ) + if len(registrars) > 1: raise AnonCredsRegistrationError( f"More than one registrar found for identifier {identifier}" diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index fe7aea77b1..2320515e10 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -12,11 +12,19 @@ ) from marshmallow import fields +from aries_cloudagent.ledger.error import LedgerError + from ..admin.request_context import AdminRequestContext from ..askar.profile import AskarProfile from ..core.event_bus import EventBus from ..messaging.models.openapi import OpenAPISchema -from ..messaging.valid import UUIDFour +from ..messaging.valid import ( + INDY_CRED_DEF_ID_EXAMPLE, + INDY_OR_KEY_DID_EXAMPLE, + INDY_REV_REG_ID_EXAMPLE, + INDY_SCHEMA_ID_EXAMPLE, + UUIDFour, +) from ..revocation.error import RevocationError, RevocationNotSupportedError from ..revocation_anoncreds.manager import RevocationManager, RevocationManagerError from ..revocation_anoncreds.routes import ( @@ -27,7 +35,11 @@ TxnOrPublishRevocationsResultSchema, ) from ..storage.error import StorageError, StorageNotFoundError -from .base import AnonCredsObjectNotFound, AnonCredsRegistrationError +from .base import ( + AnonCredsObjectNotFound, + AnonCredsRegistrationError, + AnonCredsResolutionError, +) from .issuer import AnonCredsIssuer, AnonCredsIssuerError from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema from .models.anoncreds_revocation import RevListResultSchema, RevRegDefResultSchema @@ -48,33 +60,43 @@ class SchemaIdMatchInfo(OpenAPISchema): """Path parameters and validators for request taking schema id.""" - schema_id = fields.Str(data_key="schemaId", description="Schema identifier") + schema_id = fields.Str( + description="Schema identifier", + example=INDY_SCHEMA_ID_EXAMPLE, + ) class CredIdMatchInfo(OpenAPISchema): """Path parameters and validators for request taking credential id.""" cred_def_id = fields.Str( - description="Credential identifier", required=True, example=UUIDFour.EXAMPLE + description="Credential identifier", + required=True, + example=INDY_CRED_DEF_ID_EXAMPLE, ) class InnerCredDefSchema(OpenAPISchema): """Parameters and validators for credential definition.""" - tag = fields.Str(description="Credential definition tag") - schemaId = fields.Str(data_key="schemaId", description="Schema identifier") - issuerId = fields.Str( - description="Issuer Identifier of the credential definition or schema", + tag = fields.Str(description="Credential definition tag", example="default") + schema_id = fields.Str( + description="Schema identifier", + data_key="schemaId", + example=INDY_SCHEMA_ID_EXAMPLE, + ) + issuer_id = fields.Str( + data_key="issuerId", + example=INDY_OR_KEY_DID_EXAMPLE, ) class CredDefPostOptionsSchema(OpenAPISchema): """Parameters and validators for credential definition options.""" - endorser_connection_id = fields.Str(required=False) + endorser_connection_id = fields.Str(required=False, example=UUIDFour.EXAMPLE) support_revocation = fields.Bool(required=False) - revocation_registry_size = fields.Int(required=False) + revocation_registry_size = fields.Int(required=False, example=666) class CredDefPostRequestSchema(OpenAPISchema): @@ -89,12 +111,14 @@ class CredDefsQueryStringSchema(OpenAPISchema): issuer_id = fields.Str( description="Issuer Identifier of the credential definition", + example=INDY_OR_KEY_DID_EXAMPLE, ) - schema_id = fields.Str(data_key="schemaId", description="Schema identifier") - schema_name = fields.Str( - description="Schema name", + schema_id = fields.Str( + description="Schema identifier", + example=INDY_SCHEMA_ID_EXAMPLE, ) - schema_version = fields.Str(description="Schema version") + schema_name = fields.Str(description="Schema name", example="Example schema") + schema_version = fields.Str(description="Schema version", example="1.0") class SchemaPostOptionSchema(OpenAPISchema): @@ -114,7 +138,7 @@ class SchemaPostRequestSchema(OpenAPISchema): options = fields.Nested(SchemaPostOptionSchema()) -@docs(tags=["anoncreds"], summary="") +@docs(tags=["anoncreds"], summary="Create a schema on the connected ledger") @request_schema(SchemaPostRequestSchema()) @response_schema(SchemaResultSchema(), 200, description="") async def schemas_post(request: web.BaseRequest): @@ -155,22 +179,28 @@ async def schemas_post(request: web.BaseRequest): context: AdminRequestContext = request["context"] body = await request.json() - options = body.get("option") + options = body.get("options") schema_data = body.get("schema") + if schema_data is None: + raise web.HTTPBadRequest(reason="schema object is required") + issuer_id = schema_data.get("issuerId") attr_names = schema_data.get("attrNames") name = schema_data.get("name") version = schema_data.get("version") issuer = AnonCredsIssuer(context.profile) - result = await issuer.create_and_register_schema( - issuer_id, name, version, attr_names, options=options - ) - return web.json_response(result.serialize()) + try: + result = await issuer.create_and_register_schema( + issuer_id, name, version, attr_names, options=options + ) + return web.json_response(result.serialize()) + except (AnonCredsIssuerError, AnonCredsRegistrationError) as e: + raise web.HTTPBadRequest(reason=e.roll_up) from e -@docs(tags=["anoncreds"], summary="") +@docs(tags=["anoncreds"], summary="Retrieve an individual schemas details") @match_info_schema(SchemaIdMatchInfo()) @response_schema(GetSchemaResultSchema(), 200, description="") async def schema_get(request: web.BaseRequest): @@ -185,24 +215,24 @@ async def schema_get(request: web.BaseRequest): """ context: AdminRequestContext = request["context"] anoncreds_registry = context.inject(AnonCredsRegistry) - schema_id = request.match_info["schemaId"] + schema_id = request.match_info["schema_id"] try: schema = await anoncreds_registry.get_schema(context.profile, schema_id) return web.json_response(schema.serialize()) except AnonCredsObjectNotFound: raise web.HTTPNotFound(reason=f"Schema not found: {schema_id}") + except AnonCredsResolutionError as e: + raise web.HTTPBadRequest(reason=e.roll_up) class SchemasQueryStringSchema(OpenAPISchema): """Parameters and validators for query string in schemas list query.""" - schema_name = fields.Str( - description="Schema name", - example="example-schema", - ) - schema_version = fields.Str(description="Schema version") + schema_name = fields.Str(description="Schema name", example="example-schema") + schema_version = fields.Str(description="Schema version", example="1.0") schema_issuer_id = fields.Str( - description="Issuer Identifier of the credential definition or schema", + description="Issuer identifier of the schema", + example=INDY_OR_KEY_DID_EXAMPLE, ) @@ -210,14 +240,11 @@ class GetSchemasResponseSchema(OpenAPISchema): """Parameters and validators for schema list all response.""" schema_ids = fields.List( - fields.Str( - data_key="schemaIds", - description="Schema identifier", - ) + fields.Str(description="Schema identifier", example=INDY_SCHEMA_ID_EXAMPLE) ) -@docs(tags=["anoncreds"], summary="") +@docs(tags=["anoncreds"], summary="Retrieve all schema ids") @querystring_schema(SchemasQueryStringSchema()) @response_schema(GetSchemasResponseSchema(), 200, description="") async def schemas_get(request: web.BaseRequest): @@ -243,7 +270,9 @@ async def schemas_get(request: web.BaseRequest): return web.json_response({"schema_ids": schema_ids}) -@docs(tags=["anoncreds"], summary="") +@docs( + tags=["anoncreds"], summary="Create a credential definition on the connected ledger" +) @request_schema(CredDefPostRequestSchema()) @response_schema(CredDefResultSchema(), 200, description="") async def cred_def_post(request: web.BaseRequest): @@ -260,22 +289,36 @@ async def cred_def_post(request: web.BaseRequest): body = await request.json() options = body.get("options") cred_def = body.get("credential_definition") + + if cred_def is None: + raise web.HTTPBadRequest(reason="cred_def object is required") + issuer_id = cred_def.get("issuerId") schema_id = cred_def.get("schemaId") tag = cred_def.get("tag") issuer = AnonCredsIssuer(context.profile) - result = await issuer.create_and_register_credential_definition( - issuer_id, - schema_id, - tag, - options=options, - ) - - return web.json_response(result.serialize()) + try: + result = await issuer.create_and_register_credential_definition( + issuer_id, + schema_id, + tag, + options=options, + ) + return web.json_response(result.serialize()) + except ( + AnonCredsObjectNotFound, + AnonCredsResolutionError, + ValueError, + ) as e: + raise web.HTTPBadRequest(reason=e.roll_up) + except AnonCredsIssuerError as e: + raise web.HTTPServerError(reason=e.roll_up) -@docs(tags=["anoncreds"], summary="") +@docs( + tags=["anoncreds"], summary="Retrieve an individual credential definition details" +) @match_info_schema(CredIdMatchInfo()) @response_schema(GetCredDefResultSchema(), 200, description="") async def cred_def_get(request: web.BaseRequest): @@ -291,10 +334,15 @@ async def cred_def_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] anon_creds_registry = context.inject(AnonCredsRegistry) credential_id = request.match_info["cred_def_id"] - result = await anon_creds_registry.get_credential_definition( - context.profile, credential_id - ) - return web.json_response(result.serialize()) + try: + result = await anon_creds_registry.get_credential_definition( + context.profile, credential_id + ) + return web.json_response(result.serialize()) + except AnonCredsObjectNotFound: + raise web.HTTPBadRequest( + reason=f"Credential definition {credential_id} not found" + ) class GetCredDefsResponseSchema(OpenAPISchema): @@ -303,11 +351,12 @@ class GetCredDefsResponseSchema(OpenAPISchema): credential_definition_ids = fields.List( fields.Str( description="credential definition identifiers", + example="GvLGiRogTJubmj5B36qhYz:3:CL:8:faber.agent.degree_schema", ) ) -@docs(tags=["anoncreds"], summary="") +@docs(tags=["anoncreds"], summary="Retrieve all credential definition ids") @querystring_schema(CredDefsQueryStringSchema()) @response_schema(GetCredDefsResponseSchema(), 200, description="") async def cred_defs_get(request: web.BaseRequest): @@ -329,41 +378,70 @@ async def cred_defs_get(request: web.BaseRequest): schema_name=request.query.get("schema_name"), schema_version=request.query.get("schema_version"), ) - return web.json_response(cred_def_ids) + return web.json_response({"credential_definition_ids": cred_def_ids}) -class RevRegCreateRequestSchema(OpenAPISchema): +class InnerRevRegDefSchema(OpenAPISchema): """Request schema for revocation registry creation request.""" issuer_id = fields.Str( description="Issuer Identifier of the credential definition or schema", data_key="issuerId", + example=INDY_OR_KEY_DID_EXAMPLE, ) cred_def_id = fields.Str( description="Credential definition identifier", data_key="credDefId", + example=INDY_SCHEMA_ID_EXAMPLE, + ) + tag = fields.Str(description="tag for revocation registry", example="default") + max_cred_num = fields.Int( + description="Maximum number of credential revocations per registry", + data_key="maxCredNum", + example=666, ) - tag = fields.Str(description="tag for revocation registry") - max_cred_num = fields.Int(data_key="maxCredNum") - registry_type = fields.Str( - description="Revocation registry type", - data_key="type", + + +class RevRegDefOptionsSchema(OpenAPISchema): + """Parameters and validators for rev reg def options.""" + + endorser_connection_id = fields.Str( + description="Connection identifier (optional) (this is an example)", required=False, + example=UUIDFour.EXAMPLE, ) -@docs(tags=["anoncreds"], summary="") +class RevRegCreateRequestSchema(OpenAPISchema): + """Wrapper for revocation registry creation request.""" + + revocation_registry_definition = fields.Nested(InnerRevRegDefSchema()) + options = fields.Nested(RevRegDefOptionsSchema()) + + +@docs( + tags=["anoncreds"], + summary="Create and publish a registration revocation on the connected ledger", +) @request_schema(RevRegCreateRequestSchema()) @response_schema(RevRegDefResultSchema(), 200, description="") async def rev_reg_def_post(request: web.BaseRequest): """Request handler for creating revocation registry definition.""" context: AdminRequestContext = request["context"] body = await request.json() - issuer_id = body.get("issuerId") - cred_def_id = body.get("credDefId") - max_cred_num = body.get("maxCredNum") + + revocation_registry_definition = body.get("revocation_registry_definition") options = body.get("options") + if revocation_registry_definition is None: + raise web.HTTPBadRequest( + reason="revocation_registry_definition object is required" + ) + + issuer_id = revocation_registry_definition.get("issuerId") + cred_def_id = revocation_registry_definition.get("credDefId") + max_cred_num = revocation_registry_definition.get("maxCredNum") + issuer = AnonCredsIssuer(context.profile) revocation = AnonCredsRevocation(context.profile) # check we published this cred def @@ -384,12 +462,9 @@ async def rev_reg_def_post(request: web.BaseRequest): options=options, ) ) - except RevocationNotSupportedError as e: - raise web.HTTPBadRequest(reason=e.message) from e - except AnonCredsRevocationError as e: - raise web.HTTPBadRequest(reason=e.message) from e - - return web.json_response(result.serialize()) + return web.json_response(result.serialize()) + except (RevocationNotSupportedError, AnonCredsRevocationError) as e: + raise web.HTTPBadRequest(reason=e.roll_up) from e class RevListCreateRequestSchema(OpenAPISchema): @@ -397,18 +472,21 @@ class RevListCreateRequestSchema(OpenAPISchema): rev_reg_def_id = fields.Str( description="Revocation registry definition identifier", - data_key="revRegDefId", + example=INDY_REV_REG_ID_EXAMPLE, ) -@docs(tags=["anoncreds"], summary="") +@docs( + tags=["anoncreds"], + summary="Create and publish a revocation status list on the connected ledger", +) @request_schema(RevListCreateRequestSchema()) @response_schema(RevListResultSchema(), 200, description="") async def rev_list_post(request: web.BaseRequest): """Request handler for creating registering a revocation list.""" context: AdminRequestContext = request["context"] body = await request.json() - rev_reg_def_id = body.get("revRegDefId") + rev_reg_def_id = body.get("rev_reg_def_id") options = body.get("options") revocation = AnonCredsRevocation(context.profile) @@ -420,14 +498,12 @@ async def rev_list_post(request: web.BaseRequest): ) ) LOGGER.debug("published revocation list for: %s", rev_reg_def_id) - + return web.json_response(result.serialize()) except StorageNotFoundError as err: raise web.HTTPNotFound(reason=err.roll_up) from err - except AnonCredsRevocationError as err: + except (AnonCredsRevocationError, LedgerError) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err - return web.json_response(result.serialize()) - @docs( tags=["anoncreds"], @@ -454,21 +530,19 @@ async def upload_tails_file(request: web.BaseRequest): raise web.HTTPNotFound(reason="No rev reg def found") await revocation.upload_tails_file(rev_reg_def) - + return web.json_response({}) except AnonCredsIssuerError as e: raise web.HTTPInternalServerError(reason=str(e)) from e - return web.json_response({}) - @docs( tags=["anoncreds"], - summary="Upload local tails file to server", + summary="Update the active registry", ) @match_info_schema(RevRegIdMatchInfoSchema()) @response_schema(RevocationModuleResponseSchema(), description="") async def set_active_registry(request: web.BaseRequest): - """Request handler to upload local tails file for revocation registry. + """Request handler to set the active registry. Args: request: aiohttp request object @@ -479,11 +553,10 @@ async def set_active_registry(request: web.BaseRequest): try: revocation = AnonCredsRevocation(context.profile) await revocation.set_active_registry(rev_reg_id) + return web.json_response({}) except AnonCredsRevocationError as e: raise web.HTTPInternalServerError(reason=str(e)) from e - return web.json_response({}) - @docs( tags=["anoncreds"], @@ -526,6 +599,7 @@ async def revoke(request: web.BaseRequest): else: # no cred_ex_id so we can safely splat the body await rev_manager.revoke_credential(**body) + return web.json_response({}) except ( RevocationManagerError, AnonCredsRevocationError, @@ -535,8 +609,6 @@ async def revoke(request: web.BaseRequest): ) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err - return web.json_response({}) - @docs(tags=["revocation"], summary="Publish pending revocations to ledger") @request_schema(PublishRevocationsSchema()) @@ -561,6 +633,7 @@ async def publish_revocations(request: web.BaseRequest): rev_reg_resp = await rev_manager.publish_pending_revocations( rrid2crid, ) + return web.json_response({"rrid2crid": rev_reg_resp}) except ( RevocationError, StorageError, @@ -569,8 +642,6 @@ async def publish_revocations(request: web.BaseRequest): ) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err - return web.json_response({"rrid2crid": rev_reg_resp}) - async def register(app: web.Application): """Register routes.""" @@ -578,7 +649,7 @@ async def register(app: web.Application): app.add_routes( [ web.post("/anoncreds/schema", schemas_post), - web.get("/anoncreds/schema/{schemaId}", schema_get, allow_head=False), + web.get("/anoncreds/schema/{schema_id}", schema_get, allow_head=False), web.get("/anoncreds/schemas", schemas_get, allow_head=False), web.post("/anoncreds/credential-definition", cred_def_post), web.get( diff --git a/aries_cloudagent/anoncreds/tests/test_routes.py b/aries_cloudagent/anoncreds/tests/test_routes.py index 38059de375..e539a93e73 100644 --- a/aries_cloudagent/anoncreds/tests/test_routes.py +++ b/aries_cloudagent/anoncreds/tests/test_routes.py @@ -86,14 +86,13 @@ async def test_schemas_post(self, mock_create_and_register_schema): assert mock_create_and_register_schema.call_count == 1 - # TODO: consider handling missing schema attribute better - with self.assertRaises(AttributeError): + with self.assertRaises(web.HTTPBadRequest): await test_module.schemas_post(self.request) await test_module.schemas_post(self.request) async def test_get_schema(self): - self.request.match_info = {"schemaId": "schema_id"} + self.request.match_info = {"schema_id": "schema_id"} self.context.inject = mock.Mock( return_value=mock.MagicMock( get_schema=mock.CoroutineMock( @@ -113,7 +112,7 @@ async def test_get_schema(self): await test_module.schema_get(self.request) # schema not found - self.request.match_info = {"schemaId": "schema_id"} + self.request.match_info = {"schema_id": "schema_id"} with self.assertRaises(web.HTTPNotFound): await test_module.schema_get(self.request) @@ -167,8 +166,7 @@ async def test_cred_def_post(self, mock_create_cred_def): assert json.loads(result.body)["credential_definition_id"] == "credDefId" assert mock_create_cred_def.call_count == 1 - # TODO: consider handling missing cred_def attribute better - with self.assertRaises(AttributeError): + with self.assertRaises(web.HTTPBadRequest): await test_module.cred_def_post(self.request) await test_module.cred_def_post(self.request) @@ -202,10 +200,10 @@ async def test_cred_def_get(self): ) async def test_cred_defs_get(self, mock_get_cred_defs): result = await test_module.cred_defs_get(self.request) - assert json.loads(result.body).__len__() == 2 + assert len(json.loads(result.body)["credential_definition_ids"]) == 2 result = await test_module.cred_defs_get(self.request) - assert json.loads(result.body).__len__() == 0 + assert len(json.loads(result.body)["credential_definition_ids"]) == 0 assert mock_get_cred_defs.call_count == 2 @@ -232,6 +230,24 @@ async def test_rev_reg_def_post(self, mock_match, mock_create): } ) + # Must be in wrapper object + with self.assertRaises(web.HTTPBadRequest): + await test_module.rev_reg_def_post(self.request) + + self.request.json = mock.CoroutineMock( + return_value={ + "revocation_registry_definition": { + "credDefId": "cred_def_id", + "issuerId": "issuer_id", + "maxCredNum": 100, + "options": { + "tails_public_uri": "http://tails_public_uri", + "tails_local_uri": "http://tails_local_uri", + }, + } + } + ) + result = await test_module.rev_reg_def_post(self.request) assert ( From 40e6bf1dd8e7d3e0652cb7184260723d62a61a3e Mon Sep 17 00:00:00 2001 From: jamshale Date: Fri, 29 Dec 2023 15:28:26 -0800 Subject: [PATCH 2/3] Update anoncreds get cred defs response in integration tests Signed-off-by: jamshale --- demo/runners/support/agent.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/demo/runners/support/agent.py b/demo/runners/support/agent.py index 2da2f5db62..a114ef00a3 100644 --- a/demo/runners/support/agent.py +++ b/demo/runners/support/agent.py @@ -313,9 +313,7 @@ async def fetch_cred_defs( return cred_defs_saved elif wallet_type == WALLET_TYPE_ANONCREDS: cred_defs_saved = await self.admin_GET("/anoncreds/credential-definitions") - return { - "credential_definition_ids": cred_defs_saved, - } + return cred_defs_saved else: raise Exception("Invalid wallet_type: " + str(wallet_type)) From b2e43efc6f736678e61761bc0702ad21241f0299 Mon Sep 17 00:00:00 2001 From: jamshale Date: Tue, 2 Jan 2024 17:40:27 +0000 Subject: [PATCH 3/3] Remove TypeError catch in base registry Signed-off-by: jamshale --- aries_cloudagent/anoncreds/base.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/aries_cloudagent/anoncreds/base.py b/aries_cloudagent/anoncreds/base.py index e2214f4027..402895357a 100644 --- a/aries_cloudagent/anoncreds/base.py +++ b/aries_cloudagent/anoncreds/base.py @@ -13,14 +13,13 @@ from .models.anoncreds_revocation import ( GetRevListResult, GetRevRegDefResult, - RevRegDef, - RevRegDefResult, RevList, RevListResult, + RevRegDef, + RevRegDefResult, ) from .models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult - T = TypeVar("T") @@ -103,10 +102,7 @@ def supported_identifiers_regex(self) -> Pattern: async def supports(self, identifier: str) -> bool: """Determine whether this registry supports the given identifier.""" - try: - return bool(self.supported_identifiers_regex.match(identifier)) - except TypeError: - return False + return bool(self.supported_identifiers_regex.match(identifier)) @abstractmethod async def setup(self, context: InjectionContext):