Skip to content

Commit 1803197

Browse files
authored
Merge branch 'main' into issue#1809
2 parents 42adb68 + cdea418 commit 1803197

7 files changed

Lines changed: 295 additions & 63 deletions

File tree

aries_cloudagent/indy/sdk/issuer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ async def revoke_credentials(
283283
tails_reader_handle = await create_tails_reader(tails_file_path)
284284

285285
result_json = None
286-
for cred_rev_id in cred_rev_ids:
286+
for cred_rev_id in set(cred_rev_ids):
287287
with IndyErrorHandler(
288288
"Exception when revoking credential", IndyIssuerError
289289
):

aries_cloudagent/indy/sdk/tests/test_issuer.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -311,17 +311,20 @@ async def test_create_revoke_credentials_x(
311311
values = json.loads(call_values)
312312
assert "attr1" in values
313313

314-
mock_indy_revoke_credential.side_effect = [
315-
json.dumps(TEST_RR_DELTA),
316-
IndyError(
317-
error_code=ErrorCode.AnoncredsInvalidUserRevocId,
318-
error_details={"message": "already revoked"},
319-
),
320-
IndyError(
314+
def mock_revoke(_h, _t, _r, cred_rev_id):
315+
if cred_rev_id == "42":
316+
return json.dumps(TEST_RR_DELTA)
317+
if cred_rev_id == "54":
318+
raise IndyError(
319+
error_code=ErrorCode.AnoncredsInvalidUserRevocId,
320+
error_details={"message": "already revoked"},
321+
)
322+
raise IndyError(
321323
error_code=ErrorCode.UnknownCryptoTypeError,
322324
error_details={"message": "truly an outlier"},
323-
),
324-
]
325+
)
326+
327+
mock_indy_revoke_credential.side_effect = mock_revoke
325328
mock_indy_merge_rr_deltas.return_value = json.dumps(TEST_RR_DELTA)
326329
(result, failed) = await self.issuer.revoke_credentials(
327330
REV_REG_ID, tails_file_path="dummy", cred_rev_ids=test_cred_rev_ids

aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,10 @@ def _check_proof_vs_proposal():
199199
f"attr::{name}::value": proof_value,
200200
}
201201

202-
if not any(r.items() <= criteria.items() for r in req_restrictions):
202+
if (
203+
not any(r.items() <= criteria.items() for r in req_restrictions)
204+
and len(req_restrictions) != 0
205+
):
203206
raise V20PresFormatHandlerError(
204207
f"Presented attribute {reft} does not satisfy proof request "
205208
f"restrictions {req_restrictions}"
@@ -234,7 +237,10 @@ def _check_proof_vs_proposal():
234237
},
235238
}
236239

237-
if not any(r.items() <= criteria.items() for r in req_restrictions):
240+
if (
241+
not any(r.items() <= criteria.items() for r in req_restrictions)
242+
and len(req_restrictions) != 0
243+
):
238244
raise V20PresFormatHandlerError(
239245
f"Presented attr group {reft} does not satisfy proof request "
240246
f"restrictions {req_restrictions}"
@@ -287,7 +293,10 @@ def _check_proof_vs_proposal():
287293
"issuer_did": cred_def_id.split(":")[-5],
288294
}
289295

290-
if not any(r.items() <= criteria.items() for r in req_restrictions):
296+
if (
297+
not any(r.items() <= criteria.items() for r in req_restrictions)
298+
and len(req_restrictions) != 0
299+
):
291300
raise V20PresFormatHandlerError(
292301
f"Presented predicate {reft} does not satisfy proof request "
293302
f"restrictions {req_restrictions}"

aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,6 +1400,164 @@ async def test_receive_pres_receive_pred_value_mismatch_punt_to_indy(self):
14001400
save_ex.assert_called_once()
14011401
assert px_rec_out.state == (V20PresExRecord.STATE_PRESENTATION_RECEIVED)
14021402

1403+
async def test_receive_pres_indy_no_predicate_restrictions(self):
1404+
connection_record = async_mock.MagicMock(connection_id=CONN_ID)
1405+
indy_proof_req = {
1406+
"name": PROOF_REQ_NAME,
1407+
"version": PROOF_REQ_VERSION,
1408+
"nonce": PROOF_REQ_NONCE,
1409+
"requested_attributes": {
1410+
"0_player_uuid": {
1411+
"name": "player",
1412+
"restrictions": [{"cred_def_id": CD_ID}],
1413+
"non_revoked": {"from": NOW, "to": NOW},
1414+
},
1415+
"0_screencapture_uuid": {
1416+
"name": "screenCapture",
1417+
"restrictions": [{"cred_def_id": CD_ID}],
1418+
"non_revoked": {"from": NOW, "to": NOW},
1419+
},
1420+
},
1421+
"requested_predicates": {
1422+
"0_highscore_GE_uuid": {
1423+
"name": "highScore",
1424+
"p_type": ">=",
1425+
"p_value": 1000000,
1426+
"restrictions": [],
1427+
"non_revoked": {"from": NOW, "to": NOW},
1428+
}
1429+
},
1430+
}
1431+
pres_request = V20PresRequest(
1432+
formats=[
1433+
V20PresFormat(
1434+
attach_id="indy",
1435+
format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][
1436+
V20PresFormat.Format.INDY.api
1437+
],
1438+
)
1439+
],
1440+
request_presentations_attach=[
1441+
AttachDecorator.data_base64(indy_proof_req, ident="indy")
1442+
],
1443+
)
1444+
pres = V20Pres(
1445+
formats=[
1446+
V20PresFormat(
1447+
attach_id="indy",
1448+
format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api],
1449+
)
1450+
],
1451+
presentations_attach=[
1452+
AttachDecorator.data_base64(INDY_PROOF, ident="indy")
1453+
],
1454+
)
1455+
pres.assign_thread_id("thread-id")
1456+
1457+
px_rec_dummy = V20PresExRecord(
1458+
pres_request=pres_request.serialize(),
1459+
)
1460+
1461+
# cover by_format property
1462+
by_format = px_rec_dummy.by_format
1463+
1464+
assert by_format.get("pres_request").get("indy") == indy_proof_req
1465+
1466+
with async_mock.patch.object(
1467+
V20PresExRecord, "save", autospec=True
1468+
) as save_ex, async_mock.patch.object(
1469+
V20PresExRecord, "retrieve_by_tag_filter", autospec=True
1470+
) as retrieve_ex, async_mock.patch.object(
1471+
self.profile,
1472+
"session",
1473+
async_mock.MagicMock(return_value=self.profile.session()),
1474+
) as session:
1475+
retrieve_ex.side_effect = [px_rec_dummy]
1476+
px_rec_out = await self.manager.receive_pres(pres, connection_record, None)
1477+
retrieve_ex.assert_called_once_with(
1478+
session.return_value,
1479+
{"thread_id": "thread-id"},
1480+
{"role": V20PresExRecord.ROLE_VERIFIER, "connection_id": CONN_ID},
1481+
)
1482+
save_ex.assert_called_once()
1483+
assert px_rec_out.state == (V20PresExRecord.STATE_PRESENTATION_RECEIVED)
1484+
1485+
async def test_receive_pres_indy_no_attr_restrictions(self):
1486+
connection_record = async_mock.MagicMock(connection_id=CONN_ID)
1487+
indy_proof_req = {
1488+
"name": PROOF_REQ_NAME,
1489+
"version": PROOF_REQ_VERSION,
1490+
"nonce": PROOF_REQ_NONCE,
1491+
"requested_attributes": {
1492+
"0_player_uuid": {
1493+
"name": "player",
1494+
"restrictions": [],
1495+
"non_revoked": {"from": NOW, "to": NOW},
1496+
}
1497+
},
1498+
"requested_predicates": {},
1499+
}
1500+
proof = deepcopy(INDY_PROOF)
1501+
proof["requested_proof"]["revealed_attrs"] = {
1502+
"0_player_uuid": {
1503+
"sub_proof_index": 0,
1504+
"raw": "Richie Knucklez",
1505+
"encoded": "516439982",
1506+
}
1507+
}
1508+
proof["requested_proof"]["predicates"] = {}
1509+
pres_request = V20PresRequest(
1510+
formats=[
1511+
V20PresFormat(
1512+
attach_id="indy",
1513+
format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][
1514+
V20PresFormat.Format.INDY.api
1515+
],
1516+
)
1517+
],
1518+
request_presentations_attach=[
1519+
AttachDecorator.data_base64(indy_proof_req, ident="indy")
1520+
],
1521+
)
1522+
pres = V20Pres(
1523+
formats=[
1524+
V20PresFormat(
1525+
attach_id="indy",
1526+
format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api],
1527+
)
1528+
],
1529+
presentations_attach=[AttachDecorator.data_base64(proof, ident="indy")],
1530+
)
1531+
pres.assign_thread_id("thread-id")
1532+
1533+
px_rec_dummy = V20PresExRecord(
1534+
pres_request=pres_request.serialize(),
1535+
)
1536+
1537+
# cover by_format property
1538+
by_format = px_rec_dummy.by_format
1539+
1540+
assert by_format.get("pres_request").get("indy") == indy_proof_req
1541+
1542+
with async_mock.patch.object(
1543+
V20PresExRecord, "save", autospec=True
1544+
) as save_ex, async_mock.patch.object(
1545+
V20PresExRecord, "retrieve_by_tag_filter", autospec=True
1546+
) as retrieve_ex, async_mock.patch.object(
1547+
self.profile,
1548+
"session",
1549+
async_mock.MagicMock(return_value=self.profile.session()),
1550+
) as session:
1551+
retrieve_ex.side_effect = [px_rec_dummy]
1552+
px_rec_out = await self.manager.receive_pres(pres, connection_record, None)
1553+
retrieve_ex.assert_called_once_with(
1554+
session.return_value,
1555+
{"thread_id": "thread-id"},
1556+
{"role": V20PresExRecord.ROLE_VERIFIER, "connection_id": CONN_ID},
1557+
)
1558+
save_ex.assert_called_once()
1559+
assert px_rec_out.state == (V20PresExRecord.STATE_PRESENTATION_RECEIVED)
1560+
14031561
async def test_receive_pres_bait_and_switch_attr_name(self):
14041562
connection_record = async_mock.MagicMock(connection_id=CONN_ID)
14051563
indy_proof_req = deepcopy(INDY_PROOF_REQ_NAME)

aries_cloudagent/revocation/manager.py

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ async def revoke_credential(
133133
rev_reg = await revoc.get_ledger_registry(rev_reg_id)
134134
await rev_reg.get_or_fetch_local_tails_path()
135135
# pick up pending revocations on input revocation registry
136-
crids = list(set(issuer_rr_rec.pending_pub + [cred_rev_id]))
136+
crids = (issuer_rr_rec.pending_pub or []) + [cred_rev_id]
137137
(delta_json, _) = await issuer.revoke_credentials(
138138
issuer_rr_rec.revoc_reg_id, issuer_rr_rec.tails_local_path, crids
139139
)
@@ -145,9 +145,9 @@ async def revoke_credential(
145145
issuer_rr_upd.revoc_reg_entry = json.loads(delta_json)
146146
await issuer_rr_upd.clear_pending(txn, crids)
147147
await txn.commit()
148+
await self.set_cred_revoked_state(rev_reg_id, crids)
148149
if delta_json:
149150
await issuer_rr_upd.send_entry(self._profile)
150-
await self.set_cred_revoked_state(rev_reg_id, [cred_rev_id])
151151
await notify_revocation_published_event(
152152
self._profile, rev_reg_id, [cred_rev_id]
153153
)
@@ -215,11 +215,11 @@ async def publish_pending_revocations(
215215
issuer_rr_upd.revoc_reg_entry = json.loads(delta_json)
216216
await issuer_rr_upd.clear_pending(txn, crids)
217217
await txn.commit()
218+
await self.set_cred_revoked_state(issuer_rr_rec.revoc_reg_id, crids)
218219
if delta_json:
219220
await issuer_rr_upd.send_entry(self._profile)
220221
published = sorted(crid for crid in crids if crid not in failed_crids)
221222
result[issuer_rr_rec.revoc_reg_id] = published
222-
await self.set_cred_revoked_state(issuer_rr_rec.revoc_reg_id, crids)
223223
await notify_revocation_published_event(
224224
self._profile, issuer_rr_rec.revoc_reg_id, crids
225225
)
@@ -289,34 +289,40 @@ async def set_cred_revoked_state(
289289
290290
"""
291291
for cred_rev_id in cred_rev_ids:
292+
cred_ex_id = None
293+
294+
try:
295+
async with self._profile.transaction() as txn:
296+
rev_rec = await IssuerCredRevRecord.retrieve_by_ids(
297+
txn, rev_reg_id, cred_rev_id, for_update=True
298+
)
299+
cred_ex_id = rev_rec.cred_ex_id
300+
rev_rec.state = IssuerCredRevRecord.STATE_REVOKED
301+
await rev_rec.save(txn, reason="revoke credential")
302+
await txn.commit()
303+
except StorageNotFoundError:
304+
continue
305+
292306
async with self._profile.transaction() as txn:
293307
try:
294-
rev_rec = await IssuerCredRevRecord.retrieve_by_ids(
295-
txn, rev_reg_id, cred_rev_id
308+
cred_ex_record = await V10CredentialExchange.retrieve_by_id(
309+
txn, cred_ex_id, for_update=True
310+
)
311+
cred_ex_record.state = (
312+
V10CredentialExchange.STATE_CREDENTIAL_REVOKED
296313
)
297-
try:
298-
cred_ex_record = await V10CredentialExchange.retrieve_by_id(
299-
txn, rev_rec.cred_ex_id, for_update=True
300-
)
301-
cred_ex_record.state = (
302-
V10CredentialExchange.STATE_CREDENTIAL_REVOKED
303-
)
304-
await cred_ex_record.save(txn, reason="revoke credential")
305-
await txn.commit()
306-
307-
except StorageNotFoundError:
308-
try:
309-
cred_ex_record = await V20CredExRecord.retrieve_by_id(
310-
txn, rev_rec.cred_ex_id, for_update=True
311-
)
312-
cred_ex_record.state = (
313-
V20CredExRecord.STATE_CREDENTIAL_REVOKED
314-
)
315-
await cred_ex_record.save(txn, reason="revoke credential")
316-
await txn.commit()
317-
318-
except StorageNotFoundError:
319-
pass
314+
await cred_ex_record.save(txn, reason="revoke credential")
315+
await txn.commit()
316+
continue # skip 2.0 record check
317+
except StorageNotFoundError:
318+
pass
320319

320+
try:
321+
cred_ex_record = await V20CredExRecord.retrieve_by_id(
322+
txn, cred_ex_id, for_update=True
323+
)
324+
cred_ex_record.state = V20CredExRecord.STATE_CREDENTIAL_REVOKED
325+
await cred_ex_record.save(txn, reason="revoke credential")
326+
await txn.commit()
321327
except StorageNotFoundError:
322328
pass

aries_cloudagent/revocation/models/issuer_cred_rev_record.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,15 @@ async def retrieve_by_ids(
9090
session: ProfileSession,
9191
rev_reg_id: str,
9292
cred_rev_id: str,
93+
*,
94+
for_update: bool = False,
9395
) -> "IssuerCredRevRecord":
9496
"""Retrieve an issuer cred rev record by rev reg id and cred rev id."""
9597
return await cls.retrieve_by_tag_filter(
96-
session, {"rev_reg_id": rev_reg_id}, {"cred_rev_id": cred_rev_id}
98+
session,
99+
{"rev_reg_id": rev_reg_id},
100+
{"cred_rev_id": cred_rev_id},
101+
for_update=for_update,
97102
)
98103

99104
@classmethod

0 commit comments

Comments
 (0)