Skip to content

Commit

Permalink
Merge pull request #235 from bcgov/feature/auto-respond-presentation-…
Browse files Browse the repository at this point in the history
…proposal

holder send proposal, verifier auto-respond-presentation-proposal
  • Loading branch information
usingtechnology authored Jul 28, 2022
2 parents e0fd1d5 + 75cb4e5 commit 27331c1
Show file tree
Hide file tree
Showing 19 changed files with 538 additions and 176 deletions.
4 changes: 2 additions & 2 deletions charts/traction/templates/traction_api_deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ spec:
httpGet:
path: /
port: 5000
initialDelaySeconds: 60
initialDelaySeconds: 90
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: 5000
initialDelaySeconds: 60
initialDelaySeconds: 90
periodSeconds: 30
env:
- name: TRACTION_API_ADMIN_USER
Expand Down
2 changes: 1 addition & 1 deletion charts/traction/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ acapy:
autoRespondCredentialProposal: false
autoRespondCredentialOffer: false
autoRespondCredentialRequest: true
autoRespondPresentationProposal: false
autoRespondPresentationProposal: true
autoRespondPresentationRequest: false
autoStoreCredential: true
autoVerifyPresentation: true
Expand Down
2 changes: 1 addition & 1 deletion scripts/acapy-static-args.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ auto-respond-messages: false
auto-respond-credential-proposal: false
auto-respond-credential-offer: false
auto-respond-credential-request: true
auto-respond-presentation-proposal: false
auto-respond-presentation-proposal: true
auto-respond-presentation-request: false
auto-store-credential: true
auto-verify-presentation: true
Expand Down
31 changes: 31 additions & 0 deletions services/traction/api/endpoints/models/v1/holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class HolderCredentialStatusType(str, Enum):
class HolderPresentationStatusType(str, Enum):
# pending, nothing happened yet
pending = "Pending"
proposol_sent = "Proposal Sent"
# offer received, waiting for action
request_received = "Request Received"
presentation_sent = "Presentation Sent"
Expand Down Expand Up @@ -217,3 +218,33 @@ class RejectPresentationRequestPayload(BaseModel):
alias: str | None = None
external_reference_id: str | None = None
tags: List[str] | None = []


class PresentationProposalAttribute(BaseModel):
name: str | None = None
cred_def_id: str | None = None
mime_type: str | None = None
referent: str | None = None
value: str | None = None


class PresentationProposalPredicate(BaseModel):
name: str | None = None
predicate: str | None = None
threshold: int | None = None
cred_def_id: str | None = None


class PresentationProposal(BaseModel):
attributes: List[PresentationProposalAttribute] | None = []
predicates: List[PresentationProposalPredicate] | None = []


class HolderSendProposalPayload(BaseModel):
contact_id: UUID
comment: str | None = None
presentation_proposal: PresentationProposal


class HolderSendProposalResponse(GetResponse[HolderPresentationItem]):
pass
3 changes: 2 additions & 1 deletion services/traction/api/endpoints/models/v1/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class VerifierPresentationStatusType(str, Enum):
RECEIVED = "received" # Verification has been received but not verified
VERIFIED = "verified" # Verified and proven to be correct
REJECTED = "rejected" # request was rejected/abandoned
PROPOSED = "proposed" # holder is proposing a verification
ERROR = "Error" # why is this capitalized?


Expand Down Expand Up @@ -84,7 +85,7 @@ class VerifierPresentationItem(
):
verifier_presentation_id: UUID
contact_id: UUID
proof_request: ProofRequest
# proof_request: ProofRequest
name: str
version: str
comment: Optional[str]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
HolderPresentationStatusType,
HolderPresentationListParameters,
HolderPresentationListResponse,
HolderSendProposalPayload,
HolderSendProposalResponse,
)
from api.endpoints.routes.v1.link_utils import build_list_links

Expand Down Expand Up @@ -59,3 +61,26 @@ async def list_holder_presentations(
return HolderPresentationListResponse(
items=items, count=len(items), total=total_count, links=links
)


@router.post("/send-proposal", status_code=status.HTTP_200_OK)
async def send_proposal(
payload: HolderSendProposalPayload,
save_in_traction: bool | None = False,
) -> HolderSendProposalResponse:
"""Holder - Send Proposal
This allows a holder to send a verifier a proposal of proof.
Refer to the following RFC for more information on the flow:
https://github.com/hyperledger/aries-rfcs/tree/main/features/0037-present-proof#propose-presentation
For the content structure the presentation_proposal attributes and predicates:
https://github.com/hyperledger/aries-rfcs/tree/main/features/0037-present-proof#presentation-preview
"""
wallet_id = get_from_context("TENANT_WALLET_ID")
tenant_id = get_from_context("TENANT_ID")

item = await holder_service.send_proposal(tenant_id, wallet_id, payload=payload)
links = [] # TODO
return HolderSendProposalResponse(item=item, links=links)
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ async def new_verifier_presentation(
task_payload = {
"verifier_presentation_id": item.verifier_presentation_id,
"contact_id": item.contact_id,
"proof_request": item.proof_request,
"proof_request": payload.proof_request,
}
await SendPresentProofTask.assign(tenant_id, wallet_id, task_payload)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import uuid

from api.core.profile import Profile
from api.db.models.v1.contact import Contact
from api.db.models.v1.holder import HolderPresentation
from api.db.session import async_session
from api.endpoints.models.credentials import PresentationRoleType
Expand All @@ -13,18 +10,16 @@
DefaultPresentationRequestProtocol,
)

new_request_states = [
AcapyPresentProofStateType.REQUEST_RECEIVED,
]


class DefaultHolderPresentationProtocol(DefaultPresentationRequestProtocol):
def __init__(self):
super().__init__()
self.role = PresentationRoleType.prover

def get_presentation_exchange_id(self, payload: dict) -> str:
try:
return payload["presentation_exchange_id"]
except KeyError:
return None

async def get_holder_presentation(
self, profile: Profile, payload: dict
) -> HolderPresentation:
Expand All @@ -37,20 +32,10 @@ async def get_holder_presentation(
except NotFoundError:
return None

async def get_contact(self, profile: Profile, payload: dict) -> Contact:
connection_id = uuid.UUID(payload["connection_id"])
try:
async with async_session() as db:
return await Contact.get_by_connection_id(
db, profile.tenant_id, connection_id=connection_id
)
except NotFoundError:
return None

async def approve_for_processing(self, profile: Profile, payload: dict) -> bool:
self.logger.info("> approve_for_processing()")
holder_presentation = await self.get_holder_presentation(profile, payload)
is_new_request = payload["state"] == AcapyPresentProofStateType.REQUEST_RECEIVED
is_new_request = payload["state"] in new_request_states
is_existing_item = holder_presentation is not None
approved = is_new_request or is_existing_item
self.logger.info(f"is_new_request={is_new_request}")
Expand All @@ -66,18 +51,28 @@ async def before_any(self, profile: Profile, payload: dict):
self.logger.debug("holder presentation exists, proceed with status update")
values = {"state": payload["state"]}

proposal_states = [
AcapyPresentProofStateType.PROPOSAL_SENT,
]
sent_states = [AcapyPresentProofStateType.PRESENTATION_SENT]

accepted_states = [
AcapyPresentProofStateType.PRESENTATION_ACKED,
]

if payload["state"] in proposal_states:
values["status"] = HolderPresentationStatusType.proposol_sent

if payload["state"] in sent_states:
values["status"] = HolderPresentationStatusType.presentation_sent

if payload["state"] in accepted_states:
values["status"] = HolderPresentationStatusType.presentation_acked

if "error_msg" in payload:
values["error_status_detail"] = payload["error_msg"]
values["status"] = HolderPresentationStatusType.error

self.logger.debug(f"update values = {values}")
await HolderPresentation.update_by_id(
item_id=item.holder_presentation_id, values=values
Expand All @@ -87,37 +82,46 @@ async def before_any(self, profile: Profile, payload: dict):

async def on_request_received(self, profile: Profile, payload: dict):
self.logger.info("> on_request_received()")
# create a new holder credential!
contact = await self.get_contact(profile, payload)
if contact:
offer = HolderPresentation(
tenant_id=profile.tenant_id,
contact_id=contact.contact_id,
status=HolderPresentationStatusType.request_received,
state=payload["state"],
thread_id=payload["thread_id"],
presentation_exchange_id=payload["presentation_exchange_id"],
connection_id=payload["connection_id"],
)
async with async_session() as db:
db.add(offer)
await db.commit()

# TODO: create payload and send notification to tenant.
else:
if not contact:
self.logger.warning(
f"No contact found for connection_id<{payload['connection_id']}>, cannot create holder presentation." # noqa: E501
)
self.logger.info("< on_request_received()")

async def on_presentation_sent(self, profile: Profile, payload: dict):
self.logger.info("> on_presentation_sent()")
self.logger.info("< on_presentation_sent()")
else:
holder_presentation = await self.get_holder_presentation(profile, payload)
if not holder_presentation:
# create a new holder credential!
offer = HolderPresentation(
tenant_id=profile.tenant_id,
contact_id=contact.contact_id,
status=HolderPresentationStatusType.request_received,
state=payload["state"],
thread_id=payload["thread_id"],
presentation_exchange_id=payload["presentation_exchange_id"],
connection_id=payload["connection_id"],
)
async with async_session() as db:
db.add(offer)
await db.commit()

async def on_presentation_acked(self, profile: Profile, payload: dict):
self.logger.info("> on_presentation_acked()")
self.logger.info("< on_presentation_acked()")
# TODO: create payload and send notification to tenant.
self.logger.info("< on_request_received()")

async def on_abandoned(self, profile: Profile, payload: dict):
self.logger.info("> on_abandoned()")
self.logger.info("< on_abandoned()")
if "error_msg" in payload:
self.logger.info(f"payload error_msg = {payload['error_msg']}")
if str(payload["error_msg"]).startswith("created problem report:"):
holder_presentation = await self.get_holder_presentation(
profile, payload
)
self.logger.info("holder request abandoned, request rejected.")
values = {
"state": AcapyPresentProofStateType.ABANDONED,
"status": HolderPresentationStatusType.rejected,
}
self.logger.debug(f"updating holder presentation = {values}")
async with async_session() as db:
await HolderPresentation.update_by_id(
holder_presentation.holder_presentation_id, values
)
await db.commit()
4 changes: 4 additions & 0 deletions services/traction/api/protocols/v1/verifier/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from .verifier_presentation_proposol_handler import VerifierPresentationProposolHandler
from .verifier_presentation_request_handler import VerifierPresentationRequestHandler
from .verifier_presentation_request_status_updater import (
VerifierPresentationRequestStatusUpdater,
)


def subscribe_present_proof_protocol_listeners():
VerifierPresentationProposolHandler()
VerifierPresentationRequestStatusUpdater()
VerifierPresentationRequestHandler()
Loading

0 comments on commit 27331c1

Please sign in to comment.