Skip to content

Commit

Permalink
Merge pull request #708 from sklump/admin-revocation-intervention
Browse files Browse the repository at this point in the history
Admin revocation intervention
  • Loading branch information
andrewwhitehead authored Sep 4, 2020
2 parents 9efc9bd + 403880d commit 703288d
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 44 deletions.
10 changes: 8 additions & 2 deletions aries_cloudagent/protocols/issue_credential/v1_0/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,10 @@ async def issue_credential(
)

# Make the current registry full
await active_reg.mark_full(self.context)
await active_reg.set_state(
self.context,
IssuerRevRegRecord.STATE_FULL,
)

except IssuerRevocationRegistryFullError:
active_rev_regs = await IssuerRevRegRecord.query_by_cred_def_id(
Expand Down Expand Up @@ -620,7 +623,10 @@ async def issue_credential(
retries=retries - 1,
)
else:
await active_reg.mark_full(self.context)
await active_reg.set_state(
self.context,
IssuerRevRegRecord.STATE_FULL,
)
raise

cred_ex_record.credential = json.loads(credential_json)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,7 @@ async def test_issue_credential(self):
max_creds=1000,
)
),
mark_full=async_mock.CoroutineMock(),
set_state=async_mock.CoroutineMock(),
revoc_reg_id=REV_REG_ID,
save=async_mock.CoroutineMock(),
publish_registry_entry=async_mock.CoroutineMock(),
Expand Down Expand Up @@ -1120,7 +1120,7 @@ async def test_issue_credential_rr_full(self):
)
),
revoc_reg_id=REV_REG_ID,
mark_full=async_mock.CoroutineMock(),
set_state=async_mock.CoroutineMock(),
)
]
)
Expand Down Expand Up @@ -1182,7 +1182,7 @@ async def test_issue_credential_rr_full_rr_staged_retry(self):
)
),
revoc_reg_id=REV_REG_ID,
mark_full=async_mock.CoroutineMock(),
set_state=async_mock.CoroutineMock(),
)
]

Expand All @@ -1196,7 +1196,7 @@ async def test_issue_credential_rr_full_rr_staged_retry(self):
)
),
revoc_reg_id=REV_REG_ID,
mark_full=async_mock.CoroutineMock(),
set_state=async_mock.CoroutineMock(),
)
]

Expand All @@ -1209,7 +1209,7 @@ async def test_issue_credential_rr_full_rr_staged_retry(self):
)
),
revoc_reg_id=REV_REG_ID,
mark_full=async_mock.CoroutineMock(),
set_state=async_mock.CoroutineMock(),
state="published",
save=async_mock.CoroutineMock(),
publish_registry_entry=async_mock.CoroutineMock(),
Expand Down
16 changes: 8 additions & 8 deletions aries_cloudagent/revocation/models/issuer_rev_reg_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class Meta:
STATE_PUBLISHED = "published" # definition published
STATE_STAGED = "staged"
STATE_ACTIVE = "active" # first entry published
STATE_FULL = "full"
STATE_FULL = "full" # includes corrupt, out of sync, unusable

def __init__(
self,
Expand Down Expand Up @@ -328,7 +328,7 @@ async def get_registry(self) -> RevocationRegistry:
async def query_by_cred_def_id(
cls, context: InjectionContext, cred_def_id: str, state: str = None
) -> Sequence["IssuerRevRegRecord"]:
"""Retrieve revocation registry records by credential definition ID.
"""Retrieve issuer revocation registry records by credential definition ID.
Args:
context: The injection context to use
Expand All @@ -344,7 +344,7 @@ async def query_by_cred_def_id(
async def query_by_pending(
cls, context: InjectionContext
) -> Sequence["IssuerRevRegRecord"]:
"""Retrieve revocation records with revocations pending.
"""Retrieve issuer revocation records with revocations pending.
Args:
context: The injection context to use
Expand All @@ -369,18 +369,18 @@ async def retrieve_by_revoc_reg_id(
tag_filter = {"revoc_reg_id": revoc_reg_id}
return await cls.retrieve_by_tag_filter(context, tag_filter)

async def mark_full(self, context: InjectionContext):
"""Change the registry state to full."""
self.state = IssuerRevRegRecord.STATE_FULL
await self.save(context, reason="Marked full")
async def set_state(self, context: InjectionContext, state: str = None):
"""Change the registry state (default full)."""
self.state = state or IssuerRevRegRecord.STATE_FULL
await self.save(context, reason=f"Marked {self.state}")

def __eq__(self, other: Any) -> bool:
"""Comparison between records."""
return super().__eq__(other)


class IssuerRevRegRecordSchema(BaseRecordSchema):
"""Schema to allow serialization/deserialization of revocation registry records."""
"""Schema to allow serialization/deserialization of issuer rev reg records."""

class Meta:
"""IssuerRevRegRecordSchema metadata."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ async def test_generate_registry_etc(self):
)
assert retrieved.revoc_reg_id == rec.revoc_reg_id

await rec.mark_full(self.context)
await rec.set_state(self.context)
assert rec.state == IssuerRevRegRecord.STATE_FULL

data = rec.serialize()
Expand Down
108 changes: 80 additions & 28 deletions aries_cloudagent/revocation/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class RevRegCreateRequestSchema(OpenAPISchema):
)


class RevRegCreateResultSchema(OpenAPISchema):
class RevRegResultSchema(OpenAPISchema):
"""Result schema for revocation registry creation request."""

result = IssuerRevRegRecordSchema()
Expand Down Expand Up @@ -86,6 +86,22 @@ class RevRegsCreatedQueryStringSchema(OpenAPISchema):
)


class SetRevRegStateQueryStringSchema(OpenAPISchema):
"""Query string parameters and validators for request to set rev reg state."""

state = fields.Str(
description="Revocation registry state to set",
required=True,
validate=validate.OneOf(
[
getattr(IssuerRevRegRecord, m)
for m in vars(IssuerRevRegRecord)
if m.startswith("STATE_")
]
),
)


class RevRegIdMatchInfoSchema(OpenAPISchema):
"""Path parameters and validators for request taking rev reg id."""

Expand All @@ -108,7 +124,7 @@ class CredDefIdMatchInfoSchema(OpenAPISchema):

@docs(tags=["revocation"], summary="Creates a new revocation registry")
@request_schema(RevRegCreateRequestSchema())
@response_schema(RevRegCreateResultSchema(), 200)
@response_schema(RevRegResultSchema(), 200)
async def revocation_create_registry(request: web.BaseRequest):
"""
Request handler to create a new revocation registry.
Expand Down Expand Up @@ -142,16 +158,16 @@ async def revocation_create_registry(request: web.BaseRequest):
try:
issuer_did = credential_definition_id.split(":")[0]
revoc = IndyRevocation(context)
registry_record = await revoc.init_issuer_registry(
issuer_rev_reg_rec = await revoc.init_issuer_registry(
credential_definition_id,
issuer_did,
max_cred_num=max_cred_num,
)
except RevocationNotSupportedError as e:
raise web.HTTPBadRequest(reason=e.message) from e
await shield(registry_record.generate_registry(context))
await shield(issuer_rev_reg_rec.generate_registry(context))

return web.json_response({"result": registry_record.serialize()})
return web.json_response({"result": issuer_rev_reg_rec.serialize()})


@docs(
Expand Down Expand Up @@ -189,7 +205,7 @@ async def revocation_registries_created(request: web.BaseRequest):
summary="Get revocation registry by revocation registry id",
)
@match_info_schema(RevRegIdMatchInfoSchema())
@response_schema(RevRegCreateResultSchema(), 200)
@response_schema(RevRegResultSchema(), 200)
async def get_registry(request: web.BaseRequest):
"""
Request handler to get a revocation registry by identifier.
Expand All @@ -203,23 +219,23 @@ async def get_registry(request: web.BaseRequest):
"""
context = request.app["request_context"]

registry_id = request.match_info["rev_reg_id"]
rev_reg_id = request.match_info["rev_reg_id"]

try:
revoc = IndyRevocation(context)
revoc_registry = await revoc.get_issuer_rev_reg_record(registry_id)
rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id)
except StorageNotFoundError as err:
raise web.HTTPNotFound(reason=err.roll_up) from err

return web.json_response({"result": revoc_registry.serialize()})
return web.json_response({"result": rev_reg.serialize()})


@docs(
tags=["revocation"],
summary="Get an active revocation registry by credential definition id",
)
@match_info_schema(CredDefIdMatchInfoSchema())
@response_schema(RevRegCreateResultSchema(), 200)
@response_schema(RevRegResultSchema(), 200)
async def get_active_registry(request: web.BaseRequest):
"""
Request handler to get an active revocation registry by cred def id.
Expand All @@ -237,11 +253,11 @@ async def get_active_registry(request: web.BaseRequest):

try:
revoc = IndyRevocation(context)
revoc_registry = await revoc.get_active_issuer_rev_reg_record(cred_def_id)
rev_reg = await revoc.get_active_issuer_rev_reg_record(cred_def_id)
except StorageNotFoundError as err:
raise web.HTTPNotFound(reason=err.roll_up) from err

return web.json_response({"result": revoc_registry.serialize()})
return web.json_response({"result": rev_reg.serialize()})


@docs(
Expand All @@ -264,23 +280,23 @@ async def get_tails_file(request: web.BaseRequest) -> web.FileResponse:
"""
context = request.app["request_context"]

registry_id = request.match_info["rev_reg_id"]
rev_reg_id = request.match_info["rev_reg_id"]

try:
revoc = IndyRevocation(context)
revoc_registry = await revoc.get_issuer_rev_reg_record(registry_id)
rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id)
except StorageNotFoundError as err:
raise web.HTTPNotFound(reason=err.roll_up) from err

return web.FileResponse(path=revoc_registry.tails_local_path, status=200)
return web.FileResponse(path=rev_reg.tails_local_path, status=200)


@docs(
tags=["revocation"],
summary="Publish a given revocation registry",
)
@match_info_schema(RevRegIdMatchInfoSchema())
@response_schema(RevRegCreateResultSchema(), 200)
@response_schema(RevRegResultSchema(), 200)
async def publish_registry(request: web.BaseRequest):
"""
Request handler to publish a revocation registry based on the registry id.
Expand All @@ -293,23 +309,23 @@ async def publish_registry(request: web.BaseRequest):
"""
context = request.app["request_context"]
registry_id = request.match_info["rev_reg_id"]
rev_reg_id = request.match_info["rev_reg_id"]

try:
revoc = IndyRevocation(context)
revoc_registry = await revoc.get_issuer_rev_reg_record(registry_id)
rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id)

await revoc_registry.publish_registry_definition(context)
LOGGER.debug("published registry definition: %s", registry_id)
await rev_reg.publish_registry_definition(context)
LOGGER.debug("published registry definition: %s", rev_reg_id)

await revoc_registry.publish_registry_entry(context)
LOGGER.debug("published registry entry: %s", registry_id)
await rev_reg.publish_registry_entry(context)
LOGGER.debug("published registry entry: %s", rev_reg_id)
except StorageNotFoundError as err:
raise web.HTTPNotFound(reason=err.roll_up) from err
except RevocationError as err:
raise web.HTTPBadRequest(reason=err.roll_up) from err

return web.json_response({"result": revoc_registry.serialize()})
return web.json_response({"result": rev_reg.serialize()})


@docs(
Expand All @@ -318,7 +334,7 @@ async def publish_registry(request: web.BaseRequest):
)
@match_info_schema(RevRegIdMatchInfoSchema())
@request_schema(RevRegUpdateTailsFileUriSchema())
@response_schema(RevRegCreateResultSchema(), 200)
@response_schema(RevRegResultSchema(), 200)
async def update_registry(request: web.BaseRequest):
"""
Request handler to update a revocation registry based on the registry id.
Expand All @@ -335,18 +351,50 @@ async def update_registry(request: web.BaseRequest):
body = await request.json()
tails_public_uri = body.get("tails_public_uri")

registry_id = request.match_info["rev_reg_id"]
rev_reg_id = request.match_info["rev_reg_id"]

try:
revoc = IndyRevocation(context)
revoc_registry = await revoc.get_issuer_rev_reg_record(registry_id)
await revoc_registry.set_tails_file_public_uri(context, tails_public_uri)
rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id)
await rev_reg.set_tails_file_public_uri(context, tails_public_uri)
except StorageNotFoundError as err:
raise web.HTTPNotFound(reason=err.roll_up) from err
except RevocationError as err:
raise web.HTTPBadRequest(reason=err.roll_up) from err

return web.json_response({"result": revoc_registry.serialize()})
return web.json_response({"result": rev_reg.serialize()})


@docs(tags=["revocation"], summary="Set revocation registry state manually.")
@match_info_schema(RevRegIdMatchInfoSchema())
@querystring_schema(SetRevRegStateQueryStringSchema())
@response_schema(RevRegResultSchema(), 200)
async def set_registry_state(request: web.BaseRequest):
"""
Request handler to set a revocation registry state manually.
Args:
request: aiohttp request object
Returns:
The revocation registry record, updated
"""
context = request.app["request_context"]
rev_reg_id = request.match_info["rev_reg_id"]
state = request.query.get("state")

try:
revoc = IndyRevocation(context)
rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id)
await rev_reg.set_state(context, state)

LOGGER.debug("set registry %s state: %s", rev_reg_id, state)

except StorageNotFoundError as err:
raise web.HTTPNotFound(reason=err.roll_up) from err

return web.json_response({"result": rev_reg.serialize()})


async def register(app: web.Application):
Expand Down Expand Up @@ -374,6 +422,10 @@ async def register(app: web.Application):
),
web.patch("/revocation/registry/{rev_reg_id}", update_registry),
web.post("/revocation/registry/{rev_reg_id}/publish", publish_registry),
web.patch(
"/revocation/registry/{rev_reg_id}/set-state",
set_registry_state,
),
]
)

Expand Down
Loading

0 comments on commit 703288d

Please sign in to comment.