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,147 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from collections.abc import Mapping
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from urllib.parse import quote_plus
|
||||
from uuid import NAMESPACE_URL, UUID, uuid5
|
||||
|
||||
from packages.connector_kit import ConnectorContext
|
||||
from packages.hub_runtime import read_secret_file
|
||||
|
||||
ENVIRONMENTS = frozenset({"dev", "test", "staging", "prod"})
|
||||
PROJECT_SLUG = re.compile(r"^[a-z0-9][a-z0-9-]{0,62}$")
|
||||
IDENTITY_ASSERTION = re.compile(r"^v1:([0-9]{10}):([0-9a-f]{64})$")
|
||||
TRACE_ID = re.compile(r"^[A-Za-z0-9._:-]{1,128}$")
|
||||
|
||||
|
||||
def deterministic_id(kind: str, value: str) -> UUID:
|
||||
return uuid5(NAMESPACE_URL, f"https://hub.example.com/{kind}/{value}")
|
||||
|
||||
|
||||
def load_connector_identity_key() -> bytes:
|
||||
path = Path(
|
||||
os.environ.get("CONNECTOR_IDENTITY_KEY_FILE", "/run/secrets/connector_identity_signing_key")
|
||||
)
|
||||
key = read_secret_file(path).encode("utf-8")
|
||||
if len(key) < 32:
|
||||
raise ValueError("connector identity signing key must contain at least 32 bytes")
|
||||
return key
|
||||
|
||||
|
||||
def identity_assertion(
|
||||
key: bytes,
|
||||
*,
|
||||
project: str,
|
||||
environment: str,
|
||||
client_id: str,
|
||||
trace_id: str,
|
||||
now: int | None = None,
|
||||
) -> str:
|
||||
timestamp = int(time.time()) if now is None else now
|
||||
message = f"v1\n{timestamp}\n{project}\n{environment}\n{client_id}\n{trace_id}".encode()
|
||||
signature = hmac.new(key, message, hashlib.sha256).hexdigest()
|
||||
return f"v1:{timestamp}:{signature}"
|
||||
|
||||
|
||||
def connector_context(
|
||||
headers: Mapping[str, str],
|
||||
projects: set[str],
|
||||
*,
|
||||
signing_key: bytes,
|
||||
now: int | None = None,
|
||||
) -> ConnectorContext:
|
||||
project = headers.get("x-itworx-project", "").strip().lower()
|
||||
environment = headers.get("x-itworx-environment", "").strip().lower()
|
||||
client_id = headers.get("x-itworx-client-id", "").strip().lower()
|
||||
trace_id = headers.get("x-itworx-trace-id", "").strip()
|
||||
if project not in projects or not PROJECT_SLUG.fullmatch(project):
|
||||
raise PermissionError("connector project context is not registered")
|
||||
if environment not in ENVIRONMENTS:
|
||||
raise PermissionError("connector environment context is not registered")
|
||||
try:
|
||||
UUID(client_id)
|
||||
except ValueError as exc:
|
||||
raise PermissionError("connector client context is invalid") from exc
|
||||
assertion = headers.get("x-itworx-identity-assertion", "").strip().lower()
|
||||
match = IDENTITY_ASSERTION.fullmatch(assertion)
|
||||
reference = int(time.time()) if now is None else now
|
||||
if match is None or abs(reference - int(match.group(1))) > 60:
|
||||
raise PermissionError("connector identity assertion is invalid or expired")
|
||||
expected = identity_assertion(
|
||||
signing_key,
|
||||
project=project,
|
||||
environment=environment,
|
||||
client_id=client_id,
|
||||
trace_id=trace_id,
|
||||
now=int(match.group(1)),
|
||||
)
|
||||
if not hmac.compare_digest(assertion, expected):
|
||||
raise PermissionError("connector identity assertion is invalid or expired")
|
||||
return ConnectorContext(
|
||||
workspace_id=deterministic_id("workspace", "itworx"),
|
||||
project_id=deterministic_id("project", project),
|
||||
project_slug=project,
|
||||
environment=environment,
|
||||
client_id=UUID(client_id),
|
||||
# The trace id is already covered by the identity assertion above, so it is
|
||||
# authenticated Hub input; the pattern check only keeps it a safe header value.
|
||||
correlation_id=trace_id if TRACE_ID.fullmatch(trace_id) else None,
|
||||
)
|
||||
|
||||
|
||||
def request_headers(context: Any) -> Mapping[str, str]:
|
||||
request = context.request_context.request
|
||||
headers = getattr(request, "headers", None)
|
||||
if headers is None:
|
||||
raise PermissionError("connector transport context is unavailable")
|
||||
return {str(key).lower(): str(value) for key, value in headers.items()}
|
||||
|
||||
|
||||
def run_asgi(application: Any) -> None:
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(
|
||||
application,
|
||||
host="0.0.0.0", # noqa: S104 - container network binding is intentional.
|
||||
port=int(os.environ.get("PORT", "8080")),
|
||||
access_log=False,
|
||||
proxy_headers=False,
|
||||
server_header=False,
|
||||
)
|
||||
|
||||
|
||||
def load_json_environment(name: str, expected: type[list[Any]] | type[dict[str, Any]]) -> Any:
|
||||
raw = os.environ.get(name, "")
|
||||
try:
|
||||
value = json.loads(raw)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise ValueError(f"{name} is invalid") from exc
|
||||
if not isinstance(value, expected):
|
||||
raise ValueError(f"{name} has an invalid shape")
|
||||
return value
|
||||
|
||||
|
||||
def read_regular_json(path: Path) -> dict[str, Any]:
|
||||
if not path.is_file() or path.is_symlink():
|
||||
raise ValueError("registered configuration file is unavailable")
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
raise ValueError("registered configuration file is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def hub_database_url() -> str:
|
||||
password = read_secret_file(
|
||||
Path(os.environ.get("HUB_DATABASE_PASSWORD_FILE", "/run/secrets/hub_database_password"))
|
||||
)
|
||||
host = os.environ.get("HUB_DATABASE_HOST", "postgres")
|
||||
database = os.environ.get("HUB_DATABASE_NAME", "itworx_mcp_hub")
|
||||
user = os.environ.get("HUB_DATABASE_USER", "itworx_mcp_hub")
|
||||
return f"postgresql+psycopg://{quote_plus(user)}:{quote_plus(password)}@{host}:5432/{database}"
|
||||
Reference in new issue
Block a user