4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
200 lines
6.0 KiB
Python
200 lines
6.0 KiB
Python
"""Metadata-file-backed LLM provider auth records.
|
|
|
|
The records in this file are intentionally non-secret. They exist so status
|
|
commands can report local auth state without touching Keychain secrets.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import tempfile
|
|
from collections.abc import Mapping
|
|
from contextlib import suppress
|
|
from datetime import UTC, datetime
|
|
from pathlib import Path
|
|
|
|
from filelock import FileLock
|
|
|
|
from config.constants import OPENSRE_HOME_DIR
|
|
|
|
_VERSION = 1
|
|
_AUTH_RECORD_PREFIX = "provider-auth:"
|
|
_LOCK_TIMEOUT_SECONDS = 10.0
|
|
|
|
|
|
def _auth_metadata_path() -> Path:
|
|
override = os.getenv("OPENSRE_LLM_AUTH_METADATA_PATH", "").strip()
|
|
if override:
|
|
return Path(override).expanduser()
|
|
return OPENSRE_HOME_DIR / "llm-auth.json"
|
|
|
|
|
|
def _lock_path(path: Path) -> Path:
|
|
return path.with_suffix(path.suffix + ".lock")
|
|
|
|
|
|
def _utc_now() -> str:
|
|
return datetime.now(UTC).replace(microsecond=0).isoformat()
|
|
|
|
|
|
def _ensure_parent(path: Path) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
with suppress(OSError):
|
|
path.parent.chmod(0o700)
|
|
|
|
|
|
def _empty_store() -> dict[str, object]:
|
|
return {"version": _VERSION, "providers": {}}
|
|
|
|
|
|
def _load_unlocked(path: Path) -> dict[str, object]:
|
|
if not path.exists():
|
|
return _empty_store()
|
|
try:
|
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
except (json.JSONDecodeError, OSError):
|
|
return _empty_store()
|
|
if not isinstance(data, dict):
|
|
return _empty_store()
|
|
providers = data.get("providers")
|
|
if not isinstance(providers, dict):
|
|
providers = {}
|
|
return {"version": _VERSION, "providers": providers}
|
|
|
|
|
|
def _write_unlocked(path: Path, data: Mapping[str, object]) -> None:
|
|
_ensure_parent(path)
|
|
fd, tmp_name = tempfile.mkstemp(
|
|
prefix=f".{path.name}.",
|
|
suffix=".tmp",
|
|
dir=str(path.parent),
|
|
text=True,
|
|
)
|
|
try:
|
|
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
json.dump(data, fh, indent=2, sort_keys=True)
|
|
fh.write("\n")
|
|
os.chmod(tmp_name, 0o600)
|
|
os.replace(tmp_name, path)
|
|
with suppress(OSError):
|
|
path.chmod(0o600)
|
|
finally:
|
|
if os.path.exists(tmp_name):
|
|
os.unlink(tmp_name)
|
|
|
|
|
|
def provider_auth_record_name(provider: str) -> str:
|
|
"""Return the stable metadata record name for one LLM provider."""
|
|
normalized = provider.strip().lower()
|
|
if not normalized:
|
|
raise ValueError("provider must not be empty")
|
|
return f"{_AUTH_RECORD_PREFIX}{normalized}"
|
|
|
|
|
|
def _normalize_record(values: Mapping[str, object]) -> dict[str, str]:
|
|
return {
|
|
str(key).strip(): str(value).strip()
|
|
for key, value in values.items()
|
|
if str(key).strip() and str(value).strip()
|
|
}
|
|
|
|
|
|
def save_provider_auth_record(
|
|
*,
|
|
provider: str,
|
|
auth_name: str,
|
|
kind: str,
|
|
source: str,
|
|
detail: str,
|
|
verified: bool = True,
|
|
stale: bool = False,
|
|
env_var: str = "",
|
|
) -> None:
|
|
"""Persist non-token auth metadata for a provider."""
|
|
provider_value = provider.strip().lower()
|
|
path = _auth_metadata_path()
|
|
_ensure_parent(path)
|
|
with FileLock(str(_lock_path(path)), timeout=_LOCK_TIMEOUT_SECONDS):
|
|
data = _load_unlocked(path)
|
|
providers = data.setdefault("providers", {})
|
|
if not isinstance(providers, dict):
|
|
providers = {}
|
|
data["providers"] = providers
|
|
providers[provider_value] = _normalize_record(
|
|
{
|
|
"provider": provider_value,
|
|
"auth_name": auth_name,
|
|
"kind": kind,
|
|
"source": source,
|
|
"detail": detail,
|
|
"env_var": env_var,
|
|
"verified": str(bool(verified)).lower(),
|
|
"stale": str(bool(stale)).lower(),
|
|
"updated_at": _utc_now(),
|
|
}
|
|
)
|
|
_write_unlocked(path, data)
|
|
|
|
|
|
def save_provider_auth_record_values(provider: str, values: Mapping[str, object]) -> None:
|
|
"""Persist an already-shaped non-secret provider auth record."""
|
|
provider_value = provider.strip().lower()
|
|
path = _auth_metadata_path()
|
|
_ensure_parent(path)
|
|
with FileLock(str(_lock_path(path)), timeout=_LOCK_TIMEOUT_SECONDS):
|
|
data = _load_unlocked(path)
|
|
providers = data.setdefault("providers", {})
|
|
if not isinstance(providers, dict):
|
|
providers = {}
|
|
data["providers"] = providers
|
|
record = _normalize_record({"provider": provider_value, **dict(values)})
|
|
record.setdefault("updated_at", _utc_now())
|
|
providers[provider_value] = record
|
|
_write_unlocked(path, data)
|
|
|
|
|
|
def resolve_provider_auth_record(provider: str) -> dict[str, str]:
|
|
"""Resolve non-token auth metadata for a provider without reading secrets."""
|
|
provider_value = provider.strip().lower()
|
|
path = _auth_metadata_path()
|
|
if not path.exists():
|
|
return {}
|
|
with FileLock(str(_lock_path(path)), timeout=_LOCK_TIMEOUT_SECONDS):
|
|
data = _load_unlocked(path)
|
|
providers = data.get("providers")
|
|
if not isinstance(providers, dict):
|
|
return {}
|
|
record = providers.get(provider_value)
|
|
if not isinstance(record, dict):
|
|
return {}
|
|
return {
|
|
str(key): str(value)
|
|
for key, value in record.items()
|
|
if isinstance(key, str) and isinstance(value, str)
|
|
}
|
|
|
|
|
|
def delete_provider_auth_record(provider: str) -> None:
|
|
"""Delete provider auth metadata."""
|
|
provider_value = provider.strip().lower()
|
|
path = _auth_metadata_path()
|
|
if not path.exists():
|
|
return
|
|
_ensure_parent(path)
|
|
with FileLock(str(_lock_path(path)), timeout=_LOCK_TIMEOUT_SECONDS):
|
|
data = _load_unlocked(path)
|
|
providers = data.get("providers")
|
|
if isinstance(providers, dict):
|
|
providers.pop(provider_value, None)
|
|
_write_unlocked(path, data)
|
|
|
|
|
|
__all__ = [
|
|
"delete_provider_auth_record",
|
|
"provider_auth_record_name",
|
|
"resolve_provider_auth_record",
|
|
"save_provider_auth_record",
|
|
"save_provider_auth_record_values",
|
|
]
|