Skip to content

Commit

Permalink
Merge pull request #259 from sklump/cred-proposal-1.1
Browse files Browse the repository at this point in the history
broaden aries-rfc-36 cred proposal to underspecify cred def, allow is…
  • Loading branch information
andrewwhitehead authored Nov 8, 2019
2 parents e34b277 + 78316cb commit caf0b39
Show file tree
Hide file tree
Showing 10 changed files with 238 additions and 140 deletions.
16 changes: 3 additions & 13 deletions aries_cloudagent/messaging/credential_definitions/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,7 @@
from ...ledger.base import BaseLedger
from ...storage.base import BaseStorage
from ..valid import INDY_CRED_DEF_ID, INDY_SCHEMA_ID, INDY_VERSION
from .util import CRED_DEF_SENT_RECORD_TYPE


CRED_DEF_SENT_PARMS = [
"schema_id",
"schema_issuer_did",
"schema_name",
"schema_version",
"issuer_did",
"cred_def_id",
]
from .util import CRED_DEF_TAGS, CRED_DEF_SENT_RECORD_TYPE


class CredentialDefinitionSendRequestSchema(Schema):
Expand Down Expand Up @@ -136,7 +126,7 @@ async def credential_definitions_send_credential_definition(request: web.BaseReq
"in": "query",
"schema": {"type": "string"},
"required": False,
} for p in CRED_DEF_SENT_PARMS
} for p in CRED_DEF_TAGS
],
summary="Search for matching credential definitions that agent originated",
)
Expand All @@ -158,7 +148,7 @@ async def credential_definitions_created(request: web.BaseRequest):
found = await storage.search_records(
type_filter=CRED_DEF_SENT_RECORD_TYPE,
tag_query={
p: request.query[p] for p in CRED_DEF_SENT_PARMS if p in request.query
p: request.query[p] for p in CRED_DEF_TAGS if p in request.query
}
).fetch_all()

Expand Down
8 changes: 8 additions & 0 deletions aries_cloudagent/messaging/credential_definitions/util.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
"""Credential definition utilities."""

CRED_DEF_TAGS = [
"schema_id",
"schema_issuer_did",
"schema_name",
"schema_version",
"issuer_did",
"cred_def_id",
]
CRED_DEF_SENT_RECORD_TYPE = "cred_def_sent"
9 changes: 3 additions & 6 deletions aries_cloudagent/messaging/schemas/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@
from ...ledger.base import BaseLedger
from ...storage.base import BaseStorage
from ..valid import INDY_SCHEMA_ID, INDY_VERSION
from .util import SCHEMA_SENT_RECORD_TYPE


SCHEMA_SENT_PARMS = ["schema_id", "schema_issuer_did", "schema_name", "schema_version"]
from .util import SCHEMA_SENT_RECORD_TYPE, SCHEMA_TAGS


class SchemaSendRequestSchema(Schema):
Expand Down Expand Up @@ -137,7 +134,7 @@ async def schemas_send_schema(request: web.BaseRequest):
"in": "query",
"schema": {"type": "string"},
"required": False,
} for p in SCHEMA_SENT_PARMS
} for p in SCHEMA_TAGS
],
summary="Search for matching schema that agent originated",
)
Expand All @@ -159,7 +156,7 @@ async def schemas_created(request: web.BaseRequest):
found = await storage.search_records(
type_filter=SCHEMA_SENT_RECORD_TYPE,
tag_query={
p: request.query[p] for p in SCHEMA_SENT_PARMS if p in request.query
p: request.query[p] for p in SCHEMA_TAGS if p in request.query
}
).fetch_all()

Expand Down
1 change: 1 addition & 0 deletions aries_cloudagent/messaging/schemas/util.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Schema utilities."""

SCHEMA_TAGS = ["schema_id", "schema_issuer_did", "schema_name", "schema_version"]
SCHEMA_SENT_RECORD_TYPE = "schema_sent"
112 changes: 61 additions & 51 deletions aries_cloudagent/protocols/issue_credential/v1_0/manager.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
"""Classes to manage credentials."""

import logging
from typing import Tuple
from typing import Mapping, Tuple

from ....cache.base import BaseCache
from ....config.injection_context import InjectionContext
from ....error import BaseError
from ....holder.base import BaseHolder
from ....issuer.base import BaseIssuer
from ....ledger.base import BaseLedger
from ....messaging.credential_definitions.util import (
CRED_DEF_TAGS,
CRED_DEF_SENT_RECORD_TYPE
)
from ....storage.base import BaseStorage
from ....storage.error import StorageNotFoundError

from .messages.credential_issue import CredentialIssue
Expand Down Expand Up @@ -48,6 +53,20 @@ def context(self) -> InjectionContext:
"""
return self._context

async def _match_sent_cred_def_id(self, tag_query: Mapping[str, str]) -> str:
"""Return most recent matching id of cred def that agent sent to ledger."""

storage: BaseStorage = await self.context.inject(BaseStorage)
found = await storage.search_records(
type_filter=CRED_DEF_SENT_RECORD_TYPE,
tag_query=tag_query,
).fetch_all()
if not found:
raise CredentialManagerError(
f"Issuer has no operable cred def for proposal spec {tag_query}"
)
return max(found, key=lambda r: int(r.tags["epoch"])).tags["cred_def_id"]

async def prepare_send(
self, connection_id: str, credential_proposal: CredentialProposal
) -> Tuple[V10CredentialExchange, CredentialOffer]:
Expand All @@ -63,19 +82,11 @@ async def prepare_send(
A tuple of the new credential exchange record and credential offer message
"""

credential_definition_id = credential_proposal.cred_def_id
if not credential_definition_id:
raise CredentialManagerError(
"Proposal credential definition ID is required"
)

credential_exchange = V10CredentialExchange(
auto_issue=True,
connection_id=connection_id,
initiator=V10CredentialExchange.INITIATOR_SELF,
role=V10CredentialExchange.ROLE_ISSUER,
credential_definition_id=credential_definition_id,
credential_proposal_dict=credential_proposal.serialize(),
)
(credential_exchange, credential_offer) = await self.create_offer(
Expand All @@ -91,7 +102,12 @@ async def create_proposal(
auto_offer: bool = None,
comment: str = None,
credential_preview: CredentialPreview = None,
credential_definition_id: str,
schema_id: str = None,
schema_issuer_did: str = None,
schema_name: str = None,
schema_version: str = None,
cred_def_id: str = None,
issuer_did: str = None,
) -> V10CredentialExchange:
"""
Create a credential proposal.
Expand All @@ -103,32 +119,30 @@ async def create_proposal(
comment: Optional human-readable comment to include in proposal
credential_preview: The credential preview to use to create
the credential proposal
credential_definition_id: Credential definition id for the
credential proposal
schema_id: Schema id for credential proposal
schema_issuer_did: Schema issuer DID for credential proposal
schema_name: Schema name for credential proposal
schema_version: Schema version for credential proposal
cred_def_id: Credential definition id for credential proposal
issuer_did: Issuer DID for credential proposal
Returns:
Resulting credential exchange record including credential proposal
"""
# Credential definition id must be present
if not credential_definition_id:
raise CredentialManagerError("credential_definition_id is not set")

# Credential preview must be present
if not credential_preview:
raise CredentialManagerError("credential_preview is not set")

ledger: BaseLedger = await self.context.inject(BaseLedger)
async with ledger:
schema_id = await ledger.credential_definition_id2schema_id(
credential_definition_id
)

credential_proposal_message = CredentialProposal(
comment=comment,
credential_proposal=credential_preview,
schema_id=schema_id,
cred_def_id=credential_definition_id,
schema_issuer_did=schema_issuer_did,
schema_name=schema_name,
schema_version=schema_version,
cred_def_id=cred_def_id,
issuer_did=issuer_did
)

credential_exchange_record = V10CredentialExchange(
Expand All @@ -137,8 +151,6 @@ async def create_proposal(
initiator=V10CredentialExchange.INITIATOR_SELF,
role=V10CredentialExchange.ROLE_HOLDER,
state=V10CredentialExchange.STATE_PROPOSAL_SENT,
credential_definition_id=credential_definition_id,
schema_id=schema_id,
credential_proposal_dict=credential_proposal_message.serialize(),
auto_offer=auto_offer,
)
Expand All @@ -158,24 +170,14 @@ async def receive_proposal(self) -> V10CredentialExchange:
# go to cred def via ledger to get authoritative schema id
credential_proposal_message = self.context.message
connection_id = self.context.connection_record.connection_id
cred_def_id = credential_proposal_message.cred_def_id
if cred_def_id:
ledger: BaseLedger = await self.context.inject(BaseLedger)
async with ledger:
schema_id = await ledger.credential_definition_id2schema_id(cred_def_id)
else:
raise CredentialManagerError(
"credential definition identifier is not set in proposal"
)

# at this point, cred def and schema still open to potential negotiation
credential_exchange_record = V10CredentialExchange(
connection_id=connection_id,
thread_id=credential_proposal_message._thread_id,
initiator=V10CredentialExchange.INITIATOR_EXTERNAL,
role=V10CredentialExchange.ROLE_ISSUER,
state=V10CredentialExchange.STATE_PROPOSAL_RECEIVED,
credential_definition_id=cred_def_id,
schema_id=schema_id,
credential_proposal_dict=credential_proposal_message.serialize(),
auto_offer=self.context.settings.get(
"debug.auto_respond_credential_proposal"
Expand Down Expand Up @@ -204,32 +206,38 @@ async def create_offer(
A tuple (credential exchange record, credential offer message)
"""
credential_definition_id = credential_exchange_record.credential_definition_id
if credential_exchange_record.credential_proposal_dict:
cred_preview = CredentialProposal.deserialize(
credential_proposal_message = CredentialProposal.deserialize(
credential_exchange_record.credential_proposal_dict
).credential_proposal
)
cred_def_id = await self._match_sent_cred_def_id(
{
t: getattr(credential_proposal_message, t)
for t in CRED_DEF_TAGS if getattr(credential_proposal_message, t)
}
)

cred_preview = credential_proposal_message.credential_proposal
else:
cred_def_id = credential_exchange_record.credential_definition_id
cred_preview = None

async def _create():
async def _create(cred_def_id):
issuer: BaseIssuer = await self.context.inject(BaseIssuer)
return await issuer.create_credential_offer(
credential_definition_id
)
return await issuer.create_credential_offer(cred_def_id)

credential_offer = None
cache_key = f"credential_offer::{credential_definition_id}"
cache_key = f"credential_offer::{cred_def_id}"
cache: BaseCache = await self.context.inject(BaseCache, required=False)
if cache:
async with cache.acquire(cache_key) as entry:
if entry.result:
credential_offer = entry.result
else:
credential_offer = await _create()
credential_offer = await _create(cred_def_id)
await entry.set_result(credential_offer, 3600)
if not credential_offer:
credential_offer = await _create()
credential_offer = await _create(cred_def_id)

credential_offer_message = CredentialOffer(
comment=comment,
Expand Down Expand Up @@ -267,13 +275,15 @@ async def receive_offer(self) -> V10CredentialExchange:

credential_preview = credential_offer_message.credential_preview
indy_offer = credential_offer_message.indy_offer(0)
schema_id = indy_offer["schema_id"]
cred_def_id = indy_offer["cred_def_id"]

if credential_preview:
credential_proposal_dict = CredentialProposal(
comment=credential_offer_message.comment,
credential_proposal=credential_preview,
cred_def_id=indy_offer["cred_def_id"],
schema_id=indy_offer["schema_id"],
schema_id=schema_id,
cred_def_id=cred_def_id,
).serialize()
else:
credential_proposal_dict = None
Expand All @@ -295,13 +305,13 @@ async def receive_offer(self) -> V10CredentialExchange:
thread_id=credential_offer_message._thread_id,
initiator=V10CredentialExchange.INITIATOR_EXTERNAL,
role=V10CredentialExchange.ROLE_HOLDER,
credential_definition_id=indy_offer["cred_def_id"],
schema_id=indy_offer["schema_id"],
credential_proposal_dict=credential_proposal_dict,
)

credential_exchange_record.credential_offer = indy_offer
credential_exchange_record.state = V10CredentialExchange.STATE_OFFER_RECEIVED
credential_exchange_record.schema_id = schema_id
credential_exchange_record.credential_definition_id = cred_def_id

await credential_exchange_record.save(
self.context, reason="receive credential offer"
Expand Down Expand Up @@ -462,7 +472,7 @@ async def issue_credential(
)

credential_exchange_record.state = V10CredentialExchange.STATE_ISSUED
await credential_exchange_record.save(self.context, reason="receive credential")
await credential_exchange_record.save(self.context, reason="issue credential")

credential_message = CredentialIssue(
comment=comment,
Expand Down
Loading

0 comments on commit caf0b39

Please sign in to comment.