Skip to content

Commit

Permalink
Merge pull request openwallet-foundation#86 from Indicio-tech/feature…
Browse files Browse the repository at this point in the history
…/credential-attachments-holder

Feature/credential attachments holder
  • Loading branch information
dbluhm authored Sep 14, 2022
2 parents 8288ffb + 176f281 commit c1fed3c
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ async def create_pres(
async def get_supplements(
self, pres_ex_record: V20PresExRecord, request_data: dict = None
) -> Sequence[Supplement]:
"""Retrieve supplements"""
"""Retrieve supplements."""

@abstractmethod
async def receive_pres(self, message: V20Pres, pres_ex_record: V20PresExRecord):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,33 @@
"""V2.0 present-proof indy presentation-exchange format handler."""
import json
import logging
from typing import List, Mapping, Tuple

from marshmallow import RAISE
from typing import List, Mapping, Tuple

from ......indy.credx.holder import _normalize_attr_name
from ......indy.holder import IndyHolder, IndyHolderError
from ......indy.models.predicate import Predicate
from ......indy.models.proof import IndyProofSchema
from ......indy.models.proof_request import IndyProofRequestSchema
from ......indy.models.requested_creds import IndyRequestedCredsRequestedAttrSchema
from ......indy.models.xform import indy_proof_req_preview2indy_requested_creds
from ......indy.util import generate_pr_nonce
from ......indy.verifier import IndyVerifier
from ......indy.credx.holder import _normalize_attr_name
from ......indy.holder import IndyHolder, IndyHolderError
from ......messaging.decorators.attach_decorator import AttachDecorator
from ......messaging.util import canon
from ......wallet.models.attachment_data_record import AttachmentDataRecord
from ......wallet.util import b64_to_bytes
from .....issue_credential.v2_0.hashlink import Hashlink

from ....indy.pres_exch_handler import IndyPresExchHandler

from ...message_types import (
ATTACHMENT_FORMAT,
PRES_20_REQUEST,
PRES_20,
PRES_20_PROPOSAL,
PRES_20_REQUEST,
)
from ...messages.pres import V20Pres
from ...messages.pres_format import V20PresFormat
from ...models.pres_exchange import V20PresExRecord

from ..handler import V20PresFormatHandler, V20PresFormatHandlerError

LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -139,10 +135,12 @@ async def create_pres(
indy_proof_request = proof_request.attachment(
IndyPresExchangeHandler.format
)
requested_credentials = await indy_proof_req_preview2indy_requested_creds(
indy_proof_request,
preview=None,
holder=self._profile.inject(IndyHolder),
requested_credentials = (
await indy_proof_req_preview2indy_requested_creds(
indy_proof_request,
preview=None,
holder=self._profile.inject(IndyHolder),
)
)
except ValueError as err:
LOGGER.warning(f"{err}")
Expand All @@ -168,33 +166,56 @@ async def get_supplements(
) -> List[AttachmentDataRecord]:
"""Retrieve supplements"""

requested_attributes: dict = request_data.get("requested_attributes", {})
if not request_data:
try:
proof_request = pres_ex_record.pres_request
indy_proof_request = proof_request.attachment(
IndyPresExchangeHandler.format
)
requested_credentials = (
await indy_proof_req_preview2indy_requested_creds(
indy_proof_request,
preview=None,
holder=self._profile.inject(IndyHolder),
)
)
requested_attributes = requested_credentials.get(
"requested_attributes", {}
)
except ValueError as err:
LOGGER.warning(f"{err}")
raise V20PresFormatHandlerError(
f"No matching Indy credentials found: {err}"
)
else:
requested_attributes: dict = request_data.get("requested_attributes", {})

indy_pres_request: dict = pres_ex_record.by_format["pres_request"].get(
V20PresFormat.Format.INDY.api, {}
)

records: List[AttachmentDataRecord] = []
for attr_referent, value in requested_attributes:
value: IndyRequestedCredsRequestedAttrSchema
cred_id = value.cred_id

if attr_referent in indy_pres_request["requested_attributes"]:
attr = indy_pres_request["requested_attributes"][attr_referent]
if "name" in attr:
attribute_name = _normalize_attr_name(attr["name"])
else:
raise IndyHolderError(
f"Unknown presentation request referent: {attr_referent}"
)
# TODO: handle "names" scenario
else:
for attr_referent, value in requested_attributes.items():
cred_id = value["cred_id"]

if attr_referent not in indy_pres_request["requested_attributes"]:
raise IndyHolderError(
f"Unknown presentation request referent: {attr_referent}"
)

attr = indy_pres_request["requested_attributes"][attr_referent]
if "name" in attr:
attribute_name = _normalize_attr_name(attr["name"])
elif "names" in attr:
attribute_name = [_normalize_attr_name(name) for name in attr["names"]]
else:
raise IndyHolderError(f"Unknown presentation request attr: {attr}")
async with self._profile.session() as session:
record: AttachmentDataRecord = await AttachmentDataRecord.query_by_cred_id_attribute(
session, cred_id, attribute_name
record: AttachmentDataRecord = (
await AttachmentDataRecord.query_by_cred_id_attribute(
session,
cred_id,
attribute_name,
)
)
records.append(record)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
from .....multitenant.base import BaseMultitenantManager
from .....multitenant.manager import MultitenantManager
from .....storage.error import StorageNotFoundError
from .....protocols.issue_credential.v2_0.messages.inner.supplement import Supplement
from .....wallet.models.attachment_data_record import AttachmentDataRecord

from ...indy import pres_exch_handler as test_indy_util_module

Expand Down Expand Up @@ -1101,7 +1103,9 @@ async def test_create_pres_bad_revoc_state(self):
test_indy_util_module, "RevocationRegistry", autospec=True
) as mock_rr, async_mock.patch.object(
test_indy_util_module.LOGGER, "error", async_mock.MagicMock()
) as mock_log_error:
) as mock_log_error, async_mock.patch.object(
AttachmentDataRecord, "query_by_cred_id_attribute"
):
mock_rr.from_definition = async_mock.MagicMock(return_value=more_magic_rr)

mock_attach_decorator.data_base64 = async_mock.MagicMock(
Expand Down Expand Up @@ -2082,6 +2086,14 @@ async def test_verify_pres(self):
pres_request=pres_request,
pres=pres,
)
px_rec_in.supplements = [
Supplement(
type="hashlink_data",
attrs=[{"key": "field", "value": "<fieldname>"}],
ref=None,
id="attachment_id",
)
]
self.profile.context.injector.bind_instance(
BaseMultitenantManager,
async_mock.MagicMock(MultitenantManager, autospec=True),
Expand Down
47 changes: 25 additions & 22 deletions aries_cloudagent/wallet/models/attachment_data_record.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
"""Attachment Data Record"""

from typing import Sequence
from typing import List, Sequence, Union

from marshmallow import fields

from ...core.profile import ProfileSession
from ...messaging.decorators.attach_decorator import (
AttachDecorator,
AttachDecoratorData,
AttachDecoratorSchema,
)
from ...messaging.models.base_record import BaseRecord, BaseRecordSchema
from ...messaging.valid import UUIDFour
from ...protocols.issue_credential.v2_0.messages.inner.supplement import (
Supplement,
SupplementAttribute,
SupplementSchema,
)
from ...messaging.decorators.attach_decorator import (
AttachDecorator,
AttachDecoratorData,
AttachDecoratorSchema,
)


class AttachmentDataRecord(BaseRecord):
"""Represents an attachment data record"""
"""Represents an attachment data record."""

class Meta:
"""AttachmentDataRecord metadata"""
"""AttachmentDataRecord metadata."""

schema_class = "AttachmentDataRecordSchema"

Expand All @@ -47,16 +48,21 @@ def __init__(
async def get_attachment_data_record(
self, session: ProfileSession, attachment_id: str
):
"""Query by attachment_id"""
"""Query by attachment_id."""
tag_filter = {"attachment_id": attachment_id}
return await self.retrieve_by_tag_filter(session, tag_filter)

@classmethod
async def query_by_cred_id_attribute(
self, session: ProfileSession, cred_id: str, attribute: str
cls, session: ProfileSession, cred_id: str, attribute: Union[str, List[str]]
):
"""Query by cred_id"""
tag_filter = {"cred_id": cred_id, "attribute": attribute}
return await self.retrieve_by_tag_filter(session, tag_filter)
"""Query by cred_id."""
if isinstance(attribute, list):
attrs = [{"attribute": attr} for attr in attribute]
tag_filter = {"cred_id": cred_id, "$or": attrs}
else:
tag_filter = {"cred_id": cred_id, "attribute": attribute}
return await cls.retrieve_by_tag_filter(session, tag_filter)

@classmethod
def attachment_lookup(cls, attachments: Sequence[AttachDecorator]) -> dict:
Expand Down Expand Up @@ -91,7 +97,7 @@ def match_by_attachment_id(

@classmethod
async def save_attachments(cls, session, supplements, attachments, cred_id):
""" "Save all attachments"""
"""Save all attachments."""
return [
await attachment.save(session)
for attachment in AttachmentDataRecord.match_by_attachment_id(
Expand All @@ -104,29 +110,26 @@ class AttachmentDataRecordSchema(BaseRecordSchema):
"""AttachmentDataRecord schema"""

class Meta:
"""AttachmentDataRecordSchema metadata"""
"""AttachmentDataRecordSchema metadata."""

model_class = AttachmentDataRecord

attachment_id = fields.Str(
description="Attachment identifier",
description="Attachment identifier.",
example=UUIDFour.EXAMPLE,
required=False,
allow_none=False,
data_key="@id",
)
supplements = fields.Nested(
supplement = fields.Nested(
SupplementSchema,
description="Supplements to the credential",
many=True,
description="Supplement to the credential",
required=False,
)
attachments = fields.Nested(
attachment = fields.Nested(
AttachDecoratorSchema,
many=True,
required=False,
description="Attachments of other data associated with the credential",
data_key="~attach",
)
cred_id = fields.Str(
example="3fa85f64-5717-4562-b3fc-2c963f66afa6",
Expand Down

0 comments on commit c1fed3c

Please sign in to comment.