Skip to content

Commit 1f5e4ae

Browse files
authored
Merge branch 'main' into fix/respect-auto-verify-pres-v2
2 parents 95701d7 + 49084ec commit 1f5e4ae

9 files changed

Lines changed: 350 additions & 34 deletions

File tree

aries_cloudagent/config/argparse.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1309,6 +1309,12 @@ def add_arguments(self, parser: ArgumentParser):
13091309
env_var="ACAPY_MAX_MESSAGE_SIZE",
13101310
help="Set the maximum size in bytes for inbound agent messages.",
13111311
)
1312+
parser.add_argument(
1313+
"--light-weight-webhook",
1314+
action="store_true",
1315+
env_var="ACAPY_LIGHT_WEIGHT_WEBHOOK",
1316+
help="omitted client's info from issue-credential related webhook",
1317+
)
13121318
parser.add_argument(
13131319
"--enable-undelivered-queue",
13141320
action="store_true",
@@ -1372,6 +1378,8 @@ def get_settings(self, args: Namespace):
13721378
settings["image_url"] = args.image_url
13731379
if args.max_message_size:
13741380
settings["transport.max_message_size"] = args.max_message_size
1381+
if args.light_weight_webhook:
1382+
settings["transport.light_weight_webhook"] = True
13751383
if args.max_outbound_retry:
13761384
settings["transport.max_outbound_retry"] = args.max_outbound_retry
13771385
if args.ws_heartbeat_interval:
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""v1.0 credential exchange light weight webhook."""
2+
3+
4+
class LightWeightV10CredentialExchangeWebhook:
5+
"""Class representing a state only credential exchange webhook."""
6+
7+
__acceptable_keys_list = [
8+
"connection_id",
9+
"credential_exchange_id",
10+
"cred_ex_id",
11+
"cred_def_id",
12+
"role",
13+
"initiator",
14+
"revoc_reg_id",
15+
"revocation_id",
16+
"auto_offer",
17+
"auto_issue",
18+
"auto_remove",
19+
"error_msg",
20+
"thread_id",
21+
"parent_thread_id",
22+
"state",
23+
"credential_definition_id",
24+
"schema_id",
25+
"credential_id",
26+
"trace",
27+
"public_did",
28+
"cred_id_stored",
29+
"conn_id",
30+
]
31+
32+
def __init__(
33+
self,
34+
**kwargs,
35+
):
36+
"""
37+
Initialize webhook object from V10CredentialExchange.
38+
39+
from a list of accepted attributes.
40+
"""
41+
[
42+
self.__setattr__(key, kwargs.get(key))
43+
for key in self.__acceptable_keys_list
44+
if kwargs.get(key) is not None
45+
]
46+
if kwargs.get("_id") is not None:
47+
self.credential_exchange_id = kwargs.get("_id")

aries_cloudagent/protocols/issue_credential/v1_0/models/credential_exchange.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
from ..messages.credential_proposal import CredentialProposal, CredentialProposalSchema
1919
from ..messages.credential_offer import CredentialOffer, CredentialOfferSchema
20+
from ..messages.credential_exchange_webhook import (
21+
LightWeightV10CredentialExchangeWebhook,
22+
)
2023

2124
from . import UNENCRYPTED_TAGS
2225

@@ -221,6 +224,33 @@ async def save_error_state(
221224
except StorageError:
222225
LOGGER.exception("Error saving credential exchange error state")
223226

227+
# Override
228+
async def emit_event(self, session: ProfileSession, payload: Any = None):
229+
"""
230+
Emit an event.
231+
232+
Args:
233+
session: The profile session to use
234+
payload: The event payload
235+
"""
236+
237+
if not self.RECORD_TOPIC:
238+
return
239+
240+
if self.state:
241+
topic = f"{self.EVENT_NAMESPACE}::{self.RECORD_TOPIC}::{self.state}"
242+
else:
243+
topic = f"{self.EVENT_NAMESPACE}::{self.RECORD_TOPIC}"
244+
245+
if not payload:
246+
payload = self.serialize()
247+
248+
if session.profile.settings.get("transport.light_weight_webhook"):
249+
payload = LightWeightV10CredentialExchangeWebhook(**self.__dict__)
250+
payload = payload.__dict__
251+
252+
await session.profile.notify(topic, payload)
253+
224254
@property
225255
def record_value(self) -> dict:
226256
"""Accessor for the JSON record value generated for this invitation."""
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""v2.0 credential exchange light weight webhook."""
2+
3+
4+
class LightWeightV20CredExRecordWebhook:
5+
"""Class representing a state only credential exchange webhook."""
6+
7+
__acceptable_keys_list = [
8+
"connection_id",
9+
"credential_exchange_id",
10+
"cred_ex_id",
11+
"cred_def_id",
12+
"role",
13+
"initiator",
14+
"revoc_reg_id",
15+
"revocation_id",
16+
"auto_offer",
17+
"auto_issue",
18+
"auto_remove",
19+
"error_msg",
20+
"thread_id",
21+
"parent_thread_id",
22+
"state",
23+
"credential_definition_id",
24+
"schema_id",
25+
"credential_id",
26+
"trace",
27+
"public_did",
28+
"cred_id_stored",
29+
"conn_id",
30+
]
31+
32+
def __init__(
33+
self,
34+
**kwargs,
35+
):
36+
"""
37+
Initialize webhook object from V20CredExRecord.
38+
39+
from a list of accepted attributes.
40+
"""
41+
[
42+
self.__setattr__(key, kwargs.get(key))
43+
for key in self.__acceptable_keys_list
44+
if kwargs.get(key) is not None
45+
]
46+
if kwargs.get("_id") is not None:
47+
self.cred_ex_id = kwargs.get("_id")

aries_cloudagent/protocols/issue_credential/v2_0/models/cred_ex_record.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from ..messages.cred_offer import V20CredOffer, V20CredOfferSchema
1818
from ..messages.cred_request import V20CredRequest, V20CredRequestSchema
1919
from ..messages.inner.cred_preview import V20CredPreviewSchema
20+
from ..messages.cred_ex_record_webhook import LightWeightV20CredExRecordWebhook
2021

2122
from . import UNENCRYPTED_TAGS
2223

@@ -181,6 +182,33 @@ async def save_error_state(
181182
except StorageError as err:
182183
LOGGER.exception(err)
183184

185+
# Override
186+
async def emit_event(self, session: ProfileSession, payload: Any = None):
187+
"""
188+
Emit an event.
189+
190+
Args:
191+
session: The profile session to use
192+
payload: The event payload
193+
"""
194+
195+
if not self.RECORD_TOPIC:
196+
return
197+
198+
if self.state:
199+
topic = f"{self.EVENT_NAMESPACE}::{self.RECORD_TOPIC}::{self.state}"
200+
else:
201+
topic = f"{self.EVENT_NAMESPACE}::{self.RECORD_TOPIC}"
202+
203+
if not payload:
204+
payload = self.serialize()
205+
206+
if session.profile.settings.get("transport.light_weight_webhook"):
207+
payload = LightWeightV20CredExRecordWebhook(**self.__dict__)
208+
payload = payload.__dict__
209+
210+
await session.profile.notify(topic, payload)
211+
184212
@property
185213
def record_value(self) -> Mapping:
186214
"""Accessor for the JSON record value generated for this credential exchange."""

aries_cloudagent/revocation/routes.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import json
44
import logging
5+
import os
6+
import shutil
57
from asyncio import shield
68
import re
79

@@ -146,6 +148,31 @@ def validate_fields(self, data, **kwargs):
146148
)
147149

148150

151+
class RevRegId(OpenAPISchema):
152+
"""Parameters and validators for delete tails file request."""
153+
154+
@validates_schema
155+
def validate_fields(self, data, **kwargs):
156+
"""Validate schema fields - must have either rr-id or cr-id."""
157+
158+
rev_reg_id = data.get("rev_reg_id")
159+
cred_def_id = data.get("cred_def_id")
160+
161+
if not (rev_reg_id or cred_def_id):
162+
raise ValidationError("Request must have either rev_reg_id or cred_def_id")
163+
164+
rev_reg_id = fields.Str(
165+
description="Revocation registry identifier",
166+
required=False,
167+
**INDY_REV_REG_ID,
168+
)
169+
cred_def_id = fields.Str(
170+
description="Credential definition identifier",
171+
required=False,
172+
**INDY_CRED_DEF_ID,
173+
)
174+
175+
149176
class RevokeRequestSchema(CredRevRecordQueryStringSchema):
150177
"""Parameters and validators for revocation request."""
151178

@@ -1467,6 +1494,51 @@ async def on_revocation_registry_endorsed_event(profile: Profile, event: Event):
14671494
)
14681495

14691496

1497+
@querystring_schema(RevRegId())
1498+
@docs(tags=["revocation"], summary="Delete the tail files")
1499+
async def delete_tails(request: web.BaseRequest) -> json:
1500+
"""Delete Tails Files."""
1501+
context: AdminRequestContext = request["context"]
1502+
rev_reg_id = request.query.get("rev_reg_id")
1503+
cred_def_id = request.query.get("cred_def_id")
1504+
revoc = IndyRevocation(context.profile)
1505+
session = revoc._profile.session()
1506+
if rev_reg_id:
1507+
rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id)
1508+
tails_path = rev_reg.tails_local_path
1509+
main_dir_rev = os.path.dirname(tails_path)
1510+
try:
1511+
shutil.rmtree(main_dir_rev)
1512+
return web.json_response({"message": "All files deleted successfully"})
1513+
except Exception as e:
1514+
return web.json_response({"message": str(e)})
1515+
elif cred_def_id:
1516+
async with session:
1517+
cred_reg = sorted(
1518+
await IssuerRevRegRecord.query_by_cred_def_id(
1519+
session, cred_def_id, IssuerRevRegRecord.STATE_GENERATED
1520+
)
1521+
)[0]
1522+
tails_path = cred_reg.tails_local_path
1523+
main_dir_rev = os.path.dirname(tails_path)
1524+
main_dir_cred = os.path.dirname(main_dir_rev)
1525+
filenames = os.listdir(main_dir_cred)
1526+
try:
1527+
flag = 0
1528+
for i in filenames:
1529+
safe_cred_def_id = re.escape(cred_def_id)
1530+
if re.search(safe_cred_def_id, i):
1531+
shutil.rmtree(main_dir_cred + "/" + i)
1532+
flag = 1
1533+
if flag:
1534+
return web.json_response({"message": "All files deleted successfully"})
1535+
else:
1536+
return web.json_response({"message": "No such file or directory"})
1537+
1538+
except Exception as e:
1539+
return web.json_response({"message": str(e)})
1540+
1541+
14701542
async def register(app: web.Application):
14711543
"""Register routes."""
14721544
app.add_routes(
@@ -1524,6 +1596,7 @@ async def register(app: web.Application):
15241596
"/revocation/registry/{rev_reg_id}/fix-revocation-entry-state",
15251597
update_rev_reg_revoked_state,
15261598
),
1599+
web.delete("/revocation/registry/delete-tails-file", delete_tails),
15271600
]
15281601
)
15291602

aries_cloudagent/revocation/tests/test_routes.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import os
2+
import shutil
3+
import unittest
4+
15
from aiohttp.web import HTTPBadRequest, HTTPNotFound
26
from asynctest import TestCase as AsyncTestCase
37
from asynctest import mock as async_mock
@@ -903,3 +907,63 @@ async def test_post_process_routes(self):
903907
]["get"]["responses"]["200"]["schema"] == {"type": "string", "format": "binary"}
904908

905909
assert "tags" in mock_app._state["swagger_dict"]
910+
911+
912+
class TestDeleteTails(unittest.TestCase):
913+
def setUp(self):
914+
self.rev_reg_id = "rev_reg_id_123"
915+
self.cred_def_id = "cred_def_id_456"
916+
917+
self.main_dir_rev = "path/to/main/dir/rev"
918+
self.tails_path = os.path.join(self.main_dir_rev, "tails")
919+
if not (os.path.exists(self.main_dir_rev)):
920+
os.makedirs(self.main_dir_rev)
921+
open(self.tails_path, "w").close()
922+
923+
async def test_delete_tails_by_rev_reg_id(self):
924+
# Setup
925+
rev_reg_id = self.rev_reg_id
926+
927+
# Test
928+
result = await test_module.delete_tails(
929+
{"context": None, "query": {"rev_reg_id": rev_reg_id}}
930+
)
931+
932+
# Assert
933+
self.assertEqual(result, {"message": "All files deleted successfully"})
934+
self.assertFalse(os.path.exists(self.tails_path))
935+
936+
async def test_delete_tails_by_cred_def_id(self):
937+
# Setup
938+
cred_def_id = self.cred_def_id
939+
main_dir_cred = "path/to/main/dir/cred"
940+
os.makedirs(main_dir_cred)
941+
cred_dir = os.path.join(main_dir_cred, cred_def_id)
942+
os.makedirs(cred_dir)
943+
944+
# Test
945+
result = await test_module.delete_tails(
946+
{"context": None, "query": {"cred_def_id": cred_def_id}}
947+
)
948+
949+
# Assert
950+
self.assertEqual(result, {"message": "All files deleted successfully"})
951+
self.assertFalse(os.path.exists(cred_dir))
952+
self.assertTrue(os.path.exists(main_dir_cred))
953+
954+
async def test_delete_tails_not_found(self):
955+
# Setup
956+
cred_def_id = "invalid_cred_def_id"
957+
958+
# Test
959+
result = await test_module.delete_tails(
960+
{"context": None, "query": {"cred_def_id": cred_def_id}}
961+
)
962+
963+
# Assert
964+
self.assertEqual(result, {"message": "No such file or directory"})
965+
self.assertTrue(os.path.exists(self.main_dir_rev))
966+
967+
async def tearDown(self):
968+
if os.path.exists(self.main_dir_rev):
969+
shutil.rmtree(self.main_dir_rev)

0 commit comments

Comments
 (0)