Module mydata_did.patched_protocols.issue_credential.v1_0.routes

Credential exchange admin routes.

Expand source code
"""Credential exchange admin routes."""

from aiohttp import web
from aiohttp_apispec import (
    docs,
    match_info_schema,
    querystring_schema,
    request_schema,
    response_schema,
)
from json.decoder import JSONDecodeError
from marshmallow import fields, validate
from aries_cloudagent.connections.models.connection_record import ConnectionRecord
from aries_cloudagent.issuer.base import IssuerError
from aries_cloudagent.ledger.error import LedgerError
from aries_cloudagent.messaging.credential_definitions.util import CRED_DEF_TAGS
from aries_cloudagent.messaging.models.base import BaseModelError, OpenAPISchema
from aries_cloudagent.messaging.valid import (
    INDY_CRED_DEF_ID,
    INDY_CRED_REV_ID,
    INDY_DID,
    INDY_REV_REG_ID,
    INDY_SCHEMA_ID,
    INDY_VERSION,
    NATURAL_NUM,
    UUIDFour,
    UUID4,
)
from aries_cloudagent.storage.error import StorageError, StorageNotFoundError
from aries_cloudagent.wallet.base import BaseWallet
from aries_cloudagent.wallet.error import WalletError
from aries_cloudagent.utils.outofband import serialize_outofband
from aries_cloudagent.utils.tracing import trace_event, get_timer, AdminAPIMessageTracingSchema

from aries_cloudagent.protocols.problem_report.v1_0 import internal_error
from aries_cloudagent.protocols.problem_report.v1_0.message import ProblemReport
from mydata_did.v1_0.messages.problem_report import DataAgreementNegotiationProblemReportReason
from dexa_sdk.managers.ada_manager import V2ADAManager
from dexa_sdk.agreements.da.v1_0.records.da_template_record import DataAgreementTemplateRecord
from dexa_sdk.utils import paginate_records, clean_and_get_field_from_dict
from .manager import CredentialManager, CredentialManagerError
from .message_types import SPEC_URI
from .messages.credential_proposal import CredentialProposal
from .messages.credential_offer import CredentialOfferSchema
from .messages.inner.credential_preview import (
    CredentialPreview,
    CredentialPreviewSchema,
)
from .models.credential_exchange import (
    V10CredentialExchange,
    V10CredentialExchangeSchema,
)

from ....v1_0.decorators.data_agreement_context_decorator import DataAgreementContextDecorator

PAGINATION_PAGE_SIZE = 10


class V10CredentialExchangeListQueryStringSchema(OpenAPISchema):
    """Parameters and validators for credential exchange list query."""

    connection_id = fields.UUID(
        description="Connection identifier",
        required=False,
        example=UUIDFour.EXAMPLE,  # typically but not necessarily a UUID4
    )
    thread_id = fields.UUID(
        description="Thread identifier",
        required=False,
        example=UUIDFour.EXAMPLE,  # typically but not necessarily a UUID4
    )
    role = fields.Str(
        description="Role assigned in credential exchange",
        required=False,
        validate=validate.OneOf(
            [
                getattr(V10CredentialExchange, m)
                for m in vars(V10CredentialExchange)
                if m.startswith("ROLE_")
            ]
        ),
    )
    state = fields.Str(
        description="Credential exchange state",
        required=False,
        validate=validate.OneOf(
            [
                getattr(V10CredentialExchange, m)
                for m in vars(V10CredentialExchange)
                if m.startswith("STATE_")
            ]
        ),
    )

    # Data Agreement template identifier
    template_id = fields.Str(required=False)

    # Page
    page = fields.Int(required=False)

    # Page size
    page_size = fields.Int(required=False)


class V10CredentialExchangeListResultSchema(OpenAPISchema):
    """Result schema for Aries#0036 v1.0 credential exchange query."""

    results = fields.List(
        fields.Nested(V10CredentialExchangeSchema),
        description="Aries#0036 v1.0 credential exchange records",
    )


class V10CredentialStoreRequestSchema(OpenAPISchema):
    """Request schema for sending a credential store admin message."""

    credential_id = fields.Str(required=False)


class V10CredentialCreateSchema(AdminAPIMessageTracingSchema):
    """Base class for request schema for sending credential proposal admin message."""

    cred_def_id = fields.Str(
        description="Credential definition identifier",
        required=False,
        **INDY_CRED_DEF_ID,
    )
    schema_id = fields.Str(
        description="Schema identifier", required=False, **INDY_SCHEMA_ID
    )
    schema_issuer_did = fields.Str(
        description="Schema issuer DID", required=False, **INDY_DID
    )
    schema_name = fields.Str(
        description="Schema name", required=False, example="preferences"
    )
    schema_version = fields.Str(
        description="Schema version", required=False, **INDY_VERSION
    )
    issuer_did = fields.Str(
        description="Credential issuer DID", required=False, **INDY_DID
    )
    auto_remove = fields.Bool(
        description=(
            "Whether to remove the credential exchange record on completion "
            "(overrides --preserve-exchange-records configuration setting)"
        ),
        required=False,
    )
    comment = fields.Str(
        description="Human-readable comment", required=False, allow_none=True
    )
    trace = fields.Bool(
        description="Whether to trace event (default false)",
        required=False,
        example=False,
    )
    credential_proposal = fields.Nested(CredentialPreviewSchema, required=True)


class V10CredentialProposalRequestSchemaBase(AdminAPIMessageTracingSchema):
    """Base class for request schema for sending credential proposal admin message."""

    connection_id = fields.UUID(
        description="Connection identifier",
        required=True,
        example=UUIDFour.EXAMPLE,  # typically but not necessarily a UUID4
    )
    cred_def_id = fields.Str(
        description="Credential definition identifier",
        required=False,
        **INDY_CRED_DEF_ID,
    )
    schema_id = fields.Str(
        description="Schema identifier", required=False, **INDY_SCHEMA_ID
    )
    schema_issuer_did = fields.Str(
        description="Schema issuer DID", required=False, **INDY_DID
    )
    schema_name = fields.Str(
        description="Schema name", required=False, example="preferences"
    )
    schema_version = fields.Str(
        description="Schema version", required=False, **INDY_VERSION
    )
    issuer_did = fields.Str(
        description="Credential issuer DID", required=False, **INDY_DID
    )
    auto_remove = fields.Bool(
        description=(
            "Whether to remove the credential exchange record on completion "
            "(overrides --preserve-exchange-records configuration setting)"
        ),
        required=False,
    )
    comment = fields.Str(
        description="Human-readable comment", required=False, allow_none=True
    )
    trace = fields.Bool(
        description="Whether to trace event (default false)",
        required=False,
        example=False,
    )


class V10CredentialProposalRequestOptSchema(V10CredentialProposalRequestSchemaBase):
    """Request schema for sending credential proposal on optional proposal preview."""

    credential_proposal = fields.Nested(
        CredentialPreviewSchema, required=False)


class V10CredentialProposalRequestMandSchema(V10CredentialProposalRequestSchemaBase):
    """Request schema for sending credential proposal on mandatory proposal preview."""

    credential_proposal = fields.Nested(CredentialPreviewSchema, required=True)


class V10CredentialOfferRequestSchema(AdminAPIMessageTracingSchema):
    """Request schema for sending credential offer admin message."""

    connection_id = fields.UUID(
        description="Connection identifier",
        required=True,
        example=UUIDFour.EXAMPLE,  # typically but not necessarily a UUID4
    )
    cred_def_id = fields.Str(
        description="Credential definition identifier",
        required=False,
        **INDY_CRED_DEF_ID,
    )
    auto_issue = fields.Bool(
        description=(
            "Whether to respond automatically to credential requests, creating "
            "and issuing requested credentials"
        ),
        required=False,
    )
    auto_remove = fields.Bool(
        description=(
            "Whether to remove the credential exchange record on completion "
            "(overrides --preserve-exchange-records configuration setting)"
        ),
        required=False,
        default=True,
    )
    comment = fields.Str(
        description="Human-readable comment", required=False, allow_none=True
    )
    credential_preview = fields.Nested(CredentialPreviewSchema, required=False)
    trace = fields.Bool(
        description="Whether to trace event (default false)",
        required=False,
        example=False,
    )

    # Data agreement template identifier
    template_id = fields.Str(
        description="Data agreement template identifier", required=False, example=UUIDFour.EXAMPLE
    )


class V10CredentialIssueRequestSchema(OpenAPISchema):
    """Request schema for sending credential issue admin message."""

    comment = fields.Str(
        description="Human-readable comment", required=False, allow_none=True
    )


class V10CredentialProblemReportRequestSchema(OpenAPISchema):
    """Request schema for sending problem report."""

    explain_ltxt = fields.Str(required=True)


class V10PublishRevocationsSchema(OpenAPISchema):
    """Request and result schema for revocation publication API call."""

    rrid2crid = fields.Dict(
        required=False,
        # marshmallow 3.0 ignores
        keys=fields.Str(example=INDY_REV_REG_ID["example"]),
        values=fields.List(
            fields.Str(
                description="Credential revocation identifier", **INDY_CRED_REV_ID
            )
        ),
        description="Credential revocation ids by revocation registry id",
    )


class V10ClearPendingRevocationsRequestSchema(OpenAPISchema):
    """Request schema for clear pending revocations API call."""

    purge = fields.Dict(
        required=False,
        # marshmallow 3.0 ignores
        keys=fields.Str(example=INDY_REV_REG_ID["example"]),
        values=fields.List(
            fields.Str(
                description="Credential revocation identifier", **INDY_CRED_REV_ID
            )
        ),
        description=(
            "Credential revocation ids by revocation registry id: omit for all, "
            "specify null or empty list for all pending per revocation registry"
        ),
    )


class RevokeQueryStringSchema(OpenAPISchema):
    """Parameters and validators for revocation request."""

    rev_reg_id = fields.Str(
        description="Revocation registry identifier",
        required=True,
        **INDY_REV_REG_ID,
    )
    cred_rev_id = fields.Int(
        description="Credential revocation identifier",
        required=True,
        **NATURAL_NUM,
    )
    publish = fields.Boolean(
        description=(
            "(True) publish revocation to ledger immediately, or "
            "(False) mark it pending (default value)"
        ),
        required=False,
    )


class CredIdMatchInfoSchema(OpenAPISchema):
    """Path parameters and validators for request taking credential id."""

    credential_id = fields.Str(
        description="Credential identifier", required=True, example=UUIDFour.EXAMPLE
    )


class CredExIdMatchInfoSchema(OpenAPISchema):
    """Path parameters and validators for request taking credential exchange id."""

    cred_ex_id = fields.Str(
        description="Credential exchange identifier", required=True, **UUID4
    )


class DataAgreementBoundCredentialOfferMatchInfoSchema(OpenAPISchema):
    """Path parameters and validators for request taking data agreement bound credential offer id."""

    connection_id = fields.UUID(
        description="Connection identifier",
        required=True,
        example=UUIDFour.EXAMPLE,  # typically but not necessarily a UUID4
    )

    # Data agreement identifier
    data_agreement_id = fields.Str(
        required=True,
        description="The unique identifier for the data agreement.",
        example=UUIDFour.EXAMPLE
    )


class SendDataAgreementNegotiationProblemReportRequestSchema(OpenAPISchema):
    """Request schema for sending problem report."""

    explain = fields.Str(description="Describe the problem", required=True,
                         example="Data agreement context decorator not found in the didcomm message.")
    problem_code = fields.Str(description="Problem code", required=True,
                              example=DataAgreementNegotiationProblemReportReason.DATA_AGREEMENT_CONTEXT_INVALID.value)


@docs(tags=["issue-credential"], summary="Fetch all credential exchange records")
@querystring_schema(V10CredentialExchangeListQueryStringSchema)
@response_schema(V10CredentialExchangeListResultSchema(), 200)
async def credential_exchange_list(request: web.BaseRequest):
    """
    Request handler for searching connection records.

    Args:
        request: aiohttp request object

    Returns:
        The connection list response

    """
    context = request.app["request_context"]
    tag_filter = {}
    if "thread_id" in request.query and request.query["thread_id"] != "":
        tag_filter["thread_id"] = request.query["thread_id"]
    post_filter = {
        k: request.query[k]
        for k in ("connection_id", "role", "state", "template_id")
        if request.query.get(k, "") != ""
    }

    page = clean_and_get_field_from_dict(request.query, "page")
    page = int(page) if page is not None else page
    page_size = clean_and_get_field_from_dict(request.query, "page_size")
    page_size = int(page_size) if page_size is not None else page_size

    try:
        records = await V10CredentialExchange.query(context, tag_filter, post_filter)

        # Pagination result.
        pagination_result = paginate_records(
            records,
            page if page else 1,
            page_size if page_size else 10
        )
    except (StorageError, BaseModelError) as err:
        raise web.HTTPBadRequest(reason=err.roll_up) from err

    return web.json_response(pagination_result._asdict())


@docs(tags=["issue-credential"], summary="Fetch a single credential exchange record")
@match_info_schema(CredExIdMatchInfoSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_retrieve(request: web.BaseRequest):
    """
    Request handler for fetching single connection record.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    credential_exchange_id = request.match_info["cred_ex_id"]
    cred_ex_record = None
    try:
        cred_ex_record = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )
        result = cred_ex_record.serialize()
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err
    except (BaseModelError, StorageError) as err:
        await internal_error(err, web.HTTPBadRequest, cred_ex_record, outbound_handler)

    return web.json_response(result)


@docs(
    tags=["issue-credential"],
    summary="Send holder a credential, automating entire flow",
)
@request_schema(V10CredentialCreateSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_create(request: web.BaseRequest):
    """
    Request handler for creating a credential from attr values.

    The internal credential record will be created without the credential
    being sent to any connection. This can be used in conjunction with
    the `oob` protocols to bind messages to an out of band message.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]

    body = await request.json()

    comment = body.get("comment")
    preview_spec = body.get("credential_proposal")
    if not preview_spec:
        raise web.HTTPBadRequest(reason="credential_proposal must be provided")
    auto_remove = body.get("auto_remove")
    trace_msg = body.get("trace")

    try:
        preview = CredentialPreview.deserialize(preview_spec)

        credential_proposal = CredentialProposal(
            comment=comment,
            credential_proposal=preview,
            **{t: body.get(t) for t in CRED_DEF_TAGS if body.get(t)},
        )
        credential_proposal.assign_trace_decorator(
            context.settings,
            trace_msg,
        )

        trace_event(
            context.settings,
            credential_proposal,
            outcome="credential_exchange_create.START",
        )

        credential_manager = CredentialManager(context)

        (
            credential_exchange_record,
            credential_offer_message,
        ) = await credential_manager.prepare_send(
            None,
            credential_proposal=credential_proposal,
            auto_remove=auto_remove,
        )
    except (StorageError, BaseModelError) as err:
        raise web.HTTPBadRequest(reason=err.roll_up) from err

    trace_event(
        context.settings,
        credential_offer_message,
        outcome="credential_exchange_create.END",
        perf_counter=r_time,
    )

    return web.json_response(credential_exchange_record.serialize())


@docs(
    tags=["issue-credential"],
    summary="Send holder a credential, automating entire flow",
)
@request_schema(V10CredentialProposalRequestMandSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_send(request: web.BaseRequest):
    """
    Request handler for sending credential from issuer to holder from attr values.

    If both issuer and holder are configured for automatic responses, the operation
    ultimately results in credential issue; otherwise, the result waits on the first
    response not automated; the credential exchange record retains state regardless.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    body = await request.json()

    comment = body.get("comment")
    connection_id = body.get("connection_id")
    preview_spec = body.get("credential_proposal")
    if not preview_spec:
        raise web.HTTPBadRequest(reason="credential_proposal must be provided")
    auto_remove = body.get("auto_remove")
    trace_msg = body.get("trace")

    connection_record = None
    cred_ex_record = None
    try:
        preview = CredentialPreview.deserialize(preview_spec)
        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(
                reason=f"Connection {connection_id} not ready")

        credential_proposal = CredentialProposal(
            comment=comment,
            credential_proposal=preview,
            **{t: body.get(t) for t in CRED_DEF_TAGS if body.get(t)},
        )
        credential_proposal.assign_trace_decorator(
            context.settings,
            trace_msg,
        )

        trace_event(
            context.settings,
            credential_proposal,
            outcome="credential_exchange_send.START",
        )

        credential_manager = CredentialManager(context)
        (
            cred_ex_record,
            credential_offer_message,
        ) = await credential_manager.prepare_send(
            connection_id,
            credential_proposal=credential_proposal,
            auto_remove=auto_remove,
        )
        result = cred_ex_record.serialize()
    except (StorageError, BaseModelError, CredentialManagerError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    await outbound_handler(
        credential_offer_message, connection_id=cred_ex_record.connection_id
    )

    trace_event(
        context.settings,
        credential_offer_message,
        outcome="credential_exchange_send.END",
        perf_counter=r_time,
    )

    return web.json_response(result)


@docs(tags=["issue-credential"], summary="Send issuer a credential proposal")
@request_schema(V10CredentialProposalRequestOptSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_send_proposal(request: web.BaseRequest):
    """
    Request handler for sending credential proposal.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    body = await request.json()

    connection_id = body.get("connection_id")
    comment = body.get("comment")
    preview_spec = body.get("credential_proposal")
    auto_remove = body.get("auto_remove")
    trace_msg = body.get("trace")

    connection_record = None
    cred_ex_record = None
    try:
        preview = CredentialPreview.deserialize(
            preview_spec) if preview_spec else None
        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(
                reason=f"Connection {connection_id} not ready")

        credential_manager = CredentialManager(context)
        cred_ex_record = await credential_manager.create_proposal(
            connection_id,
            comment=comment,
            credential_preview=preview,
            auto_remove=auto_remove,
            trace=trace_msg,
            **{t: body.get(t) for t in CRED_DEF_TAGS if body.get(t)},
        )

        credential_proposal = CredentialProposal.deserialize(
            cred_ex_record.credential_proposal_dict
        )
        result = cred_ex_record.serialize()
    except (BaseModelError, StorageError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    await outbound_handler(
        credential_proposal,
        connection_id=connection_id,
    )

    trace_event(
        context.settings,
        credential_proposal,
        outcome="credential_exchange_send_proposal.END",
        perf_counter=r_time,
    )

    return web.json_response(result)


async def _create_free_offer(
    context,
    cred_def_id: str,
    connection_id: str = None,
    auto_issue: bool = False,
    auto_remove: bool = False,
    preview_spec: dict = None,
    comment: str = None,
    trace_msg: bool = None,
):
    """Create a credential offer and related exchange record."""

    credential_preview = CredentialPreview.deserialize(preview_spec)
    credential_proposal = CredentialProposal(
        comment=comment,
        credential_proposal=credential_preview,
        cred_def_id=cred_def_id,
    )
    credential_proposal.assign_trace_decorator(
        context.settings,
        trace_msg,
    )
    credential_proposal_dict = credential_proposal.serialize()

    cred_ex_record = V10CredentialExchange(
        connection_id=connection_id,
        initiator=V10CredentialExchange.INITIATOR_SELF,
        credential_definition_id=cred_def_id,
        credential_proposal_dict=credential_proposal_dict,
        auto_issue=auto_issue,
        auto_remove=auto_remove,
        trace=trace_msg,
    )

    credential_manager = CredentialManager(context)

    (
        cred_ex_record,
        credential_offer_message,
    ) = await credential_manager.create_offer(cred_ex_record, comment=comment)

    return (cred_ex_record, credential_offer_message)


@docs(
    tags=["issue-credential"],
    summary="Create a credential offer, independent of any proposal",
)
@request_schema(V10CredentialOfferRequestSchema())
@response_schema(CredentialOfferSchema(), 200)
async def credential_exchange_create_free_offer(request: web.BaseRequest):
    """
    Request handler for creating free credential offer.

    Unlike with `send-offer`, this credential exchange is not tied to a specific
    connection. It must be dispatched out-of-band by the controller.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    body = await request.json()

    cred_def_id = body.get("cred_def_id")
    if not cred_def_id:
        raise web.HTTPBadRequest(reason="cred_def_id is required")

    auto_issue = body.get(
        "auto_issue", context.settings.get(
            "debug.auto_respond_credential_request")
    )
    auto_remove = body.get("auto_remove")
    comment = body.get("comment")
    preview_spec = body.get("credential_preview")
    if not preview_spec:
        raise web.HTTPBadRequest(reason=("Missing credential_preview"))

    connection_id = body.get("connection_id")
    trace_msg = body.get("trace")

    wallet: BaseWallet = await context.inject(BaseWallet)
    if connection_id:
        try:
            connection_record = await ConnectionRecord.retrieve_by_id(
                context, connection_id
            )
            conn_did = await wallet.get_local_did(connection_record.my_did)
        except (WalletError, StorageError) as err:
            raise web.HTTPBadRequest(reason=err.roll_up) from err
    else:
        conn_did = await wallet.get_public_did()
        if not conn_did:
            raise web.HTTPBadRequest(
                reason=f"Wallet '{wallet.name}' has no public DID")
        connection_id = None

    did_info = await wallet.get_public_did()
    endpoint = did_info.metadata.get(
        "endpoint", context.settings.get("default_endpoint")
    )
    if not endpoint:
        raise web.HTTPBadRequest(
            reason="An endpoint for the public DID is required")

    cred_ex_record = None
    try:
        (cred_ex_record, credential_offer_message,) = await _create_free_offer(
            context,
            cred_def_id,
            connection_id,
            auto_issue,
            auto_remove,
            preview_spec,
            comment,
            trace_msg,
        )

        trace_event(
            context.settings,
            credential_offer_message,
            outcome="credential_exchange_create_free_offer.END",
            perf_counter=r_time,
        )

        oob_url = serialize_outofband(
            credential_offer_message, conn_did, endpoint)
        result = cred_ex_record.serialize()
    except (BaseModelError, CredentialManagerError, LedgerError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    response = {"record": result, "oob_url": oob_url}
    return web.json_response(response)


@docs(
    tags=["issue-credential"],
    summary="Send holder a credential offer, independent of any proposal",
)
@request_schema(V10CredentialOfferRequestSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_send_free_offer(request: web.BaseRequest):
    """
    Request handler for sending free credential offer.

    An issuer initiates a such a credential offer, free from any
    holder-initiated corresponding credential proposal with preview.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """

    print("Patched route for sending credential offers.")

    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    body = await request.json()

    connection_id = body.get("connection_id")
    cred_def_id = body.get("cred_def_id")
    template_id = body.get("template_id")

    # Fetch data agreement template record
    template_record: DataAgreementTemplateRecord = None
    if template_id:
        template_record: DataAgreementTemplateRecord = \
            await DataAgreementTemplateRecord.latest_published_template_by_id(
                context,
                template_id
            )

        # Validate agreement method of use
        if template_record.method_of_use != DataAgreementTemplateRecord.METHOD_OF_USE_DATA_SOURCE:
            raise web.HTTPBadRequest(
                reason="Data agreement method of use must be data-source."
            )

        # Replace cred def id from template if available.
        cred_def_id = template_record.cred_def_id if template_record.cred_def_id else cred_def_id

    # Check if either cred def id or data agreement id is present.
    if not cred_def_id and not template_id:
        raise web.HTTPBadRequest(
            reason="Either cred def id or data agreement template id is required."
        )

    # Data agreement model
    da_model = template_record.data_agreement_model

    # Third party data sharing or not.
    third_party_data_sharing = da_model.data_policy.third_party_data_sharing

    auto_issue = body.get(
        "auto_issue", context.settings.get(
            "debug.auto_respond_credential_request")
    )

    auto_remove = body.get("auto_remove")
    comment = body.get("comment")

    # If not third party data sharing,
    # then use credential preview from request body
    if not third_party_data_sharing:
        preview_spec = body.get("credential_preview")
        if not preview_spec:
            raise web.HTTPBadRequest(reason=("Missing credential_preview"))
    else:
        # If third party data sharing,
        # then construct the credential dynamically from personal data.
        # Values are empty strings.
        preview_spec_attrs = []
        for pd in da_model.personal_data:
            preview_spec_attrs.append({
                "name": pd.attribute_name,
                "value": " "
            })
        preview_spec = {
            "@type": "issue-credential/1.0/credential-preview",
            "attributes": preview_spec_attrs
        }

    trace_msg = body.get("trace")

    cred_ex_record = None
    connection_record = None
    try:
        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(
                reason=f"Connection {connection_id} not ready")

        (cred_ex_record, credential_offer_message,) = await _create_free_offer(
            context,
            cred_def_id,
            connection_id,
            auto_issue,
            auto_remove,
            preview_spec,
            comment,
            trace_msg,
        )
        result = cred_ex_record.serialize()
    except (
        StorageNotFoundError,
        BaseModelError,
        CredentialManagerError,
        LedgerError,
    ) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    # Initialise MyData DID Manager
    manager: V2ADAManager = V2ADAManager(context=context)

    # Construct data agreement offer message.
    data_agreement_offer_message = \
        await manager.build_data_agreement_offer_for_credential_exchange(
            template_id=template_id,
            cred_ex_record=cred_ex_record,
            connection_record=connection_record,

        )

    # Add data agreement context decorator
    credential_offer_message._decorators["data-agreement-context"] =\
        DataAgreementContextDecorator(
        message_type="protocol",
        message=data_agreement_offer_message.serialize()
    )

    cred_ex_record.credential_offer_dict = credential_offer_message.serialize()
    cred_ex_record.template_id = template_id
    await cred_ex_record.save(context)

    result = cred_ex_record.serialize()

    await outbound_handler(credential_offer_message, connection_id=connection_id)

    trace_event(
        context.settings,
        credential_offer_message,
        outcome="credential_exchange_send_free_offer.END",
        perf_counter=r_time,
    )

    return web.json_response(result)


@docs(
    tags=["issue-credential"],
    summary="Send holder a credential offer in reference to a proposal with preview",
)
@match_info_schema(CredExIdMatchInfoSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_send_bound_offer(request: web.BaseRequest):
    """
    Request handler for sending bound credential offer.

    A holder initiates this sequence with a credential proposal; this message
    responds with an offer bound to the proposal.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    credential_exchange_id = request.match_info["cred_ex_id"]
    try:
        cred_ex_record = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err

    connection_record = None
    connection_id = cred_ex_record.connection_id
    try:
        if cred_ex_record.state != (
            V10CredentialExchange.STATE_PROPOSAL_RECEIVED
        ):  # check state here: manager call creates free offers too
            raise CredentialManagerError(
                f"Credential exchange {cred_ex_record.credential_exchange_id} "
                f"in {cred_ex_record.state} state "
                f"(must be {V10CredentialExchange.STATE_PROPOSAL_RECEIVED})"
            )

        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready")

        credential_manager = CredentialManager(context)
        (
            cred_ex_record,
            credential_offer_message,
        ) = await credential_manager.create_offer(cred_ex_record, comment=None)

        result = cred_ex_record.serialize()
    except (StorageError, BaseModelError, CredentialManagerError, LedgerError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    await outbound_handler(credential_offer_message, connection_id=connection_id)

    trace_event(
        context.settings,
        credential_offer_message,
        outcome="credential_exchange_send_bound_offer.END",
        perf_counter=r_time,
    )

    return web.json_response(result)


@docs(tags=["issue-credential"], summary="Send issuer a credential request")
@match_info_schema(CredExIdMatchInfoSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_send_request(request: web.BaseRequest):
    """
    Request handler for sending credential request.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    credential_exchange_id = request.match_info["cred_ex_id"]
    try:
        cred_ex_record = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err
    connection_id = cred_ex_record.connection_id

    connection_record = None
    try:
        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(
                reason=f"Connection {connection_id} not ready")

        credential_manager = CredentialManager(context)
        (
            cred_ex_record,
            credential_request_message,
        ) = await credential_manager.create_request(
            cred_ex_record, connection_record.my_did
        )

        # Initialize ADA manager.
        manager = V2ADAManager(context)

        # Create data agreement accept message.
        accept_message = await manager.build_data_agreement_accept_for_data_ex_record(
            connection_record,
            cred_ex_record
        )

        # Update credential request message with data agreement context decorator
        credential_request_message._decorators["data-agreement-context"] = \
            DataAgreementContextDecorator(
            message_type="protocol",
            message=accept_message.serialize()
        )

        result = cred_ex_record.serialize()
    except (StorageError, CredentialManagerError, BaseModelError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    await outbound_handler(credential_request_message, connection_id=connection_id)

    trace_event(
        context.settings,
        credential_request_message,
        outcome="credential_exchange_send_request.END",
        perf_counter=r_time,
    )

    return web.json_response(result)


@docs(tags=["issue-credential"], summary="Send holder a credential")
@match_info_schema(CredExIdMatchInfoSchema())
@request_schema(V10CredentialIssueRequestSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_issue(request: web.BaseRequest):
    """
    Request handler for sending credential.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    body = await request.json()
    comment = body.get("comment")

    credential_exchange_id = request.match_info["cred_ex_id"]
    try:
        cred_ex_record = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err
    connection_id = cred_ex_record.connection_id

    connection_record = None
    try:
        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(
                reason=f"Connection {connection_id} not ready")

        credential_manager = CredentialManager(context)
        (
            cred_ex_record,
            credential_issue_message,
        ) = await credential_manager.issue_credential(cred_ex_record, comment=comment)

        result = cred_ex_record.serialize()
    except (BaseModelError, CredentialManagerError, IssuerError, StorageError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    await outbound_handler(credential_issue_message, connection_id=connection_id)

    trace_event(
        context.settings,
        credential_issue_message,
        outcome="credential_exchange_issue.END",
        perf_counter=r_time,
    )

    return web.json_response(result)


@docs(tags=["issue-credential"], summary="Store a received credential")
@match_info_schema(CredExIdMatchInfoSchema())
@request_schema(V10CredentialStoreRequestSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_store(request: web.BaseRequest):
    """
    Request handler for storing credential.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    try:
        body = await request.json() or {}
        credential_id = body.get("credential_id")
    except JSONDecodeError:
        credential_id = None

    credential_exchange_id = request.match_info["cred_ex_id"]
    try:
        cred_ex_record = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err

    connection_record = None
    connection_id = cred_ex_record.connection_id
    try:
        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(
                reason=f"Connection {connection_id} not ready")

        credential_manager = CredentialManager(context)
        (
            cred_ex_record,
            credential_stored_message,
        ) = await credential_manager.store_credential(cred_ex_record, credential_id)

        result = cred_ex_record.serialize()
    except (StorageError, CredentialManagerError, BaseModelError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    await outbound_handler(credential_stored_message, connection_id=connection_id)

    trace_event(
        context.settings,
        credential_stored_message,
        outcome="credential_exchange_store.END",
        perf_counter=r_time,
    )

    return web.json_response(result)


@docs(
    tags=["issue-credential"], summary="Remove an existing credential exchange record"
)
@match_info_schema(CredExIdMatchInfoSchema())
async def credential_exchange_remove(request: web.BaseRequest):
    """
    Request handler for removing a credential exchange record.

    Args:
        request: aiohttp request object

    """
    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    credential_exchange_id = request.match_info["cred_ex_id"]
    cred_ex_record = None
    try:
        cred_ex_record: V10CredentialExchange = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )

        await cred_ex_record.delete_record(context)

        # Initialize ADA manager
        manager = V2ADAManager(context)

        # Delete data agreement instance
        await manager.delete_da_instance_by_data_ex_id(cred_ex_record.credential_exchange_id)

    except StorageNotFoundError as err:
        await internal_error(err, web.HTTPNotFound, cred_ex_record, outbound_handler)
    except StorageError as err:
        await internal_error(err, web.HTTPBadRequest, cred_ex_record, outbound_handler)

    return web.json_response({})


@docs(
    tags=["issue-credential"], summary="Send a problem report for credential exchange"
)
@match_info_schema(CredExIdMatchInfoSchema())
@request_schema(V10CredentialProblemReportRequestSchema())
async def credential_exchange_problem_report(request: web.BaseRequest):
    """
    Request handler for sending problem report.

    Args:
        request: aiohttp request object

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    credential_exchange_id = request.match_info["cred_ex_id"]
    body = await request.json()

    try:
        cred_ex_record = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err

    error_result = ProblemReport(explain_ltxt=body["explain_ltxt"])
    error_result.assign_thread_id(cred_ex_record.thread_id)

    await outbound_handler(error_result, connection_id=cred_ex_record.connection_id)

    trace_event(
        context.settings,
        error_result,
        outcome="credential_exchange_problem_report.END",
        perf_counter=r_time,
    )

    return web.json_response({})


async def register(app: web.Application):
    """Register routes."""

    app.add_routes(
        [
            web.get(
                "/issue-credential/records", credential_exchange_list, allow_head=False
            ),
            web.get(
                "/issue-credential/records/{cred_ex_id}",
                credential_exchange_retrieve,
                allow_head=False,
            ),
            web.post("/issue-credential/create", credential_exchange_create),
            web.post("/issue-credential/send", credential_exchange_send),
            web.post(
                "/issue-credential/send-proposal", credential_exchange_send_proposal
            ),
            web.post(
                "/issue-credential/send-offer", credential_exchange_send_free_offer
            ),
            web.post(
                "/issue-credential/records/{cred_ex_id}/send-offer",
                credential_exchange_send_bound_offer,
            ),
            web.post(
                "/issue-credential/records/{cred_ex_id}/send-request",
                credential_exchange_send_request,
            ),
            web.post(
                "/issue-credential/records/{cred_ex_id}/issue",
                credential_exchange_issue,
            ),
            web.post(
                "/issue-credential/records/{cred_ex_id}/store",
                credential_exchange_store,
            ),
            web.post(
                "/issue-credential/records/{cred_ex_id}/problem-report",
                credential_exchange_problem_report,
            ),
            web.delete(
                "/issue-credential/records/{cred_ex_id}",
                credential_exchange_remove,
            ),
        ]
    )


def post_process_routes(app: web.Application):
    """Amend swagger API."""

    # Add top-level tags description
    if "tags" not in app._state["swagger_dict"]:
        app._state["swagger_dict"]["tags"] = []
    app._state["swagger_dict"]["tags"].append(
        {
            "name": "issue-credential",
            "description": "Credential issue, revocation",
            "externalDocs": {"description": "Specification", "url": SPEC_URI},
        }
    )

Functions

async def credential_exchange_create(request: aiohttp.web_request.BaseRequest)

Request handler for creating a credential from attr values.

The internal credential record will be created without the credential being sent to any connection. This can be used in conjunction with the oob protocols to bind messages to an out of band message.

Args

request
aiohttp request object

Returns

The credential exchange record

Expand source code
@docs(
    tags=["issue-credential"],
    summary="Send holder a credential, automating entire flow",
)
@request_schema(V10CredentialCreateSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_create(request: web.BaseRequest):
    """
    Request handler for creating a credential from attr values.

    The internal credential record will be created without the credential
    being sent to any connection. This can be used in conjunction with
    the `oob` protocols to bind messages to an out of band message.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]

    body = await request.json()

    comment = body.get("comment")
    preview_spec = body.get("credential_proposal")
    if not preview_spec:
        raise web.HTTPBadRequest(reason="credential_proposal must be provided")
    auto_remove = body.get("auto_remove")
    trace_msg = body.get("trace")

    try:
        preview = CredentialPreview.deserialize(preview_spec)

        credential_proposal = CredentialProposal(
            comment=comment,
            credential_proposal=preview,
            **{t: body.get(t) for t in CRED_DEF_TAGS if body.get(t)},
        )
        credential_proposal.assign_trace_decorator(
            context.settings,
            trace_msg,
        )

        trace_event(
            context.settings,
            credential_proposal,
            outcome="credential_exchange_create.START",
        )

        credential_manager = CredentialManager(context)

        (
            credential_exchange_record,
            credential_offer_message,
        ) = await credential_manager.prepare_send(
            None,
            credential_proposal=credential_proposal,
            auto_remove=auto_remove,
        )
    except (StorageError, BaseModelError) as err:
        raise web.HTTPBadRequest(reason=err.roll_up) from err

    trace_event(
        context.settings,
        credential_offer_message,
        outcome="credential_exchange_create.END",
        perf_counter=r_time,
    )

    return web.json_response(credential_exchange_record.serialize())
async def credential_exchange_create_free_offer(request: aiohttp.web_request.BaseRequest)

Request handler for creating free credential offer.

Unlike with send-offer, this credential exchange is not tied to a specific connection. It must be dispatched out-of-band by the controller.

Args

request
aiohttp request object

Returns

The credential exchange record

Expand source code
@docs(
    tags=["issue-credential"],
    summary="Create a credential offer, independent of any proposal",
)
@request_schema(V10CredentialOfferRequestSchema())
@response_schema(CredentialOfferSchema(), 200)
async def credential_exchange_create_free_offer(request: web.BaseRequest):
    """
    Request handler for creating free credential offer.

    Unlike with `send-offer`, this credential exchange is not tied to a specific
    connection. It must be dispatched out-of-band by the controller.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    body = await request.json()

    cred_def_id = body.get("cred_def_id")
    if not cred_def_id:
        raise web.HTTPBadRequest(reason="cred_def_id is required")

    auto_issue = body.get(
        "auto_issue", context.settings.get(
            "debug.auto_respond_credential_request")
    )
    auto_remove = body.get("auto_remove")
    comment = body.get("comment")
    preview_spec = body.get("credential_preview")
    if not preview_spec:
        raise web.HTTPBadRequest(reason=("Missing credential_preview"))

    connection_id = body.get("connection_id")
    trace_msg = body.get("trace")

    wallet: BaseWallet = await context.inject(BaseWallet)
    if connection_id:
        try:
            connection_record = await ConnectionRecord.retrieve_by_id(
                context, connection_id
            )
            conn_did = await wallet.get_local_did(connection_record.my_did)
        except (WalletError, StorageError) as err:
            raise web.HTTPBadRequest(reason=err.roll_up) from err
    else:
        conn_did = await wallet.get_public_did()
        if not conn_did:
            raise web.HTTPBadRequest(
                reason=f"Wallet '{wallet.name}' has no public DID")
        connection_id = None

    did_info = await wallet.get_public_did()
    endpoint = did_info.metadata.get(
        "endpoint", context.settings.get("default_endpoint")
    )
    if not endpoint:
        raise web.HTTPBadRequest(
            reason="An endpoint for the public DID is required")

    cred_ex_record = None
    try:
        (cred_ex_record, credential_offer_message,) = await _create_free_offer(
            context,
            cred_def_id,
            connection_id,
            auto_issue,
            auto_remove,
            preview_spec,
            comment,
            trace_msg,
        )

        trace_event(
            context.settings,
            credential_offer_message,
            outcome="credential_exchange_create_free_offer.END",
            perf_counter=r_time,
        )

        oob_url = serialize_outofband(
            credential_offer_message, conn_did, endpoint)
        result = cred_ex_record.serialize()
    except (BaseModelError, CredentialManagerError, LedgerError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    response = {"record": result, "oob_url": oob_url}
    return web.json_response(response)
async def credential_exchange_issue(request: aiohttp.web_request.BaseRequest)

Request handler for sending credential.

Args

request
aiohttp request object

Returns

The credential exchange record

Expand source code
@docs(tags=["issue-credential"], summary="Send holder a credential")
@match_info_schema(CredExIdMatchInfoSchema())
@request_schema(V10CredentialIssueRequestSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_issue(request: web.BaseRequest):
    """
    Request handler for sending credential.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    body = await request.json()
    comment = body.get("comment")

    credential_exchange_id = request.match_info["cred_ex_id"]
    try:
        cred_ex_record = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err
    connection_id = cred_ex_record.connection_id

    connection_record = None
    try:
        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(
                reason=f"Connection {connection_id} not ready")

        credential_manager = CredentialManager(context)
        (
            cred_ex_record,
            credential_issue_message,
        ) = await credential_manager.issue_credential(cred_ex_record, comment=comment)

        result = cred_ex_record.serialize()
    except (BaseModelError, CredentialManagerError, IssuerError, StorageError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    await outbound_handler(credential_issue_message, connection_id=connection_id)

    trace_event(
        context.settings,
        credential_issue_message,
        outcome="credential_exchange_issue.END",
        perf_counter=r_time,
    )

    return web.json_response(result)
async def credential_exchange_list(request: aiohttp.web_request.BaseRequest)

Request handler for searching connection records.

Args

request
aiohttp request object

Returns

The connection list response

Expand source code
@docs(tags=["issue-credential"], summary="Fetch all credential exchange records")
@querystring_schema(V10CredentialExchangeListQueryStringSchema)
@response_schema(V10CredentialExchangeListResultSchema(), 200)
async def credential_exchange_list(request: web.BaseRequest):
    """
    Request handler for searching connection records.

    Args:
        request: aiohttp request object

    Returns:
        The connection list response

    """
    context = request.app["request_context"]
    tag_filter = {}
    if "thread_id" in request.query and request.query["thread_id"] != "":
        tag_filter["thread_id"] = request.query["thread_id"]
    post_filter = {
        k: request.query[k]
        for k in ("connection_id", "role", "state", "template_id")
        if request.query.get(k, "") != ""
    }

    page = clean_and_get_field_from_dict(request.query, "page")
    page = int(page) if page is not None else page
    page_size = clean_and_get_field_from_dict(request.query, "page_size")
    page_size = int(page_size) if page_size is not None else page_size

    try:
        records = await V10CredentialExchange.query(context, tag_filter, post_filter)

        # Pagination result.
        pagination_result = paginate_records(
            records,
            page if page else 1,
            page_size if page_size else 10
        )
    except (StorageError, BaseModelError) as err:
        raise web.HTTPBadRequest(reason=err.roll_up) from err

    return web.json_response(pagination_result._asdict())
async def credential_exchange_problem_report(request: aiohttp.web_request.BaseRequest)

Request handler for sending problem report.

Args

request
aiohttp request object
Expand source code
@docs(
    tags=["issue-credential"], summary="Send a problem report for credential exchange"
)
@match_info_schema(CredExIdMatchInfoSchema())
@request_schema(V10CredentialProblemReportRequestSchema())
async def credential_exchange_problem_report(request: web.BaseRequest):
    """
    Request handler for sending problem report.

    Args:
        request: aiohttp request object

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    credential_exchange_id = request.match_info["cred_ex_id"]
    body = await request.json()

    try:
        cred_ex_record = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err

    error_result = ProblemReport(explain_ltxt=body["explain_ltxt"])
    error_result.assign_thread_id(cred_ex_record.thread_id)

    await outbound_handler(error_result, connection_id=cred_ex_record.connection_id)

    trace_event(
        context.settings,
        error_result,
        outcome="credential_exchange_problem_report.END",
        perf_counter=r_time,
    )

    return web.json_response({})
async def credential_exchange_remove(request: aiohttp.web_request.BaseRequest)

Request handler for removing a credential exchange record.

Args

request
aiohttp request object
Expand source code
@docs(
    tags=["issue-credential"], summary="Remove an existing credential exchange record"
)
@match_info_schema(CredExIdMatchInfoSchema())
async def credential_exchange_remove(request: web.BaseRequest):
    """
    Request handler for removing a credential exchange record.

    Args:
        request: aiohttp request object

    """
    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    credential_exchange_id = request.match_info["cred_ex_id"]
    cred_ex_record = None
    try:
        cred_ex_record: V10CredentialExchange = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )

        await cred_ex_record.delete_record(context)

        # Initialize ADA manager
        manager = V2ADAManager(context)

        # Delete data agreement instance
        await manager.delete_da_instance_by_data_ex_id(cred_ex_record.credential_exchange_id)

    except StorageNotFoundError as err:
        await internal_error(err, web.HTTPNotFound, cred_ex_record, outbound_handler)
    except StorageError as err:
        await internal_error(err, web.HTTPBadRequest, cred_ex_record, outbound_handler)

    return web.json_response({})
async def credential_exchange_retrieve(request: aiohttp.web_request.BaseRequest)

Request handler for fetching single connection record.

Args

request
aiohttp request object

Returns

The credential exchange record

Expand source code
@docs(tags=["issue-credential"], summary="Fetch a single credential exchange record")
@match_info_schema(CredExIdMatchInfoSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_retrieve(request: web.BaseRequest):
    """
    Request handler for fetching single connection record.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    credential_exchange_id = request.match_info["cred_ex_id"]
    cred_ex_record = None
    try:
        cred_ex_record = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )
        result = cred_ex_record.serialize()
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err
    except (BaseModelError, StorageError) as err:
        await internal_error(err, web.HTTPBadRequest, cred_ex_record, outbound_handler)

    return web.json_response(result)
async def credential_exchange_send(request: aiohttp.web_request.BaseRequest)

Request handler for sending credential from issuer to holder from attr values.

If both issuer and holder are configured for automatic responses, the operation ultimately results in credential issue; otherwise, the result waits on the first response not automated; the credential exchange record retains state regardless.

Args

request
aiohttp request object

Returns

The credential exchange record

Expand source code
@docs(
    tags=["issue-credential"],
    summary="Send holder a credential, automating entire flow",
)
@request_schema(V10CredentialProposalRequestMandSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_send(request: web.BaseRequest):
    """
    Request handler for sending credential from issuer to holder from attr values.

    If both issuer and holder are configured for automatic responses, the operation
    ultimately results in credential issue; otherwise, the result waits on the first
    response not automated; the credential exchange record retains state regardless.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    body = await request.json()

    comment = body.get("comment")
    connection_id = body.get("connection_id")
    preview_spec = body.get("credential_proposal")
    if not preview_spec:
        raise web.HTTPBadRequest(reason="credential_proposal must be provided")
    auto_remove = body.get("auto_remove")
    trace_msg = body.get("trace")

    connection_record = None
    cred_ex_record = None
    try:
        preview = CredentialPreview.deserialize(preview_spec)
        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(
                reason=f"Connection {connection_id} not ready")

        credential_proposal = CredentialProposal(
            comment=comment,
            credential_proposal=preview,
            **{t: body.get(t) for t in CRED_DEF_TAGS if body.get(t)},
        )
        credential_proposal.assign_trace_decorator(
            context.settings,
            trace_msg,
        )

        trace_event(
            context.settings,
            credential_proposal,
            outcome="credential_exchange_send.START",
        )

        credential_manager = CredentialManager(context)
        (
            cred_ex_record,
            credential_offer_message,
        ) = await credential_manager.prepare_send(
            connection_id,
            credential_proposal=credential_proposal,
            auto_remove=auto_remove,
        )
        result = cred_ex_record.serialize()
    except (StorageError, BaseModelError, CredentialManagerError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    await outbound_handler(
        credential_offer_message, connection_id=cred_ex_record.connection_id
    )

    trace_event(
        context.settings,
        credential_offer_message,
        outcome="credential_exchange_send.END",
        perf_counter=r_time,
    )

    return web.json_response(result)
async def credential_exchange_send_bound_offer(request: aiohttp.web_request.BaseRequest)

Request handler for sending bound credential offer.

A holder initiates this sequence with a credential proposal; this message responds with an offer bound to the proposal.

Args

request
aiohttp request object

Returns

The credential exchange record

Expand source code
@docs(
    tags=["issue-credential"],
    summary="Send holder a credential offer in reference to a proposal with preview",
)
@match_info_schema(CredExIdMatchInfoSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_send_bound_offer(request: web.BaseRequest):
    """
    Request handler for sending bound credential offer.

    A holder initiates this sequence with a credential proposal; this message
    responds with an offer bound to the proposal.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    credential_exchange_id = request.match_info["cred_ex_id"]
    try:
        cred_ex_record = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err

    connection_record = None
    connection_id = cred_ex_record.connection_id
    try:
        if cred_ex_record.state != (
            V10CredentialExchange.STATE_PROPOSAL_RECEIVED
        ):  # check state here: manager call creates free offers too
            raise CredentialManagerError(
                f"Credential exchange {cred_ex_record.credential_exchange_id} "
                f"in {cred_ex_record.state} state "
                f"(must be {V10CredentialExchange.STATE_PROPOSAL_RECEIVED})"
            )

        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready")

        credential_manager = CredentialManager(context)
        (
            cred_ex_record,
            credential_offer_message,
        ) = await credential_manager.create_offer(cred_ex_record, comment=None)

        result = cred_ex_record.serialize()
    except (StorageError, BaseModelError, CredentialManagerError, LedgerError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    await outbound_handler(credential_offer_message, connection_id=connection_id)

    trace_event(
        context.settings,
        credential_offer_message,
        outcome="credential_exchange_send_bound_offer.END",
        perf_counter=r_time,
    )

    return web.json_response(result)
async def credential_exchange_send_free_offer(request: aiohttp.web_request.BaseRequest)

Request handler for sending free credential offer.

An issuer initiates a such a credential offer, free from any holder-initiated corresponding credential proposal with preview.

Args

request
aiohttp request object

Returns

The credential exchange record

Expand source code
@docs(
    tags=["issue-credential"],
    summary="Send holder a credential offer, independent of any proposal",
)
@request_schema(V10CredentialOfferRequestSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_send_free_offer(request: web.BaseRequest):
    """
    Request handler for sending free credential offer.

    An issuer initiates a such a credential offer, free from any
    holder-initiated corresponding credential proposal with preview.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """

    print("Patched route for sending credential offers.")

    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    body = await request.json()

    connection_id = body.get("connection_id")
    cred_def_id = body.get("cred_def_id")
    template_id = body.get("template_id")

    # Fetch data agreement template record
    template_record: DataAgreementTemplateRecord = None
    if template_id:
        template_record: DataAgreementTemplateRecord = \
            await DataAgreementTemplateRecord.latest_published_template_by_id(
                context,
                template_id
            )

        # Validate agreement method of use
        if template_record.method_of_use != DataAgreementTemplateRecord.METHOD_OF_USE_DATA_SOURCE:
            raise web.HTTPBadRequest(
                reason="Data agreement method of use must be data-source."
            )

        # Replace cred def id from template if available.
        cred_def_id = template_record.cred_def_id if template_record.cred_def_id else cred_def_id

    # Check if either cred def id or data agreement id is present.
    if not cred_def_id and not template_id:
        raise web.HTTPBadRequest(
            reason="Either cred def id or data agreement template id is required."
        )

    # Data agreement model
    da_model = template_record.data_agreement_model

    # Third party data sharing or not.
    third_party_data_sharing = da_model.data_policy.third_party_data_sharing

    auto_issue = body.get(
        "auto_issue", context.settings.get(
            "debug.auto_respond_credential_request")
    )

    auto_remove = body.get("auto_remove")
    comment = body.get("comment")

    # If not third party data sharing,
    # then use credential preview from request body
    if not third_party_data_sharing:
        preview_spec = body.get("credential_preview")
        if not preview_spec:
            raise web.HTTPBadRequest(reason=("Missing credential_preview"))
    else:
        # If third party data sharing,
        # then construct the credential dynamically from personal data.
        # Values are empty strings.
        preview_spec_attrs = []
        for pd in da_model.personal_data:
            preview_spec_attrs.append({
                "name": pd.attribute_name,
                "value": " "
            })
        preview_spec = {
            "@type": "issue-credential/1.0/credential-preview",
            "attributes": preview_spec_attrs
        }

    trace_msg = body.get("trace")

    cred_ex_record = None
    connection_record = None
    try:
        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(
                reason=f"Connection {connection_id} not ready")

        (cred_ex_record, credential_offer_message,) = await _create_free_offer(
            context,
            cred_def_id,
            connection_id,
            auto_issue,
            auto_remove,
            preview_spec,
            comment,
            trace_msg,
        )
        result = cred_ex_record.serialize()
    except (
        StorageNotFoundError,
        BaseModelError,
        CredentialManagerError,
        LedgerError,
    ) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    # Initialise MyData DID Manager
    manager: V2ADAManager = V2ADAManager(context=context)

    # Construct data agreement offer message.
    data_agreement_offer_message = \
        await manager.build_data_agreement_offer_for_credential_exchange(
            template_id=template_id,
            cred_ex_record=cred_ex_record,
            connection_record=connection_record,

        )

    # Add data agreement context decorator
    credential_offer_message._decorators["data-agreement-context"] =\
        DataAgreementContextDecorator(
        message_type="protocol",
        message=data_agreement_offer_message.serialize()
    )

    cred_ex_record.credential_offer_dict = credential_offer_message.serialize()
    cred_ex_record.template_id = template_id
    await cred_ex_record.save(context)

    result = cred_ex_record.serialize()

    await outbound_handler(credential_offer_message, connection_id=connection_id)

    trace_event(
        context.settings,
        credential_offer_message,
        outcome="credential_exchange_send_free_offer.END",
        perf_counter=r_time,
    )

    return web.json_response(result)
async def credential_exchange_send_proposal(request: aiohttp.web_request.BaseRequest)

Request handler for sending credential proposal.

Args

request
aiohttp request object

Returns

The credential exchange record

Expand source code
@docs(tags=["issue-credential"], summary="Send issuer a credential proposal")
@request_schema(V10CredentialProposalRequestOptSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_send_proposal(request: web.BaseRequest):
    """
    Request handler for sending credential proposal.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    body = await request.json()

    connection_id = body.get("connection_id")
    comment = body.get("comment")
    preview_spec = body.get("credential_proposal")
    auto_remove = body.get("auto_remove")
    trace_msg = body.get("trace")

    connection_record = None
    cred_ex_record = None
    try:
        preview = CredentialPreview.deserialize(
            preview_spec) if preview_spec else None
        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(
                reason=f"Connection {connection_id} not ready")

        credential_manager = CredentialManager(context)
        cred_ex_record = await credential_manager.create_proposal(
            connection_id,
            comment=comment,
            credential_preview=preview,
            auto_remove=auto_remove,
            trace=trace_msg,
            **{t: body.get(t) for t in CRED_DEF_TAGS if body.get(t)},
        )

        credential_proposal = CredentialProposal.deserialize(
            cred_ex_record.credential_proposal_dict
        )
        result = cred_ex_record.serialize()
    except (BaseModelError, StorageError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    await outbound_handler(
        credential_proposal,
        connection_id=connection_id,
    )

    trace_event(
        context.settings,
        credential_proposal,
        outcome="credential_exchange_send_proposal.END",
        perf_counter=r_time,
    )

    return web.json_response(result)
async def credential_exchange_send_request(request: aiohttp.web_request.BaseRequest)

Request handler for sending credential request.

Args

request
aiohttp request object

Returns

The credential exchange record

Expand source code
@docs(tags=["issue-credential"], summary="Send issuer a credential request")
@match_info_schema(CredExIdMatchInfoSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_send_request(request: web.BaseRequest):
    """
    Request handler for sending credential request.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    credential_exchange_id = request.match_info["cred_ex_id"]
    try:
        cred_ex_record = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err
    connection_id = cred_ex_record.connection_id

    connection_record = None
    try:
        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(
                reason=f"Connection {connection_id} not ready")

        credential_manager = CredentialManager(context)
        (
            cred_ex_record,
            credential_request_message,
        ) = await credential_manager.create_request(
            cred_ex_record, connection_record.my_did
        )

        # Initialize ADA manager.
        manager = V2ADAManager(context)

        # Create data agreement accept message.
        accept_message = await manager.build_data_agreement_accept_for_data_ex_record(
            connection_record,
            cred_ex_record
        )

        # Update credential request message with data agreement context decorator
        credential_request_message._decorators["data-agreement-context"] = \
            DataAgreementContextDecorator(
            message_type="protocol",
            message=accept_message.serialize()
        )

        result = cred_ex_record.serialize()
    except (StorageError, CredentialManagerError, BaseModelError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    await outbound_handler(credential_request_message, connection_id=connection_id)

    trace_event(
        context.settings,
        credential_request_message,
        outcome="credential_exchange_send_request.END",
        perf_counter=r_time,
    )

    return web.json_response(result)
async def credential_exchange_store(request: aiohttp.web_request.BaseRequest)

Request handler for storing credential.

Args

request
aiohttp request object

Returns

The credential exchange record

Expand source code
@docs(tags=["issue-credential"], summary="Store a received credential")
@match_info_schema(CredExIdMatchInfoSchema())
@request_schema(V10CredentialStoreRequestSchema())
@response_schema(V10CredentialExchangeSchema(), 200)
async def credential_exchange_store(request: web.BaseRequest):
    """
    Request handler for storing credential.

    Args:
        request: aiohttp request object

    Returns:
        The credential exchange record

    """
    r_time = get_timer()

    context = request.app["request_context"]
    outbound_handler = request.app["outbound_message_router"]

    try:
        body = await request.json() or {}
        credential_id = body.get("credential_id")
    except JSONDecodeError:
        credential_id = None

    credential_exchange_id = request.match_info["cred_ex_id"]
    try:
        cred_ex_record = await V10CredentialExchange.retrieve_by_id(
            context, credential_exchange_id
        )
    except StorageNotFoundError as err:
        raise web.HTTPNotFound(reason=err.roll_up) from err

    connection_record = None
    connection_id = cred_ex_record.connection_id
    try:
        connection_record = await ConnectionRecord.retrieve_by_id(
            context, connection_id
        )
        if not connection_record.is_ready:
            raise web.HTTPForbidden(
                reason=f"Connection {connection_id} not ready")

        credential_manager = CredentialManager(context)
        (
            cred_ex_record,
            credential_stored_message,
        ) = await credential_manager.store_credential(cred_ex_record, credential_id)

        result = cred_ex_record.serialize()
    except (StorageError, CredentialManagerError, BaseModelError) as err:
        await internal_error(
            err,
            web.HTTPBadRequest,
            cred_ex_record or connection_record,
            outbound_handler,
        )

    await outbound_handler(credential_stored_message, connection_id=connection_id)

    trace_event(
        context.settings,
        credential_stored_message,
        outcome="credential_exchange_store.END",
        perf_counter=r_time,
    )

    return web.json_response(result)
def post_process_routes(app: aiohttp.web_app.Application)

Amend swagger API.

Expand source code
def post_process_routes(app: web.Application):
    """Amend swagger API."""

    # Add top-level tags description
    if "tags" not in app._state["swagger_dict"]:
        app._state["swagger_dict"]["tags"] = []
    app._state["swagger_dict"]["tags"].append(
        {
            "name": "issue-credential",
            "description": "Credential issue, revocation",
            "externalDocs": {"description": "Specification", "url": SPEC_URI},
        }
    )
async def register(app: aiohttp.web_app.Application)

Register routes.

Expand source code
async def register(app: web.Application):
    """Register routes."""

    app.add_routes(
        [
            web.get(
                "/issue-credential/records", credential_exchange_list, allow_head=False
            ),
            web.get(
                "/issue-credential/records/{cred_ex_id}",
                credential_exchange_retrieve,
                allow_head=False,
            ),
            web.post("/issue-credential/create", credential_exchange_create),
            web.post("/issue-credential/send", credential_exchange_send),
            web.post(
                "/issue-credential/send-proposal", credential_exchange_send_proposal
            ),
            web.post(
                "/issue-credential/send-offer", credential_exchange_send_free_offer
            ),
            web.post(
                "/issue-credential/records/{cred_ex_id}/send-offer",
                credential_exchange_send_bound_offer,
            ),
            web.post(
                "/issue-credential/records/{cred_ex_id}/send-request",
                credential_exchange_send_request,
            ),
            web.post(
                "/issue-credential/records/{cred_ex_id}/issue",
                credential_exchange_issue,
            ),
            web.post(
                "/issue-credential/records/{cred_ex_id}/store",
                credential_exchange_store,
            ),
            web.post(
                "/issue-credential/records/{cred_ex_id}/problem-report",
                credential_exchange_problem_report,
            ),
            web.delete(
                "/issue-credential/records/{cred_ex_id}",
                credential_exchange_remove,
            ),
        ]
    )

Classes

class CredExIdMatchInfoSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Path parameters and validators for request taking credential exchange id.

Expand source code
class CredExIdMatchInfoSchema(OpenAPISchema):
    """Path parameters and validators for request taking credential exchange id."""

    cred_ex_id = fields.Str(
        description="Credential exchange identifier", required=True, **UUID4
    )

Ancestors

  • aries_cloudagent.messaging.models.base.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class CredIdMatchInfoSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Path parameters and validators for request taking credential id.

Expand source code
class CredIdMatchInfoSchema(OpenAPISchema):
    """Path parameters and validators for request taking credential id."""

    credential_id = fields.Str(
        description="Credential identifier", required=True, example=UUIDFour.EXAMPLE
    )

Ancestors

  • aries_cloudagent.messaging.models.base.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class DataAgreementBoundCredentialOfferMatchInfoSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Path parameters and validators for request taking data agreement bound credential offer id.

Expand source code
class DataAgreementBoundCredentialOfferMatchInfoSchema(OpenAPISchema):
    """Path parameters and validators for request taking data agreement bound credential offer id."""

    connection_id = fields.UUID(
        description="Connection identifier",
        required=True,
        example=UUIDFour.EXAMPLE,  # typically but not necessarily a UUID4
    )

    # Data agreement identifier
    data_agreement_id = fields.Str(
        required=True,
        description="The unique identifier for the data agreement.",
        example=UUIDFour.EXAMPLE
    )

Ancestors

  • aries_cloudagent.messaging.models.base.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class RevokeQueryStringSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Parameters and validators for revocation request.

Expand source code
class RevokeQueryStringSchema(OpenAPISchema):
    """Parameters and validators for revocation request."""

    rev_reg_id = fields.Str(
        description="Revocation registry identifier",
        required=True,
        **INDY_REV_REG_ID,
    )
    cred_rev_id = fields.Int(
        description="Credential revocation identifier",
        required=True,
        **NATURAL_NUM,
    )
    publish = fields.Boolean(
        description=(
            "(True) publish revocation to ledger immediately, or "
            "(False) mark it pending (default value)"
        ),
        required=False,
    )

Ancestors

  • aries_cloudagent.messaging.models.base.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class SendDataAgreementNegotiationProblemReportRequestSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Request schema for sending problem report.

Expand source code
class SendDataAgreementNegotiationProblemReportRequestSchema(OpenAPISchema):
    """Request schema for sending problem report."""

    explain = fields.Str(description="Describe the problem", required=True,
                         example="Data agreement context decorator not found in the didcomm message.")
    problem_code = fields.Str(description="Problem code", required=True,
                              example=DataAgreementNegotiationProblemReportReason.DATA_AGREEMENT_CONTEXT_INVALID.value)

Ancestors

  • aries_cloudagent.messaging.models.base.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class V10ClearPendingRevocationsRequestSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Request schema for clear pending revocations API call.

Expand source code
class V10ClearPendingRevocationsRequestSchema(OpenAPISchema):
    """Request schema for clear pending revocations API call."""

    purge = fields.Dict(
        required=False,
        # marshmallow 3.0 ignores
        keys=fields.Str(example=INDY_REV_REG_ID["example"]),
        values=fields.List(
            fields.Str(
                description="Credential revocation identifier", **INDY_CRED_REV_ID
            )
        ),
        description=(
            "Credential revocation ids by revocation registry id: omit for all, "
            "specify null or empty list for all pending per revocation registry"
        ),
    )

Ancestors

  • aries_cloudagent.messaging.models.base.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class V10CredentialCreateSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Base class for request schema for sending credential proposal admin message.

Expand source code
class V10CredentialCreateSchema(AdminAPIMessageTracingSchema):
    """Base class for request schema for sending credential proposal admin message."""

    cred_def_id = fields.Str(
        description="Credential definition identifier",
        required=False,
        **INDY_CRED_DEF_ID,
    )
    schema_id = fields.Str(
        description="Schema identifier", required=False, **INDY_SCHEMA_ID
    )
    schema_issuer_did = fields.Str(
        description="Schema issuer DID", required=False, **INDY_DID
    )
    schema_name = fields.Str(
        description="Schema name", required=False, example="preferences"
    )
    schema_version = fields.Str(
        description="Schema version", required=False, **INDY_VERSION
    )
    issuer_did = fields.Str(
        description="Credential issuer DID", required=False, **INDY_DID
    )
    auto_remove = fields.Bool(
        description=(
            "Whether to remove the credential exchange record on completion "
            "(overrides --preserve-exchange-records configuration setting)"
        ),
        required=False,
    )
    comment = fields.Str(
        description="Human-readable comment", required=False, allow_none=True
    )
    trace = fields.Bool(
        description="Whether to trace event (default false)",
        required=False,
        example=False,
    )
    credential_proposal = fields.Nested(CredentialPreviewSchema, required=True)

Ancestors

  • aries_cloudagent.utils.tracing.AdminAPIMessageTracingSchema
  • aries_cloudagent.messaging.models.openapi.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class V10CredentialExchangeListQueryStringSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Parameters and validators for credential exchange list query.

Expand source code
class V10CredentialExchangeListQueryStringSchema(OpenAPISchema):
    """Parameters and validators for credential exchange list query."""

    connection_id = fields.UUID(
        description="Connection identifier",
        required=False,
        example=UUIDFour.EXAMPLE,  # typically but not necessarily a UUID4
    )
    thread_id = fields.UUID(
        description="Thread identifier",
        required=False,
        example=UUIDFour.EXAMPLE,  # typically but not necessarily a UUID4
    )
    role = fields.Str(
        description="Role assigned in credential exchange",
        required=False,
        validate=validate.OneOf(
            [
                getattr(V10CredentialExchange, m)
                for m in vars(V10CredentialExchange)
                if m.startswith("ROLE_")
            ]
        ),
    )
    state = fields.Str(
        description="Credential exchange state",
        required=False,
        validate=validate.OneOf(
            [
                getattr(V10CredentialExchange, m)
                for m in vars(V10CredentialExchange)
                if m.startswith("STATE_")
            ]
        ),
    )

    # Data Agreement template identifier
    template_id = fields.Str(required=False)

    # Page
    page = fields.Int(required=False)

    # Page size
    page_size = fields.Int(required=False)

Ancestors

  • aries_cloudagent.messaging.models.base.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class V10CredentialExchangeListResultSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Result schema for Aries#0036 v1.0 credential exchange query.

Expand source code
class V10CredentialExchangeListResultSchema(OpenAPISchema):
    """Result schema for Aries#0036 v1.0 credential exchange query."""

    results = fields.List(
        fields.Nested(V10CredentialExchangeSchema),
        description="Aries#0036 v1.0 credential exchange records",
    )

Ancestors

  • aries_cloudagent.messaging.models.base.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class V10CredentialIssueRequestSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Request schema for sending credential issue admin message.

Expand source code
class V10CredentialIssueRequestSchema(OpenAPISchema):
    """Request schema for sending credential issue admin message."""

    comment = fields.Str(
        description="Human-readable comment", required=False, allow_none=True
    )

Ancestors

  • aries_cloudagent.messaging.models.base.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class V10CredentialOfferRequestSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Request schema for sending credential offer admin message.

Expand source code
class V10CredentialOfferRequestSchema(AdminAPIMessageTracingSchema):
    """Request schema for sending credential offer admin message."""

    connection_id = fields.UUID(
        description="Connection identifier",
        required=True,
        example=UUIDFour.EXAMPLE,  # typically but not necessarily a UUID4
    )
    cred_def_id = fields.Str(
        description="Credential definition identifier",
        required=False,
        **INDY_CRED_DEF_ID,
    )
    auto_issue = fields.Bool(
        description=(
            "Whether to respond automatically to credential requests, creating "
            "and issuing requested credentials"
        ),
        required=False,
    )
    auto_remove = fields.Bool(
        description=(
            "Whether to remove the credential exchange record on completion "
            "(overrides --preserve-exchange-records configuration setting)"
        ),
        required=False,
        default=True,
    )
    comment = fields.Str(
        description="Human-readable comment", required=False, allow_none=True
    )
    credential_preview = fields.Nested(CredentialPreviewSchema, required=False)
    trace = fields.Bool(
        description="Whether to trace event (default false)",
        required=False,
        example=False,
    )

    # Data agreement template identifier
    template_id = fields.Str(
        description="Data agreement template identifier", required=False, example=UUIDFour.EXAMPLE
    )

Ancestors

  • aries_cloudagent.utils.tracing.AdminAPIMessageTracingSchema
  • aries_cloudagent.messaging.models.openapi.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class V10CredentialProblemReportRequestSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Request schema for sending problem report.

Expand source code
class V10CredentialProblemReportRequestSchema(OpenAPISchema):
    """Request schema for sending problem report."""

    explain_ltxt = fields.Str(required=True)

Ancestors

  • aries_cloudagent.messaging.models.base.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class V10CredentialProposalRequestMandSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Request schema for sending credential proposal on mandatory proposal preview.

Expand source code
class V10CredentialProposalRequestMandSchema(V10CredentialProposalRequestSchemaBase):
    """Request schema for sending credential proposal on mandatory proposal preview."""

    credential_proposal = fields.Nested(CredentialPreviewSchema, required=True)

Ancestors

  • V10CredentialProposalRequestSchemaBase
  • aries_cloudagent.utils.tracing.AdminAPIMessageTracingSchema
  • aries_cloudagent.messaging.models.openapi.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class V10CredentialProposalRequestOptSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Request schema for sending credential proposal on optional proposal preview.

Expand source code
class V10CredentialProposalRequestOptSchema(V10CredentialProposalRequestSchemaBase):
    """Request schema for sending credential proposal on optional proposal preview."""

    credential_proposal = fields.Nested(
        CredentialPreviewSchema, required=False)

Ancestors

  • V10CredentialProposalRequestSchemaBase
  • aries_cloudagent.utils.tracing.AdminAPIMessageTracingSchema
  • aries_cloudagent.messaging.models.openapi.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class V10CredentialProposalRequestSchemaBase (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Base class for request schema for sending credential proposal admin message.

Expand source code
class V10CredentialProposalRequestSchemaBase(AdminAPIMessageTracingSchema):
    """Base class for request schema for sending credential proposal admin message."""

    connection_id = fields.UUID(
        description="Connection identifier",
        required=True,
        example=UUIDFour.EXAMPLE,  # typically but not necessarily a UUID4
    )
    cred_def_id = fields.Str(
        description="Credential definition identifier",
        required=False,
        **INDY_CRED_DEF_ID,
    )
    schema_id = fields.Str(
        description="Schema identifier", required=False, **INDY_SCHEMA_ID
    )
    schema_issuer_did = fields.Str(
        description="Schema issuer DID", required=False, **INDY_DID
    )
    schema_name = fields.Str(
        description="Schema name", required=False, example="preferences"
    )
    schema_version = fields.Str(
        description="Schema version", required=False, **INDY_VERSION
    )
    issuer_did = fields.Str(
        description="Credential issuer DID", required=False, **INDY_DID
    )
    auto_remove = fields.Bool(
        description=(
            "Whether to remove the credential exchange record on completion "
            "(overrides --preserve-exchange-records configuration setting)"
        ),
        required=False,
    )
    comment = fields.Str(
        description="Human-readable comment", required=False, allow_none=True
    )
    trace = fields.Bool(
        description="Whether to trace event (default false)",
        required=False,
        example=False,
    )

Ancestors

  • aries_cloudagent.utils.tracing.AdminAPIMessageTracingSchema
  • aries_cloudagent.messaging.models.openapi.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Subclasses

Class variables

var opts
class V10CredentialStoreRequestSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Request schema for sending a credential store admin message.

Expand source code
class V10CredentialStoreRequestSchema(OpenAPISchema):
    """Request schema for sending a credential store admin message."""

    credential_id = fields.Str(required=False)

Ancestors

  • aries_cloudagent.messaging.models.base.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts
class V10PublishRevocationsSchema (*, only: Union[Sequence[str], Set[str]] = None, exclude: Union[Sequence[str], Set[str]] = (), many: bool = False, context: Dict[~KT, ~VT] = None, load_only: Union[Sequence[str], Set[str]] = (), dump_only: Union[Sequence[str], Set[str]] = (), partial: Union[bool, Sequence[str], Set[str]] = False, unknown: str = None)

Request and result schema for revocation publication API call.

Expand source code
class V10PublishRevocationsSchema(OpenAPISchema):
    """Request and result schema for revocation publication API call."""

    rrid2crid = fields.Dict(
        required=False,
        # marshmallow 3.0 ignores
        keys=fields.Str(example=INDY_REV_REG_ID["example"]),
        values=fields.List(
            fields.Str(
                description="Credential revocation identifier", **INDY_CRED_REV_ID
            )
        ),
        description="Credential revocation ids by revocation registry id",
    )

Ancestors

  • aries_cloudagent.messaging.models.base.OpenAPISchema
  • marshmallow.schema.Schema
  • marshmallow.base.SchemaABC

Class variables

var opts