Skip to content

Commit

Permalink
Merge pull request #587 from sklump/names-restrictions-attr-values-in…
Browse files Browse the repository at this point in the history
…-proof-req

support names and attribute-value specifications in attribute restric…
  • Loading branch information
ianco authored Jul 6, 2020
2 parents 02a65b9 + b11473c commit b7e9d41
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,44 @@ async def test_credential_exchange_create(self):
mock_cred_ex_record.serialize.return_value
)

async def test_credential_exchange_create_x(self):
mock = async_mock.MagicMock()
mock.json = async_mock.CoroutineMock()
context = RequestContext(base_context=InjectionContext(enforce_typing=False))
mock.app = {
"outbound_message_router": async_mock.CoroutineMock(),
"request_context": context,
}
mock.app["request_context"].settings = {}

with async_mock.patch.object(
test_module, "ConnectionRecord", autospec=True
) as mock_connection_record, async_mock.patch.object(
test_module, "CredentialManager", autospec=True
) as mock_credential_manager, async_mock.patch.object(
test_module.CredentialPreview, "deserialize", autospec=True
), async_mock.patch.object(
test_module.web, "json_response"
) as mock_response:
mock_credential_manager.return_value.create_offer = (
async_mock.CoroutineMock()
)

mock_credential_manager.return_value.create_offer.return_value = (
async_mock.CoroutineMock(),
async_mock.CoroutineMock(),
)

mock_cred_ex_record = async_mock.MagicMock()
mock_cred_offer = async_mock.MagicMock()

mock_credential_manager.return_value.prepare_send.side_effect = (
test_module.StorageError()
)

with self.assertRaises(test_module.web.HTTPBadRequest):
await test_module.credential_exchange_create(mock)

async def test_credential_exchange_create_no_proposal(self):
conn_id = "connection-id"

Expand Down
59 changes: 54 additions & 5 deletions aries_cloudagent/protocols/present_proof/v1_0/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class V10PresentationProposalRequestSchema(AdminAPIMessageTracingSchema):
)


class IndyProofReqSpecRestrictionsSchema(Schema):
class IndyProofReqPredSpecRestrictionsSchema(Schema):
"""Schema for restrictions in attr or pred specifier indy proof request."""

schema_id = fields.String(
Expand Down Expand Up @@ -185,15 +185,64 @@ class IndyProofReqAttrSpecSchema(Schema):
"""Schema for attribute specification in indy proof request."""

name = fields.String(
example="favouriteDrink", description="Attribute name", required=True
example="favouriteDrink", description="Attribute name", required=False
)
names = fields.List(
fields.String(example="age"),
description="Attribute name group",
required=False,
)
restrictions = fields.List(
fields.Nested(IndyProofReqSpecRestrictionsSchema()),
description="If present, credential must satisfy one of given restrictions",
fields.Dict(
keys=fields.Str(
validate=validate.Regexp(
"^schema_id|"
"schema_issuer_did|"
"schema_name|"
"schema_version|"
"issuer_did|"
"cred_def_id|"
"attr::.+::value$" # indy does not support attr::...::marker here
),
example="cred_def_id", # marshmallow/apispec v3.0 ignores
),
values=fields.Str(example=INDY_CRED_DEF_ID["example"]),
),
description=(
"If present, credential must satisfy one of given restrictions: specify "
"schema_id, schema_issuer_did, schema_name, schema_version, "
"issuer_did, cred_def_id, and/or attr::<attribute-name>::value "
"where <attribute-name> represents a credential attribute name"
),
required=False,
)
non_revoked = fields.Nested(IndyProofReqNonRevokedSchema(), required=False)

@validates_schema
def validate_fields(self, data, **kwargs):
"""
Validate schema fields.
Data must have exactly one of name or names; if names then restrictions are
mandatory.
Args:
data: The data to validate
Raises:
ValidationError: if data has both or neither of name and names
"""
if ("name" in data) == ("names" in data):
raise ValidationError(
"Attribute specification must have either name or names but not both"
)
restrictions = data.get("restrictions")
if ("names" in data) and (not restrictions or all(not r for r in restrictions)):
raise ValidationError(
"Attribute specification on 'names' must have non-empty restrictions"
)


class IndyProofReqPredSpecSchema(Schema):
"""Schema for predicate specification in indy proof request."""
Expand All @@ -206,7 +255,7 @@ class IndyProofReqPredSpecSchema(Schema):
)
p_value = fields.Integer(description="Threshold value", required=True)
restrictions = fields.List(
fields.Nested(IndyProofReqSpecRestrictionsSchema()),
fields.Nested(IndyProofReqPredSpecRestrictionsSchema()),
description="If present, credential must satisfy one of given restrictions",
required=False,
)
Expand Down
29 changes: 26 additions & 3 deletions aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,35 @@ def setUp(self):

async def test_validate_non_revoked(self):
non_revo = test_module.IndyProofReqNonRevokedSchema()
non_revo.validate({"from": 1234567890})
non_revo.validate({"to": 1234567890})
non_revo.validate({"from": 1234567890, "to": 1234567890})
non_revo.validate_fields({"from": 1234567890})
non_revo.validate_fields({"to": 1234567890})
non_revo.validate_fields({"from": 1234567890, "to": 1234567890})
with self.assertRaises(test_module.ValidationError):
non_revo.validate_fields({})

async def test_validate_proof_req_attr_spec(self):
aspec = test_module.IndyProofReqAttrSpecSchema()
aspec.validate_fields({"name": "attr0"})
aspec.validate_fields(
{
"names": ["attr0", "attr1"],
"restrictions": [{"attr::attr1::value": "my-value"}],
}
)
aspec.validate_fields(
{"name": "attr0", "restrictions": [{"schema_name": "preferences"}]}
)
with self.assertRaises(test_module.ValidationError):
aspec.validate_fields({})
with self.assertRaises(test_module.ValidationError):
aspec.validate_fields({"name": "attr0", "names": ["attr1", "attr2"]})
with self.assertRaises(test_module.ValidationError):
aspec.validate_fields({"names": ["attr1", "attr2"]})
with self.assertRaises(test_module.ValidationError):
aspec.validate_fields({"names": ["attr0", "attr1"], "restrictions": []})
with self.assertRaises(test_module.ValidationError):
aspec.validate_fields({"names": ["attr0", "attr1"], "restrictions": [{}]})

async def test_presentation_exchange_list(self):
mock = async_mock.MagicMock()
mock.query = {
Expand Down
12 changes: 12 additions & 0 deletions demo/runners/faber.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,18 @@ async def main(
for req_pred in req_preds
},
}
# test with an attribute group with attribute value restrictions
# indy_proof_request["requested_attributes"] = {
# "n_group_attrs": {
# "names": ["name", "degree", "timestamp", "date"],
# "restrictions": [
# {
# "issuer_did": agent.did,
# "attr::name::value": "Alice Smith"
# }
# ]
# }
# }
if revocation:
indy_proof_request["non_revoked"] = {"to": int(time.time())}
proof_request_web_request = {
Expand Down
6 changes: 5 additions & 1 deletion demo/runners/support/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,8 +482,12 @@ async def admin_request(
async with self.client_session.request(
method, self.admin_url + path, json=data, params=params
) as resp:
resp.raise_for_status()
resp_text = await resp.text()
try:
resp.raise_for_status()
except Exception as e:
# try to retrieve and print text on error
raise Exception(f"Error: {resp_text}") from e
if not resp_text and not text:
return None
if not text:
Expand Down

0 comments on commit b7e9d41

Please sign in to comment.