Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ async def emit_event(self, session: ProfileSession, payload: Optional[Any] = Non
if not session.profile.settings.get("debug.webhooks"):
payload = V20PresExRecordWebhook(**payload)
payload = payload.__dict__
# BUG #3802: remove legacy fields when by_format is present
elif payload.get("by_format"):
for key in ("pres_proposal", "pres_request", "pres"):
payload.pop(key, None)
Comment on lines +209 to +212
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        if not session.profile.settings.get("debug.webhooks"):
...
            # BUG #3802: remove legacy fields when by_format is present
        elif payload.get("by_format"):
            for key in ("pres_proposal", "pres_request", "pres"):
                payload.pop(key, None)

->

        if not session.profile.settings.get("debug.webhooks"):
...
        elif by_format := payload.get("by_format"):
            # Issue #3802: remove legacy fields when by_format is present
            for key in ("pres_proposal", "pres_request", "pres"):
                if key in by_format:
                    payload.pop(key, None)

^ Move/reword comment; assign by_format; and check key in by_format before removing.

It's probably good to explicitly check that they key being removed does in fact exist in by_format already.


await session.emit_event(topic, payload)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
from ......messaging.models.base_record import BaseExchangeRecord, BaseExchangeSchema
from ......tests import mock
from ......utils.testing import create_test_profile
from ...message_types import ATTACHMENT_FORMAT, PRES_20_PROPOSAL
from ...message_types import ATTACHMENT_FORMAT, PRES_20, PRES_20_PROPOSAL, PRES_20_REQUEST
from ...messages.pres import V20Pres
from ...messages.pres_format import V20PresFormat
from ...messages.pres_proposal import V20PresProposal
from ...messages.pres_request import V20PresRequest
from .. import pres_exchange as test_module
from ..pres_exchange import V20PresExRecord

Expand Down Expand Up @@ -52,6 +54,16 @@
}
},
}
INDY_PROOF = {
"proof": {"proofs": []},
"requested_proof": {
"revealed_attrs": {},
"self_attested_attrs": {},
"unrevealed_attrs": {},
"predicates": {},
},
"identifiers": [],
}


class BasexRecordImpl(BaseExchangeRecord):
Expand Down Expand Up @@ -134,3 +146,58 @@ async def test_save_error_state(self):
mock_save.side_effect = test_module.StorageError()
await record.save_error_state(session, reason="testing")
mock_log_exc.assert_called_once()

# BUG #3802: ensure webhook payloads omit legacy pres_* fields when by_format exists
async def test_emit_event_strips_legacy_pres_fields(self):
settings = {
"wallet.type": "askar",
"auto_provision": True,
"wallet.key": "5BngFuBpS4wjFfVFCtPqoix3ZXG2XR8XJ7qosUzMak7R",
"wallet.key_derivation_method": "RAW",
"debug.webhooks": True,
}
self.profile = await create_test_profile(settings=settings)
pres_request = V20PresRequest(
formats=[
V20PresFormat(
attach_id="indy",
format_=ATTACHMENT_FORMAT[PRES_20_REQUEST][
V20PresFormat.Format.INDY.api
],
)
],
request_presentations_attach=[
AttachDecorator.data_base64(mapping=INDY_PROOF_REQ, ident="indy")
],
)
pres = V20Pres(
formats=[
V20PresFormat(
attach_id="indy",
format_=ATTACHMENT_FORMAT[PRES_20][V20PresFormat.Format.INDY.api],
)
],
presentations_attach=[
AttachDecorator.data_base64(mapping=INDY_PROOF, ident="indy")
],
)
record = V20PresExRecord(
pres_ex_id="pxid",
thread_id="thid",
connection_id="conn_id",
initiator="init",
role="role",
state=V20PresExRecord.STATE_PRESENTATION_RECEIVED,
pres_request=pres_request,
pres=pres,
)

async with self.profile.session() as session:
session.emit_event = mock.CoroutineMock()
await record.emit_event(session)

payload = session.emit_event.call_args.args[1]
assert "by_format" in payload
assert "pres_request" not in payload
assert "pres" not in payload
assert "pres_proposal" not in payload
Comment on lines +199 to +203
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would just add an assertion that those 3 keys do exist in payload["by_format"]

for key in ("pres", "pres_proposal", "pres_request"):
    assert key not in payload and key in payload["by_format"]

19 changes: 16 additions & 3 deletions scenarios/examples/did_indy_issuance_and_revocation/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,36 @@
didexchange,
indy_anoncred_credential_artifacts,
indy_issue_credential_v2,
indy_present_proof_v2,
params,
)
from aiohttp import ClientSession
from examples.util import indy_present_proof_v2

ALICE = getenv("ALICE", "http://alice:3001")
BOB = getenv("BOB", "http://bob:3001")


def _presentation_request_payload(presentation: V20PresExRecord):
if presentation.by_format and presentation.by_format.pres_request:
return presentation.by_format.pres_request
request = presentation.pres_request
if not request:
return None
if isinstance(request, dict):
return request
if hasattr(request, "model_dump"):
return request.model_dump(by_alias=True)
return request.dict(by_alias=True)


Comment on lines +24 to +42
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function _presentation_request_payload is duplicated in this file. It already exists in scenarios/examples/util.py (lines 63-75) with identical implementation. Consider removing this duplicate and importing it from util.py instead to maintain DRY principles and reduce maintenance burden.

Suggested change
from examples.util import indy_present_proof_v2
ALICE = getenv("ALICE", "http://alice:3001")
BOB = getenv("BOB", "http://bob:3001")
def _presentation_request_payload(presentation: V20PresExRecord):
if presentation.by_format and presentation.by_format.pres_request:
return presentation.by_format.pres_request
request = presentation.pres_request
if not request:
return None
if isinstance(request, dict):
return request
if hasattr(request, "model_dump"):
return request.model_dump(by_alias=True)
return request.dict(by_alias=True)
from examples.util import indy_present_proof_v2, _presentation_request_payload
ALICE = getenv("ALICE", "http://alice:3001")
BOB = getenv("BOB", "http://bob:3001")

Copilot uses AI. Check for mistakes.
def summary(presentation: V20PresExRecord) -> str:
"""Summarize a presentation exchange record."""
request = presentation.pres_request
request = _presentation_request_payload(presentation)
return "Summary: " + json.dumps(
{
"state": presentation.state,
"verified": presentation.verified,
"presentation_request": request.dict(by_alias=True) if request else None,
"presentation_request": request,
},
indent=2,
sort_keys=True,
Expand Down
17 changes: 15 additions & 2 deletions scenarios/examples/kanon_issuance_and_presentation/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,27 @@
BOB = getenv("BOB", "http://bob:3001")


def _presentation_request_payload(presentation: V20PresExRecord):
if presentation.by_format and presentation.by_format.pres_request:
return presentation.by_format.pres_request
request = presentation.pres_request
if not request:
return None
if isinstance(request, dict):
return request
if hasattr(request, "model_dump"):
return request.model_dump(by_alias=True)
return request.dict(by_alias=True)
Comment on lines +32 to +42
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function _presentation_request_payload is duplicated in this file. It already exists in scenarios/examples/util.py (lines 63-75) with identical implementation. Consider removing this duplicate and importing it from util.py instead to maintain DRY principles and reduce maintenance burden.

Copilot uses AI. Check for mistakes.


def summary(presentation: V20PresExRecord) -> str:
"""Summarize a presentation exchange record."""
request = presentation.pres_request
request = _presentation_request_payload(presentation)
return "Summary: " + json.dumps(
{
"state": presentation.state,
"verified": presentation.verified,
"presentation_request": request.dict(by_alias=True) if request else None,
"presentation_request": request,
},
indent=2,
sort_keys=True,
Expand Down
2 changes: 1 addition & 1 deletion scenarios/examples/multitenancy/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@
didexchange,
indy_anoncred_credential_artifacts,
indy_issue_credential_v2,
indy_present_proof_v2,
params,
)
from aiohttp import ClientSession
from examples.util import indy_present_proof_v2

AGENCY = getenv("AGENCY", "http://agency:3001")

Expand Down
19 changes: 16 additions & 3 deletions scenarios/examples/presenting_revoked_credential/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,36 @@
didexchange,
indy_anoncred_credential_artifacts,
indy_issue_credential_v2,
indy_present_proof_v2,
params,
)
from aiohttp import ClientSession
from examples.util import indy_present_proof_v2

ALICE = getenv("ALICE", "http://alice:3001")
BOB = getenv("BOB", "http://bob:3001")


def _presentation_request_payload(presentation: V20PresExRecord):
if presentation.by_format and presentation.by_format.pres_request:
return presentation.by_format.pres_request
request = presentation.pres_request
if not request:
return None
if isinstance(request, dict):
return request
if hasattr(request, "model_dump"):
return request.model_dump(by_alias=True)
return request.dict(by_alias=True)


Comment on lines +24 to +42
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function _presentation_request_payload is duplicated in this file. It already exists in scenarios/examples/util.py (lines 63-75) with identical implementation. Consider removing this duplicate and importing it from util.py instead to maintain DRY principles and reduce maintenance burden.

Suggested change
from examples.util import indy_present_proof_v2
ALICE = getenv("ALICE", "http://alice:3001")
BOB = getenv("BOB", "http://bob:3001")
def _presentation_request_payload(presentation: V20PresExRecord):
if presentation.by_format and presentation.by_format.pres_request:
return presentation.by_format.pres_request
request = presentation.pres_request
if not request:
return None
if isinstance(request, dict):
return request
if hasattr(request, "model_dump"):
return request.model_dump(by_alias=True)
return request.dict(by_alias=True)
from examples.util import indy_present_proof_v2, _presentation_request_payload
ALICE = getenv("ALICE", "http://alice:3001")
BOB = getenv("BOB", "http://bob:3001")

Copilot uses AI. Check for mistakes.
def summary(presentation: V20PresExRecord) -> str:
"""Summarize a presentation exchange record."""
request = presentation.pres_request
request = _presentation_request_payload(presentation)
return "Summary: " + json.dumps(
{
"state": presentation.state,
"verified": presentation.verified,
"presentation_request": request.dict(by_alias=True) if request else None,
"presentation_request": request,
},
indent=2,
sort_keys=True,
Expand Down
3 changes: 1 addition & 2 deletions scenarios/examples/simple_restart/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@
indy_anoncred_credential_artifacts,
indy_anoncred_onboard,
indy_issue_credential_v2,
indy_present_proof_v2,
)
from examples.util import wait_until_healthy
from examples.util import indy_present_proof_v2, wait_until_healthy

import docker

Expand Down
Loading
Loading