"""What a connector keeps across restarts, for a superuser to see and reset. A connector writes these itself (``ConnectorNode.store``). This is the way back when one of them is wrong: a Hue bridge replaced, so the certificate pinned for it no longer matches. Values are never handed out, since a connector may keep a token here; the keys say enough to decide what to clear. A connector is named by any node type it installs, as the canvas knows it, and its keys are shared by every node type of the same package. """ from datetime import datetime from typing import Any from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from fluksio.api.deps import get_current_active_superuser from fluksio.flow.connector import ConnectorNode from fluksio.flow.connector_store import KEY, ConnectorStore, namespace_of from fluksio.flow.controller import NODE_TYPES from fluksio.models import Message router = APIRouter( prefix="/connectors", tags=["connectors"], dependencies=[Depends(get_current_active_superuser)], ) class StoredKey(BaseModel): key: str #: Bytes, so a superuser can tell a fingerprint from a blob. size: int updated_at: datetime class ConnectorStatePublic(BaseModel): #: The package the keys belong to. connector: str data: list[StoredKey] count: int def _store(node_type: str) -> ConnectorStore: """The store of the connector that installed *node_type*, or a 404.""" known = NODE_TYPES.get(node_type) cls = known.cls if known is not None else None if not (isinstance(cls, type) and issubclass(cls, ConnectorNode)): raise HTTPException(status_code=404, detail=f"No connector named '{node_type}'") return ConnectorStore(namespace_of(cls)) @router.get("/{node_type}/state", response_model=ConnectorStatePublic) def read_connector_state(node_type: str) -> Any: """The keys this connector keeps, without their values.""" store = _store(node_type) keys = [ StoredKey(key=row.key, size=len(row.value.encode()), updated_at=row.updated_at) for row in store.entries() ] return ConnectorStatePublic(connector=store.connector, data=keys, count=len(keys)) @router.delete("/{node_type}/state", response_model=Message) def clear_connector_state(node_type: str) -> Any: """Forget everything this connector keeps. It starts over on next use.""" store = _store(node_type) cleared = store.clear() return Message(message=f"Cleared {cleared} key(s) of {store.connector}") @router.delete("/{node_type}/state/{key}", response_model=Message) def delete_connector_state(node_type: str, key: str) -> Any: """Forget one key, such as the certificate pinned for one bridge.""" store = _store(node_type) if not KEY.fullmatch(key) or not store.delete(key): raise HTTPException(status_code=404, detail=f"No key '{key}' stored") return Message(message=f"Deleted '{key}' of {store.connector}")