Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aries 36 and 37 v1, demo/workshop updates to match #164

Merged
merged 7 commits into from
Sep 1, 2019
8 changes: 8 additions & 0 deletions aries_cloudagent/admin/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
from ..messaging.connections.routes import register as register_connections
from ..messaging.credentials.routes import register as register_credentials
from ..messaging.introduction.routes import register as register_introduction
from ..messaging.issue_credential.v1_0.routes import (
register as register_v10_issue_credential
)
from ..messaging.present_proof.v1_0.routes import (
register as register_v10_present_proof
)
from ..messaging.presentations.routes import register as register_presentations
from ..messaging.schemas.routes import register as register_schemas
from ..messaging.credential_definitions.routes import (
Expand Down Expand Up @@ -34,4 +40,6 @@ async def register_module_routes(app: web.Application):
await register_basicmessages(app)
await register_discovery(app)
await register_trustping(app)
await register_v10_issue_credential(app)
await register_v10_present_proof(app)
await register_wallet(app)
7 changes: 6 additions & 1 deletion aries_cloudagent/admin/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,12 @@ async def _perform_send_webhook(
full_webhook_url, json=payload
) as response:
if response.status < 200 or response.status > 299:
raise Exception("Unexpected response status")
# raise Exception(f"Unexpected response status {response.status}")
raise Exception(
f"Unexpected: target {target_url}\n"
f"full {full_webhook_url}\n"
f"response {response}"
)

async def complete_webhooks(self):
"""Wait for all pending webhooks to be dispatched, used in testing."""
Expand Down
23 changes: 23 additions & 0 deletions aries_cloudagent/config/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,29 @@ def add_arguments(self, parser: ArgumentParser):
help="Automatically respond to basic messages indicating the message was\
received. Default: false.",
)
parser.add_argument(
"--auto-respond-credential-proposal",
action="store_true",
help="Auto-respond to credential proposals with corresponding "
+ "credential offers",
)
parser.add_argument(
"--auto-respond-credential-offer",
action="store_true",
help="Automatically respond to Indy credential offers with a credential\
request. Default: false",
)
parser.add_argument(
"--auto-respond-credential-request",
action="store_true",
help="Auto-respond to credential requests with corresponding credentials",
)
parser.add_argument(
"--auto-respond-presentation-proposal",
action="store_true",
help="Auto-respond to presentation proposals with corresponding "
+ "presentation requests",
)
parser.add_argument(
"--auto-respond-presentation-request",
action="store_true",
Expand Down Expand Up @@ -270,8 +287,14 @@ def get_settings(self, args: Namespace) -> dict:
if args.invite:
settings["debug.print_invitation"] = True

if args.auto_respond_credential_proposal:
settings["debug.auto_respond_credential_proposal"] = True
if args.auto_respond_credential_offer:
settings["debug.auto_respond_credential_offer"] = True
if args.auto_respond_credential_request:
settings["debug.auto_respond_credential_request"] = True
if args.auto_respond_presentation_proposal:
settings["debug.auto_respond_presentation_proposal"] = True
if args.auto_respond_presentation_request:
settings["debug.auto_respond_presentation_request"] = True
if args.auto_store_credential:
Expand Down
8 changes: 8 additions & 0 deletions aries_cloudagent/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
from .messaging.credentials.message_types import MESSAGE_TYPES as CREDENTIAL_MESSAGES
from .messaging.trustping.message_types import MESSAGE_TYPES as TRUSTPING_MESSAGES
from .messaging.routing.message_types import MESSAGE_TYPES as ROUTING_MESSAGES
from .messaging.issue_credential.v1_0.message_types import (
MESSAGE_TYPES as V10_ISSUE_CREDENTIAL_MESSAGES
)
from .messaging.present_proof.v1_0.message_types import (
MESSAGE_TYPES as V10_PRESENT_PROOF_MESSAGES
)

from .messaging.problem_report.message import (
MESSAGE_TYPE as PROBLEM_REPORT,
Expand All @@ -34,7 +40,9 @@ def default_protocol_registry() -> ProtocolRegistry:
DISCOVERY_MESSAGES,
INTRODUCTION_MESSAGES,
PRESENTATION_MESSAGES,
V10_PRESENT_PROOF_MESSAGES,
CREDENTIAL_MESSAGES,
V10_ISSUE_CREDENTIAL_MESSAGES,
ROUTING_MESSAGES,
TRUSTPING_MESSAGES,
{PROBLEM_REPORT: ProblemReport},
Expand Down
79 changes: 73 additions & 6 deletions aries_cloudagent/holder/indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@
import logging

from collections import OrderedDict
from typing import Sequence
from typing import Sequence, Union

import indy.anoncreds
from indy.error import ErrorCode, IndyError

from ..storage.indy import IndyStorage
from ..storage.error import StorageError, StorageNotFoundError
from ..storage.record import StorageRecord

from ..wallet.error import WalletNotFoundError

from .base import BaseHolder
Expand All @@ -17,6 +21,8 @@
class IndyHolder(BaseHolder):
"""Indy holder class."""

RECORD_TYPE_MIME_TYPES = "attribute-mime-types"

def __init__(self, wallet):
"""
Initialize an IndyHolder instance.
Expand Down Expand Up @@ -66,17 +72,24 @@ async def create_credential_request(
return credential_request, credential_request_metadata

async def store_credential(
self, credential_definition, credential_data, credential_request_metadata
self,
credential_definition,
credential_data,
credential_request_metadata,
credential_attr_mime_types=None
):
"""
Store a credential in the wallet.

Args:
credential_definition: Credential definition for this credential
credential_data: Credential data generated by the issuer
credential_request_metadata: credential request metadata generated
by the issuer
credential_attr_mime_types: dict mapping attribute names to (optional)
MIME types to store as non-secret record, if specified

"""

credential_id = await indy.anoncreds.prover_store_credential(
self.wallet.handle,
None, # Always let indy set the id for now
Expand All @@ -86,6 +99,22 @@ async def store_credential(
None, # We don't support revocation yet
)

if credential_attr_mime_types:
mime_types = {
attr: credential_attr_mime_types.get(attr)
for attr in credential_data["values"]
if attr in credential_attr_mime_types
}
if mime_types:
record = StorageRecord(
type=IndyHolder.RECORD_TYPE_MIME_TYPES,
value=credential_id,
tags=mime_types,
id=f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}"
)
indy_stor = IndyStorage(self.wallet)
await indy_stor.add_record(record)

return credential_id

async def get_credentials(self, start: int, count: int, wql: dict):
Expand All @@ -99,7 +128,8 @@ async def get_credentials(self, start: int, count: int, wql: dict):

"""
search_handle, record_count = await indy.anoncreds.prover_search_credentials(
self.wallet.handle, json.dumps(wql)
self.wallet.handle,
json.dumps(wql)
)

# We need to move the database cursor position manually...
Expand All @@ -108,7 +138,8 @@ async def get_credentials(self, start: int, count: int, wql: dict):
await indy.anoncreds.prover_fetch_credentials(search_handle, start)

credentials_json = await indy.anoncreds.prover_fetch_credentials(
search_handle, count
search_handle,
count
)
await indy.anoncreds.prover_close_credentials_search(search_handle)

Expand Down Expand Up @@ -212,6 +243,16 @@ async def delete_credential(self, credential_id: str):
credential_id: Credential id to remove

"""
try:
indy_stor = IndyStorage(self.wallet)
mime_types_record = await indy_stor.get_record(
IndyHolder.RECORD_TYPE_MIME_TYPES,
f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}"
)
await indy_stor.delete_record(mime_types_record)
except StorageNotFoundError:
pass # MIME types record not present: carry on

try:
await indy.anoncreds.prover_delete_credential(
self.wallet.handle, credential_id
Expand All @@ -224,6 +265,32 @@ async def delete_credential(self, credential_id: str):
else:
raise

async def get_mime_type(
self,
credential_id: str,
attr: str = None
) -> Union[dict, str]:
"""
Get MIME type per attribute (or for all attributes).

Args:
credential_id: credential id
attr: attribute of interest or omit for all

Returns: Attribute MIME type or dict mapping attribute names to MIME types
attr_meta_json = all_meta.tags.get(attr)

"""
try:
mime_types_record = await IndyStorage(self.wallet).get_record(
IndyHolder.RECORD_TYPE_MIME_TYPES,
f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}"
)
except StorageError:
return None # no MIME types: not an error

return mime_types_record.tags.get(attr) if attr else mime_types_record.tags

async def create_presentation(
self,
presentation_request: dict,
Expand All @@ -249,7 +316,7 @@ async def create_presentation(
self.wallet.master_secret_id,
json.dumps(schemas),
json.dumps(credential_definitions),
json.dumps({}), # We don't support revocation currently.
json.dumps({}) # We don't support revocation currently.
)

presentation = json.loads(presentation_json)
Expand Down
102 changes: 99 additions & 3 deletions aries_cloudagent/holder/tests/test_indy.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@
from asynctest import TestCase as AsyncTestCase
from asynctest import mock as async_mock

import pytest
from indy.error import IndyError, ErrorCode

from aries_cloudagent.holder.indy import IndyHolder
from aries_cloudagent.storage.error import StorageError
from aries_cloudagent.storage.record import StorageRecord
from aries_cloudagent.wallet.indy import IndyWallet

import pytest

from ...messaging.issue_credential.v1_0.messages.inner.credential_preview import (
CredentialPreview
)


@pytest.mark.indy
Expand Down Expand Up @@ -56,6 +65,72 @@ async def test_store_credential(self, mock_store_cred):

assert cred_id == "cred_id"

@async_mock.patch("indy.non_secrets.get_wallet_record")
async def test_get_credential_attrs_mime_types(self, mock_nonsec_get_wallet_record):
cred_id = "credential_id"
dummy_tags = {"a": "1", "b": "2"}
dummy_rec = {
"type": IndyHolder.RECORD_TYPE_MIME_TYPES,
"id": cred_id,
"value": "value",
"tags": dummy_tags
}
mock_nonsec_get_wallet_record.return_value = json.dumps(dummy_rec)

mock_wallet = async_mock.MagicMock()

holder = IndyHolder(mock_wallet)

mime_types = await holder.get_mime_type(cred_id)

mock_nonsec_get_wallet_record.assert_called_once_with(
mock_wallet.handle,
dummy_rec["type"],
f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{dummy_rec['id']}",
json.dumps(
{
"retrieveType": True,
"retrieveValue": True,
"retrieveTags": True
}
)
)

assert mime_types == dummy_tags

@async_mock.patch("indy.non_secrets.get_wallet_record")
async def test_get_credential_attr_mime_type(self, mock_nonsec_get_wallet_record):
cred_id = "credential_id"
dummy_tags = {"a": "1", "b": "2"}
dummy_rec = {
"type": IndyHolder.RECORD_TYPE_MIME_TYPES,
"id": cred_id,
"value": "value",
"tags": dummy_tags
}
mock_nonsec_get_wallet_record.return_value = json.dumps(dummy_rec)

mock_wallet = async_mock.MagicMock()

holder = IndyHolder(mock_wallet)

a_mime_type = await holder.get_mime_type(cred_id, "a")

mock_nonsec_get_wallet_record.assert_called_once_with(
mock_wallet.handle,
dummy_rec["type"],
f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{dummy_rec['id']}",
json.dumps(
{
"retrieveType": True,
"retrieveValue": True,
"retrieveTags": True
}
)
)

assert a_mime_type == dummy_tags["a"]

@async_mock.patch("indy.anoncreds.prover_search_credentials")
@async_mock.patch("indy.anoncreds.prover_fetch_credentials")
@async_mock.patch("indy.anoncreds.prover_close_credentials_search")
Expand Down Expand Up @@ -152,13 +227,34 @@ async def test_get_credential(self, mock_get_cred):
assert credential == json.loads("{}")

@async_mock.patch("indy.anoncreds.prover_delete_credential")
async def test_get_credential(self, mock_del_cred):
@async_mock.patch("indy.non_secrets.get_wallet_record")
@async_mock.patch("indy.non_secrets.delete_wallet_record")
async def test_delete_credential(
self,
mock_nonsec_del_wallet_record,
mock_nonsec_get_wallet_record,
mock_prover_del_cred
):
mock_wallet = async_mock.MagicMock()
holder = IndyHolder(mock_wallet)
mock_nonsec_get_wallet_record.return_value = json.dumps(
{
"type": "typ",
"id": "ident",
"value": "value",
"tags": {
"a": json.dumps("1"),
"b": json.dumps("2")
}
}
)

credential = await holder.delete_credential("credential_id")

mock_del_cred.assert_called_once_with(mock_wallet.handle, "credential_id")
mock_prover_del_cred.assert_called_once_with(
mock_wallet.handle,
"credential_id"
)

@async_mock.patch("indy.anoncreds.prover_create_proof")
async def test_create_presentation(self, mock_create_proof):
Expand Down
Loading