Skip to content

Commit

Permalink
Merge branch 'main' into fix-3550
Browse files Browse the repository at this point in the history
  • Loading branch information
jamshale authored Mar 5, 2025
2 parents 42e4fc3 + d9a6712 commit d240762
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 29 deletions.
8 changes: 6 additions & 2 deletions acapy_agent/askar/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,15 @@ def bind_providers(self):
),
)

def session(self, context: Optional[InjectionContext] = None) -> ProfileSession:
def session(
self, context: Optional[InjectionContext] = None
) -> "AskarProfileSession":
"""Start a new interactive session with no transaction support requested."""
return AskarProfileSession(self, False, context=context)

def transaction(self, context: Optional[InjectionContext] = None) -> ProfileSession:
def transaction(
self, context: Optional[InjectionContext] = None
) -> "AskarProfileSession":
"""Start a new interactive session with commit and rollback support.
If the current backend does not support transactions, then commit
Expand Down
24 changes: 23 additions & 1 deletion acapy_agent/indy/credx/holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def profile(self) -> AskarProfile:

async def get_link_secret(self) -> LinkSecret:
"""Get or create the default link secret."""
LOGGER.debug("Attempting to fetch or create the link secret.")

while True:
async with self._profile.session() as session:
Expand All @@ -75,30 +76,51 @@ async def get_link_secret(self) -> LinkSecret:
CATEGORY_LINK_SECRET, IndyCredxHolder.LINK_SECRET_ID
)
except AskarError as err:
LOGGER.error("Error fetching link secret: %s", err)
raise IndyHolderError("Error fetching link secret") from err

if record:
try:
LOGGER.debug("Loading LinkSecret")
secret = LinkSecret.load(record.raw_value)
LOGGER.debug("Loaded existing link secret.")
except CredxError as err:
raise IndyHolderError("Error loading link secret") from err
LOGGER.info(
"Attempt fallback method after error loading link secret: %s",
err,
)
try:
ms_string = record.value.decode("ascii")
link_secret_dict = {"value": {"ms": ms_string}}
secret = LinkSecret.load(link_secret_dict)
LOGGER.debug("Loaded LinkSecret from Anoncreds secret.")
except CredxError as decode_err:
LOGGER.error("Error loading link secret: %s", decode_err)
raise IndyHolderError("Error loading link secret") from err
break
else:
try:
secret = LinkSecret.create()
LOGGER.debug("Created new link secret.")
except CredxError as err:
LOGGER.error("Error creating link secret: %s", err)
raise IndyHolderError("Error creating link secret") from err

try:
await session.handle.insert(
CATEGORY_LINK_SECRET,
IndyCredxHolder.LINK_SECRET_ID,
secret.to_json_buffer(),
)
LOGGER.debug("Saved new link secret.")
except AskarError as err:
if err.code != AskarErrorCode.DUPLICATE:
LOGGER.error("Error saving link secret: %s", err)
raise IndyHolderError("Error saving link secret") from err
# else: lost race to create record, retry
else:
break
LOGGER.debug("Returning link secret.")
return secret

async def create_credential_request(
Expand Down
130 changes: 130 additions & 0 deletions acapy_agent/indy/credx/tests/test_get_link_secret.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from unittest import IsolatedAsyncioTestCase

import pytest
from aries_askar import AskarError, AskarErrorCode

from acapy_agent.indy.credx.holder import CredxError, IndyHolderError
from acapy_agent.utils.testing import create_test_profile

from ....tests import mock
from .. import holder


@pytest.mark.askar
@pytest.mark.indy_credx
class TestIndyCredxGetLinkSecret(IsolatedAsyncioTestCase):
async def asyncSetUp(self):
self.holder_profile = await create_test_profile()
self.holder = holder.IndyCredxHolder(self.holder_profile)

self.mock_session = mock.MagicMock()
self.mock_session.__aenter__.return_value = self.mock_session
self.mock_session.__aexit__.return_value = None
self.holder._profile.session = mock.MagicMock(return_value=self.mock_session)

@mock.patch("acapy_agent.indy.credx.holder.LinkSecret.load")
@mock.patch("acapy_agent.indy.credx.holder.LinkSecret.create")
async def test_get_link_secret_existing(self, mock_create, mock_load):
# Mock session and record
mock_record = mock.MagicMock()
mock_record.raw_value = b'{"value": {"ms": "mocked_ms"}}'

self.mock_session.handle.fetch = mock.CoroutineMock(return_value=mock_record)

# Test fetching existing link secret
secret = await self.holder.get_link_secret()
assert secret is not None
mock_load.assert_called_once_with(mock_record.raw_value)
mock_create.assert_not_called()

@mock.patch("acapy_agent.indy.credx.holder.LinkSecret.load")
async def test_get_link_secret_fetch_error(self, mock_load):
# Mock session to raise an error
self.mock_session.handle.fetch = mock.CoroutineMock(
side_effect=AskarError(AskarErrorCode.BACKEND, "Fetch error")
)

with pytest.raises(IndyHolderError, match="Error fetching link secret"):
await self.holder.get_link_secret()

mock_load.assert_not_called()

@mock.patch("acapy_agent.indy.credx.holder.LinkSecret.load")
async def test_get_link_secret_load_error(self, mock_load):
# Mock session and record
mock_record = mock.MagicMock()
mock_record.raw_value = b'{"value": {"ms": "mocked_ms"}}'
self.mock_session.handle.fetch = mock.CoroutineMock(return_value=mock_record)

# Mock load to raise an error
mock_load.side_effect = CredxError(4, "Load error")

with pytest.raises(IndyHolderError, match="Error loading link secret"):
await self.holder.get_link_secret()

@mock.patch("acapy_agent.indy.credx.holder.LinkSecret.load")
async def test_get_link_secret_fallback_load(self, mock_load):
# Mock session and record
mock_record = mock.MagicMock()
mock_record.raw_value = b'{"value": {"ms": "mocked_ms"}}'
self.mock_session.handle.fetch = mock.CoroutineMock(return_value=mock_record)

# Mock load to raise an error initially
mock_load.side_effect = [CredxError(4, "Load error"), mock.MagicMock()]

# Test fallback method
secret = await self.holder.get_link_secret()
assert secret is not None
assert mock_load.call_count == 2

@mock.patch("acapy_agent.indy.credx.holder.LinkSecret.create")
async def test_get_link_secret_create_error(self, mock_create):
# Mock session to return no record
self.mock_session.handle.fetch = mock.CoroutineMock(return_value=None)

# Mock create to raise an error
mock_create.side_effect = CredxError(4, "Create error")

with pytest.raises(IndyHolderError, match="Error creating link secret"):
await self.holder.get_link_secret()

@mock.patch("acapy_agent.indy.credx.holder.LinkSecret.create")
async def test_get_link_secret_create_and_save(self, mock_create):
# Mock session to return no record
self.mock_session.handle.fetch = mock.CoroutineMock(return_value=None)

# Mock successful creation
mock_secret = mock.MagicMock()
mock_create.return_value = mock_secret

# Mock successful insert
self.mock_session.handle.insert = mock.CoroutineMock()

# Test creating and saving new link secret
secret = await self.holder.get_link_secret()
assert secret is not None
mock_create.assert_called_once()
self.mock_session.handle.insert.assert_called_once()

@mock.patch("acapy_agent.indy.credx.holder.LinkSecret.create")
async def test_get_link_secret_duplicate_error(self, mock_create):
# Mock session to return no record
self.mock_session.handle.fetch = mock.CoroutineMock(return_value=None)

# Mock successful creation
mock_secret = mock.MagicMock()
mock_create.return_value = mock_secret

# Mock insert to raise a duplicate error
self.mock_session.handle.insert = mock.CoroutineMock(
side_effect=[
AskarError(AskarErrorCode.DUPLICATE, "Duplicate error"),
mock.CoroutineMock(),
]
)

# Test handling of duplicate error
secret = await self.holder.get_link_secret()
assert secret is not None
assert mock_create.call_count == 2
assert self.mock_session.handle.insert.call_count == 2
26 changes: 0 additions & 26 deletions acapy_agent/protocols/present_proof/v2_0/formats/indy/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,11 @@ class IndyPresExchangeHandler(V20PresFormatHandler):
"""Indy presentation format handler."""

format = V20PresFormat.Format.INDY
anoncreds_handler = None

def __init__(self, profile: Profile):
"""Shim initialization to check for new AnonCreds library."""
super().__init__(profile)

# Temporary shim while the new anoncreds library integration is in progress
wallet_type = profile.settings.get_value("wallet.type")
if wallet_type == "askar-anoncreds":
self.anoncreds_handler = AnonCredsPresExchangeHandler(profile)

@classmethod
def validate_fields(cls, message_type: str, attachment_data: Mapping):
"""Validate attachment data for a specific message type.
Expand Down Expand Up @@ -120,12 +114,6 @@ async def create_bound_request(
A tuple (updated presentation exchange record, presentation request message)
"""
# Temporary shim while the new anoncreds library integration is in progress
if self.anoncreds_handler:
return await self.anoncreds_handler.create_bound_request(
pres_ex_record,
request_data,
)

indy_proof_request = pres_ex_record.pres_proposal.attachment(
IndyPresExchangeHandler.format
Expand All @@ -149,12 +137,6 @@ async def create_pres(
) -> Tuple[V20PresFormat, AttachDecorator]:
"""Create a presentation."""

if self.anoncreds_handler:
return await self.anoncreds_handler.create_pres(
pres_ex_record,
request_data,
)

requested_credentials = {}
if not request_data:
try:
Expand Down Expand Up @@ -323,10 +305,6 @@ def _check_proof_vs_proposal():
f"restrictions {req_restrictions}"
)

# Temporary shim while the new anoncreds library integration is in progress
if self.anoncreds_handler:
return await self.anoncreds_handler.receive_pres(message, pres_ex_record)

proof = message.attachment(IndyPresExchangeHandler.format)
_check_proof_vs_proposal()

Expand All @@ -341,10 +319,6 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord:
presentation exchange record, updated
"""
# Temporary shim while the new anoncreds library integration is in progress
if self.anoncreds_handler:
return await self.anoncreds_handler.verify_pres(pres_ex_record)

pres_request_msg = pres_ex_record.pres_request

# The `or` anoncreds format is for the indy <--> anoncreds compatibility
Expand Down

0 comments on commit d240762

Please sign in to comment.