Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020
This commit is contained in:
commit
cb5be898ea
663 files changed
+82674
No files matched your search
@@ -0,0 +1,141 @@
|
||||
"""Authenticated management routes for machine-client onboarding and revocation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from packages.hub_clients import BootstrapHandle, ClientConfig, ClientKind, ClientRecord
|
||||
|
||||
from .service import ClientService
|
||||
|
||||
|
||||
def _deny_by_default(_: Request) -> None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN, detail="client management authorization required"
|
||||
)
|
||||
|
||||
|
||||
def _deny_all_actions(_: Any, action: str) -> None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"client management authorization required for {action}",
|
||||
)
|
||||
|
||||
|
||||
class ClientCreateRequest(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
name: str = Field(min_length=1, max_length=128)
|
||||
project: str = Field(min_length=1, max_length=63, pattern=r"^[a-z0-9][a-z0-9-]*$")
|
||||
environment: str = Field(min_length=1, max_length=32, pattern=r"^[a-z0-9][a-z0-9-]*$")
|
||||
kind: ClientKind
|
||||
|
||||
|
||||
class ClientOnboardingResponse(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
client: ClientRecord
|
||||
bootstrap: BootstrapHandle
|
||||
config: ClientConfig
|
||||
provisioning: dict[str, Any]
|
||||
|
||||
|
||||
def build_client_router(
|
||||
service: ClientService,
|
||||
*,
|
||||
authenticate: Callable[[Request], Any] = _deny_by_default,
|
||||
authorize: Callable[[Any, str], None] = _deny_all_actions,
|
||||
prefix: str = "/clients",
|
||||
token_endpoint: str = "https://auth.example.invalid/application/o/token/", # noqa: S107
|
||||
oauth_client_id: str = "itworx-mcp-hub",
|
||||
) -> APIRouter:
|
||||
"""Every route authenticates first, then checks a specific action against
|
||||
`authorize` -- deliberately not one blanket router-level dependency, so a
|
||||
read (list/config) and a destructive action (revoke) can require
|
||||
different roles. See services/control_api/authz.py for the actual
|
||||
permission matrix `authorize` is expected to enforce in production.
|
||||
"""
|
||||
router = APIRouter(prefix=prefix, tags=["clients"])
|
||||
# Bound once, reused as route defaults below: ruff (flake8-bugbear B008)
|
||||
# flags calling `Depends(...)` directly inside a function signature's
|
||||
# default value.
|
||||
authenticated = Depends(authenticate)
|
||||
|
||||
@router.post("", response_model=ClientOnboardingResponse, status_code=status.HTTP_201_CREATED)
|
||||
def create(
|
||||
payload: ClientCreateRequest, principal: Any = authenticated
|
||||
) -> ClientOnboardingResponse:
|
||||
authorize(principal, "client.create")
|
||||
try:
|
||||
client, bootstrap, config = service.create(**payload.model_dump())
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
return ClientOnboardingResponse(
|
||||
client=client,
|
||||
bootstrap=bootstrap,
|
||||
config=config,
|
||||
provisioning={
|
||||
"status": "pending-authentik-service-account",
|
||||
"subject": None,
|
||||
"issuer": str(getattr(principal, "issuer", "unavailable")),
|
||||
"oauthUsername": (
|
||||
f"svc-mcp-codex-{client.project}-{str(client.id).replace('-', '')[:8]}"
|
||||
),
|
||||
"tokenEndpoint": token_endpoint,
|
||||
"oauthClientId": oauth_client_id,
|
||||
"steps": [
|
||||
"Provision the named Authentik service account and app password.",
|
||||
"Activate this pending Hub client with the exact Authentik user UUID.",
|
||||
"Install the generated Codex stdio profile using a user-only credential file.",
|
||||
"Run MCP discovery, one read-only invocation and one cross-project denial.",
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
@router.get("", response_model=tuple[ClientRecord, ...])
|
||||
def list_clients(principal: Any = authenticated) -> tuple[ClientRecord, ...]:
|
||||
authorize(principal, "client.read")
|
||||
return service.list()
|
||||
|
||||
@router.get("/{client_id}/config", response_model=ClientConfig)
|
||||
def config(
|
||||
client_id: UUID, kind: ClientKind | None = None, principal: Any = authenticated
|
||||
) -> ClientConfig:
|
||||
authorize(principal, "client.read")
|
||||
try:
|
||||
return service.config(client_id, kind)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail="client not found") from exc
|
||||
|
||||
@router.post("/{client_id}/bootstrap", response_model=BootstrapHandle)
|
||||
def bootstrap(client_id: UUID, principal: Any = authenticated) -> BootstrapHandle:
|
||||
authorize(principal, "client.bootstrap")
|
||||
try:
|
||||
return service.bootstrap(client_id)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail="client not found") from exc
|
||||
except PermissionError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
||||
|
||||
@router.post("/{client_id}/disable", response_model=ClientRecord)
|
||||
def disable(client_id: UUID, principal: Any = authenticated) -> ClientRecord:
|
||||
authorize(principal, "client.disable")
|
||||
try:
|
||||
return service.disable(client_id)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail="client not found") from exc
|
||||
|
||||
@router.post("/{client_id}/revoke", response_model=ClientRecord)
|
||||
def revoke(client_id: UUID, principal: Any = authenticated) -> ClientRecord:
|
||||
authorize(principal, "client.revoke")
|
||||
try:
|
||||
return service.revoke(client_id)
|
||||
except KeyError as exc:
|
||||
raise HTTPException(status_code=404, detail="client not found") from exc
|
||||
|
||||
return router
|
||||
Reference in new issue
Block a user