Esempio n. 1
0
async def create_tenant(
        body: CreateTenantRequest,
        aries_controller: AcaPyClient = Depends(multitenant_admin),
        auth: AcaPyAuth = Depends(acapy_auth),
) -> Tenant:
    """Create a new tenant."""
    if auth.role == Role.MEMBER_ADMIN and body.roles and len(body.roles) > 0:
        raise CloudApiException(
            "Not allowed to provide roles for member tenants, only ecosystem tenants",
            403,
        )

    tenant_role = auth.role.agent_type.tenant_role

    if not tenant_role:
        raise CloudApiException(
            f"Unable to create tenant for agent type ${auth.role}, as it has no admin rights over tenants"
        )

    wallet_response = await aries_controller.multitenancy.create_wallet(
        body=CreateWalletRequest(
            image_url=body.image_url,
            key_management_mode="managed",
            label=body.name,
            wallet_key=token_urlsafe(48),
            wallet_name=get_wallet_name_for_role(tenant_role),
            wallet_type="indy",
        ))

    if auth.role == Role.ECOSYSTEM_ADMIN and body.roles and len(
            body.roles) > 0:
        onboard_result = await onboard_ecosystem_tenant(
            name=body.name,
            roles=body.roles,
            tenant_auth_token=wallet_response.token,
            tenant_id=wallet_response.wallet_id,
        )

        await register_actor(actor=Actor(
            id=wallet_response.wallet_id,
            name=body.name,
            roles=list(body.roles),
            did=onboard_result.did,
            didcomm_invitation=onboard_result.didcomm_invitation,
        ))

    return CreateTenantResponse(
        tenant_id=wallet_response.wallet_id,
        created_at=wallet_response.created_at,
        image_url=body.image_url,
        updated_at=wallet_response.updated_at,
        tenant_name=body.name,
        access_token=tenant_api_key(auth.role, wallet_response.token),
    )
Esempio n. 2
0
async def get_tenant_for_admin_role(aries_controller: AcaPyClient,
                                    tenant_id: str,
                                    role: Role) -> WalletRecord:
    """Get the wallet record for a tenant with specified id.

    Will throw an error if the wallet is not created with same role as
    role specified in method parameters. This is identified by a role prefix
    to the wallet name. E.g. wallet name "member.xxxxxxxxxxxxxxxx" means
    the wallet was created in the context of a member.

    Args:
        aries_controller (AcaPyClient): aries controller to use
        tenant_id (str): Tenant id to retrieve the wallet for
        role (Role): Role the wallet should be in

    Raises:
        CloudApiException: When the role of the wallet does not match the
            role from the method parameteres

    Returns:
        WalletRecord: The wallet record.
    """

    # We retrieve the wallet to make sure it exists
    wallet = await aries_controller.multitenancy.get_wallet(wallet_id=tenant_id
                                                            )

    # This checks if the role of the wallet is in the current context. It would be possible
    # to retrieve a member wallet while authorized as an ecosystem admin.
    if not wallet_is_for_admin_role(wallet, role):
        raise CloudApiException(f"Unauthorized", 401)

    return wallet
Esempio n. 3
0
def tenant_api_key(role: Role, tenant_token: str):
    "Get the cloud api key for a tenant with specified role."

    if not role.agent_type.tenant_role:
        raise CloudApiException("Invalid role", 403)

    return f"{role.agent_type.tenant_role.name}.{tenant_token}"
Esempio n. 4
0
async def update_tenant(
        tenant_id: str,
        body: UpdateTenantRequest,
        aries_controller: AcaPyClient = Depends(multitenant_admin),
        auth: AcaPyAuth = Depends(acapy_auth),
) -> Tenant:
    """Update tenant by id."""
    wallet = await get_tenant_for_admin_role(aries_controller=aries_controller,
                                             tenant_id=tenant_id,
                                             role=auth.role)

    # Only when in ecosystem context we update the trust registry / ecosystem onboarding steps
    if auth.role == Role.ECOSYSTEM_ADMIN:
        await handle_ecosystem_tenant_update(admin_controller=aries_controller,
                                             tenant_id=tenant_id,
                                             update=body)

    elif auth.role == Role.MEMBER_ADMIN and body.roles and len(body.roles) > 0:
        raise CloudApiException("Roles not allowed for member", 403)

    wallet = await aries_controller.multitenancy.update_wallet(
        wallet_id=tenant_id,
        body=UpdateWalletRequest(
            image_url=body.image_url,
            label=body.name,
        ),
    )

    return tenant_from_wallet_record(wallet)
Esempio n. 5
0
async def get_public_did(
        aries_controller: AcaPyClient = Depends(agent_selector), ):
    """
    Fetch the current public DID.
    """
    result = await aries_controller.wallet.get_public_did()

    if not result.result:
        raise CloudApiException("No public did found", 404)

    return result.result
Esempio n. 6
0
async def get_public_did(controller: AcaPyClient) -> Did:
    """Get the public did.

    Args:
        controller (AcaPyClient): aca-py client

    Raises:
        CloudApiException: if retrieving the public did failed.

    Returns:
        Did: the public did
    """
    result = await controller.wallet.get_public_did()

    if not result.result:
        raise CloudApiException("No public did found", 404)

    return Did(did=result.result.did, verkey=result.result.verkey)
Esempio n. 7
0
async def set_public_did(controller: AcaPyClient, did: str) -> DID:
    """Set the public did.

    Args:
        controller (AcaPyClient): aca-py client
        did (str): the did to set as public

    Raises:
        CloudApiException: if registration of the public did failed

    Returns:
        DID: the did
    """
    result = await controller.wallet.set_public_did(did=did)

    if not result.result:
        raise CloudApiException(f"Error setting public did: {did}")

    return result.result
Esempio n. 8
0
async def assert_public_did(aries_controller: AcaPyClient) -> str:
    """assert the agent has a public did, throwing an error otherwise.

    Args:
        aries_controller (AcaPyClient): the aca-py client.

    Returns:
        str: the public did formatted as fully qualified did
    """
    # Assert the agent has a public did
    public_did = await aries_controller.wallet.get_public_did()

    if not public_did.result or not public_did.result.did:
        raise CloudApiException(
            "Agent has no public did",
            403,
        )

    return f"did:sov:{public_did.result.did}"
async def onboard_verifier(*, name: str, verifier_controller: AcaPyClient):
    """Onboard the controller as verifier.

    The onboarding will take care of the following:
      - create a multi_use invitation to use in the

    Args:
        verifier_controller (AcaPyClient): authenticated ACA-Py client for verifier
    """

    onboarding_result = {}

    # If the verifier already has a public did it doesn't need an invitation. The invitation
    # is just to bypass having to pay for a public did for every verifier
    try:
        public_did = await acapy_wallet.get_public_did(
            controller=verifier_controller)

        onboarding_result["did"] = qualified_did_sov(public_did.did)
    except CloudApiException:
        # create a multi_use invitation from the did
        invitation = await verifier_controller.out_of_band.create_invitation(
            auto_accept=True,
            multi_use=True,
            body=InvitationCreateRequest(
                use_public_did=False,
                alias=f"Trust Registry {name}",
                handshake_protocols=["https://didcomm.org/didexchange/1.0"],
            ),
        )

        try:
            # Because we're not creating an invitation with a public did the invitation will always
            # contain a did:key as the first recipientKey in the first service
            onboarding_result["did"] = invitation.invitation.services[0][
                "recipientKeys"][0]
            onboarding_result["didcomm_invitation"] = invitation.invitation_url
        except (KeyError, IndexError) as e:
            # FIXME: more verbose error
            raise CloudApiException(f"Error creating invitation: {e}")

    return OnboardResult(**onboarding_result)
Esempio n. 10
0
async def create_did(controller: AcaPyClient) -> Did:
    """Create a local did

    Args:
        controller (AcaPyClient): [description]

    Raises:
        HTTPException: If the creation of the did failed

    Returns:
        Did: The created did
    """
    did_result = await controller.wallet.create_did(body=DIDCreate())

    if (not did_result.result or not did_result.result.did
            or not did_result.result.verkey):
        logger.error("Failed to create DID:\n %s", did_result)
        raise CloudApiException(f"Error creating did: {did_result.dict()}",
                                500)

    return Did(did=did_result.result.did, verkey=did_result.result.verkey)
Esempio n. 11
0
async def onboard_ecosystem_tenant(*, name: str,
                                   roles: List[TrustRegistryRole],
                                   tenant_auth_token: str,
                                   tenant_id: str) -> OnboardResult:
    if "issuer" in roles:
        # Get governance and tenant controllers, onboard issuer
        async with get_governance_controller(
        ) as governance_controller, get_tenant_controller(
                Role.ECOSYSTEM, tenant_auth_token) as tenant_controller:
            return await onboard_issuer(
                name=name,
                endorser_controller=governance_controller,
                issuer_controller=tenant_controller,
                issuer_wallet_id=tenant_id,
            )

    elif "verifier" in roles:
        async with get_tenant_controller(
                Role.ECOSYSTEM, tenant_auth_token) as tenant_controller:
            return await onboard_verifier(
                name=name, verifier_controller=tenant_controller)

    raise CloudApiException("Unable to onboard tenant without role(s).")
Esempio n. 12
0
async def onboard_issuer(
    *,
    name: str,
    endorser_controller: AcaPyClient,
    issuer_controller: AcaPyClient,
    issuer_wallet_id: str,
):
    """Onboard the controller as issuer.

    The onboarding will take care of the following:
      - make sure the issuer has a public did
      - make sure the issuer has a connection with the endorser
      - make sure the issuer has set up endorsement with the endorser connection

    Args:
        name (str): name of the issuer
        issuer_controller (AcaPyClient): authenticated ACA-Py client for issuer
        endorser_controller (AcaPyClient): authenticated ACA-Py client for endorser
    """
    # Make sure the issuer has a public did
    try:
        issuer_did = await acapy_wallet.get_public_did(
            controller=issuer_controller)
    except CloudApiException:
        # no public did
        issuer_did = await acapy_wallet.create_did(issuer_controller)
        await acapy_ledger.register_nym_on_ledger(
            endorser_controller,
            did=issuer_did.did,
            verkey=issuer_did.verkey,
            alias=name,
        )
        await acapy_ledger.accept_taa_if_required(issuer_controller)
        await acapy_wallet.set_public_did(issuer_controller, issuer_did.did)

    endorser_did = await acapy_wallet.get_public_did(
        controller=endorser_controller)

    # Make sure the issuer has a connection with the endorser
    invitation = await endorser_controller.out_of_band.create_invitation(
        auto_accept=True,
        body=InvitationCreateRequest(
            handshake_protocols=["https://didcomm.org/didexchange/1.0"],
            use_public_did=True,
        ),
    )

    logger.info(
        f"Starting webhook listener for connections with wallet id {issuer_wallet_id}"
    )

    wait_for_event, _ = await start_listener(topic="connections",
                                             wallet_id=issuer_wallet_id)

    logger.debug("Receiving connection invitation")

    # FIXME: make sure the connection with this alias doesn't exist yet
    # Or does use_existing_connection take care of this?
    connection_record = await issuer_controller.out_of_band.receive_invitation(
        auto_accept=True,
        use_existing_connection=True,
        body=invitation.invitation,
        alias=ACAPY_ENDORSER_ALIAS,
    )

    logger.debug(
        f"Waiting for connection with id {connection_record.connection_id} to be completed"
    )

    # Wait for connection to be completed before continuing
    try:
        await wait_for_event(
            filter_map={
                "connection_id": connection_record.connection_id,
                "state": "completed",
            })
    except TimeoutError:
        raise CloudApiException("Error creating connection with endorser", 500)

    logger.debug("Successfully created connection")

    await issuer_controller.endorse_transaction.set_endorser_role(
        conn_id=connection_record.connection_id,
        transaction_my_job="TRANSACTION_AUTHOR")

    # Make sure endorsement has been configured
    # There is currently no way to retrieve endorser info. We'll just set it
    # to make sure the endorser info is set.
    await issuer_controller.endorse_transaction.set_endorser_info(
        conn_id=connection_record.connection_id,
        endorser_did=endorser_did.did,
    )

    return OnboardResult(did=qualified_did_sov(issuer_did.did))