List the user's providers

Merge request reports

Loading
+14 −1
Changes for capif_backend/backoffice_service/controllers/me_controller.py: 14 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@ from flask_jwt_extended import jwt_required

from ..core import invoker_ops, provider_ops
from ..utils.utils import get_capif_token, get_capif_urls, capif_user_required
from ..utils.vault import read_invoker, list_invokers, read_provider, VaultError
from ..utils.vault import read_invoker, list_invokers, list_providers, read_provider, VaultError

me_routes = Blueprint("me_routes", __name__)

@@ -76,6 +76,19 @@ def onboard_provider(user_uuid):
    data, status = provider_ops.onboard(get_capif_token(), user_uuid, ccf_provider_onboarding_url, prov_dom_info)
    return jsonify(data), status

@me_routes.route("/providers", methods=["GET"])
@jwt_required()
@capif_user_required
def list_my_providers(user_uuid):
    # No user input reaches the Vault path here: the uuid comes from the JWT.
    try:
        provider_ids = list_providers(user_uuid)
    except VaultError as e:
        print(f"ERROR: {e}")
        return jsonify({"error": "Could not list providers."}), 500

    return jsonify({"providers": provider_ids}), 200

@me_routes.route("/providers/<api_prov_dom_id>/apis", methods=["POST"])
@jwt_required()
@capif_user_required
+26 −8
Changes for capif_backend/backoffice_service/utils/vault.py: 26 added lines, 8 removed lines.
Original line number Diff line number Diff line
@@ -138,10 +138,20 @@ def read_provider(user_uuid, prov_dom_id):
    except (ValueError, KeyError, TypeError) as e:
        raise VaultError(f"Unexpected Vault response shape for provider {prov_dom_id}") from e

def list_invokers(user_uuid):
    """Return the apiInvokerIds stored for a user."""
_ENTITY_KINDS = ("invokers", "providers")

def _list_entities(user_uuid, kind):
    """Return the ids stored under the user's `kind` folder (possibly an empty list).

    `kind` must be one of the hardcoded _ENTITY_KINDS, never a value from the request: it becomes
    part of the Vault path and is not checked by _ENTITY_ID_RE. The uuid comes from the JWT.
    Raises VaultError on any failure other than "nothing there".
    KV v2 only lists under `metadata` (not `data`); Vault's LIST verb is sent as GET ?list=true.
    """
    if kind not in _ENTITY_KINDS:
        raise ValueError(f"Unknown entity kind: {kind}")

    path = f"secret/metadata/backoffice/users/{user_uuid}/invokers"
    path = f"secret/metadata/backoffice/users/{user_uuid}/{kind}"
    try:
        response = requests.get(
            f"{vault_addr()}/v1/{path}",
@@ -151,18 +161,26 @@ def list_invokers(user_uuid):
            timeout=10,
        )
    except requests.exceptions.RequestException as e:
        raise VaultError(f"Vault list of invokers failed: {e}") from e
        raise VaultError(f"Vault list of {kind} failed: {e}") from e

    # Vault returns 404 when the path has no keys: this user has no invokers yet.
    # Vault answers 404 when the path has no keys: this user has none yet.
    if response.status_code == 404:
        return []
    if not response.ok:
        raise VaultError(f"Vault list of invokers failed: {response.status_code}")
        raise VaultError(f"Vault list of {kind} failed: {response.status_code}")

    try:
        keys = response.json()["data"]["keys"]
    except (ValueError, KeyError, TypeError) as e:
        raise VaultError("Unexpected Vault list response shape") from e
        raise VaultError(f"Unexpected Vault list response shape for {kind}") from e

    # Keys ending in "/" would be sub-folders, not invokers
    # Keys ending in "/" would be sub-folders, not entities; we never create them, but skip them defensively.
    return [key for key in keys if not key.endswith("/")]

def list_invokers(user_uuid):
    """Return the apiInvokerIds stored for a user (possibly an empty list)."""
    return _list_entities(user_uuid, "invokers")

def list_providers(user_uuid):
    """Return the apiProvDomIds stored for a user (possibly an empty list)."""
    return _list_entities(user_uuid, "providers")
+2 −1
Changes for README.md: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -118,6 +118,7 @@ Endpoints acting on behalf of the logged-in user. They require a valid access to
- POST /api/me/provider
  - Registers an API provider in CAPIF for the logged-in user, with one key pair and certificate per function (AEF, APF, AMF), all stored in a single Vault secret under `secret/backoffice/users/{uuid}/providers/{apiProvDomId}`.
  - Body: `{"apiProvDomInfo": "..."}`. Returns `201` `{"apiProvDomId": "..."}`.
- GET /api/me/providers: lists the logged-in user's apiProvDomIds (empty list if none).
- POST /api/me/providers/{apiProvDomId}/apis
  - Publishes an API in CAPIF as the provider's APF (mTLS with the APF certificate). The AEF is recorded as the function exposing the API.
  - Body: `apiName`, `apiVersion`, `resourceName`, `uri`, `operations`, `address`, `port` (required); `description`, `resourceDescription`, `protocol` (`HTTP_1_1`), `dataFormat` (`JSON`), `securityMethods` (`["OAUTH"]`) (optional). Returns `201` `{"apiId": "..."}`.
@@ -193,7 +194,7 @@ Implemented:
- Authentication with access and refresh JWT tokens.
- Resource management for Invokers, Providers, APIs, and Users.
- Configuration and Register configuration management endpoints.
- User-scoped invoker onboarding (`POST /api/me/invoker`), with credentials stored in Vault.
- User-scoped CAPIF flow under `/api/me`: invoker onboarding, list and discover; provider onboarding, list and publish.

In progress:
- local_build.sh version automation and registry push flow.
Loading