All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 18s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 24s
Migration 005, Registry-Sync, Placeholder-Validation, capability-geschuetzte API, Tests und Abschlussbericht v0.1. Co-authored-by: Cursor <cursoragent@cursor.com>
209 lines
7.0 KiB
Python
209 lines
7.0 KiB
Python
"""Central configuration service — no plaintext secrets."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from db import get_connection
|
|
from services.audit import log_audit
|
|
|
|
SECRET_VALUE_FORBIDDEN = (
|
|
"Configuration entries with is_secret=true must not store plaintext values; "
|
|
"use environment or secret management."
|
|
)
|
|
|
|
|
|
def _validate_secret_entry(*, is_secret: bool, value: Any) -> None:
|
|
if not is_secret:
|
|
return
|
|
if value not in (None, {}, "", {"ref": "env"}):
|
|
raise ValueError(SECRET_VALUE_FORBIDDEN)
|
|
|
|
|
|
def get_config(
|
|
config_key: str,
|
|
*,
|
|
scope: str = "global",
|
|
tenant_id: str | None = None,
|
|
) -> dict | None:
|
|
conn = get_connection()
|
|
try:
|
|
with conn.cursor() as cur:
|
|
cur.execute(
|
|
"""
|
|
SELECT config_key, scope, tenant_id, value, value_type, description, is_secret
|
|
FROM configuration_entries
|
|
WHERE config_key = %s AND scope = %s
|
|
AND (
|
|
(scope = 'global' AND tenant_id IS NULL)
|
|
OR (scope = 'tenant' AND tenant_id = %s::uuid)
|
|
)
|
|
""",
|
|
(config_key, scope, tenant_id),
|
|
)
|
|
row = cur.fetchone()
|
|
if not row:
|
|
return None
|
|
return {
|
|
"config_key": row[0],
|
|
"scope": row[1],
|
|
"tenant_id": str(row[2]) if row[2] else None,
|
|
"value": row[3],
|
|
"value_type": row[4],
|
|
"description": row[5],
|
|
"is_secret": row[6],
|
|
}
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def list_configs(*, scope: str | None = None, tenant_id: str | None = None) -> list[dict]:
|
|
conn = get_connection()
|
|
try:
|
|
with conn.cursor() as cur:
|
|
if scope == "global":
|
|
cur.execute(
|
|
"""
|
|
SELECT config_key, scope, tenant_id, value, value_type, description, is_secret
|
|
FROM configuration_entries
|
|
WHERE scope = 'global' AND tenant_id IS NULL
|
|
ORDER BY config_key
|
|
"""
|
|
)
|
|
elif scope == "tenant" and tenant_id:
|
|
cur.execute(
|
|
"""
|
|
SELECT config_key, scope, tenant_id, value, value_type, description, is_secret
|
|
FROM configuration_entries
|
|
WHERE scope = 'tenant' AND tenant_id = %s::uuid
|
|
ORDER BY config_key
|
|
""",
|
|
(tenant_id,),
|
|
)
|
|
else:
|
|
cur.execute(
|
|
"""
|
|
SELECT config_key, scope, tenant_id, value, value_type, description, is_secret
|
|
FROM configuration_entries
|
|
ORDER BY scope, config_key
|
|
"""
|
|
)
|
|
rows = []
|
|
for row in cur.fetchall():
|
|
item = {
|
|
"config_key": row[0],
|
|
"scope": row[1],
|
|
"tenant_id": str(row[2]) if row[2] else None,
|
|
"value_type": row[4],
|
|
"description": row[5],
|
|
"is_secret": row[6],
|
|
}
|
|
if row[6]:
|
|
item["value"] = {"ref": "env"}
|
|
else:
|
|
item["value"] = row[3]
|
|
rows.append(item)
|
|
return rows
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def upsert_config(
|
|
*,
|
|
config_key: str,
|
|
scope: str,
|
|
value: Any,
|
|
value_type: str = "string",
|
|
description: str = "",
|
|
is_secret: bool = False,
|
|
tenant_id: str | None = None,
|
|
user_id: str | None = None,
|
|
) -> dict:
|
|
_validate_secret_entry(is_secret=is_secret, value=value)
|
|
if scope == "tenant" and not tenant_id:
|
|
raise ValueError("tenant_id required for tenant-scoped config")
|
|
if scope == "global":
|
|
tenant_id = None
|
|
|
|
conn = get_connection()
|
|
try:
|
|
with conn.cursor() as cur:
|
|
if scope == "global":
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO configuration_entries (
|
|
config_key, scope, tenant_id, value, value_type, description, is_secret
|
|
)
|
|
VALUES (%s, 'global', NULL, %s::jsonb, %s, %s, %s)
|
|
ON CONFLICT (config_key) WHERE scope = 'global' DO UPDATE SET
|
|
value = EXCLUDED.value,
|
|
value_type = EXCLUDED.value_type,
|
|
description = EXCLUDED.description,
|
|
is_secret = EXCLUDED.is_secret,
|
|
updated_at = NOW()
|
|
""",
|
|
(
|
|
config_key,
|
|
json.dumps(value if not is_secret else {"ref": "env"}),
|
|
value_type,
|
|
description,
|
|
is_secret,
|
|
),
|
|
)
|
|
else:
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO configuration_entries (
|
|
config_key, scope, tenant_id, value, value_type, description, is_secret
|
|
)
|
|
VALUES (%s, 'tenant', %s::uuid, %s::jsonb, %s, %s, %s)
|
|
ON CONFLICT (config_key, tenant_id) WHERE scope = 'tenant' DO UPDATE SET
|
|
value = EXCLUDED.value,
|
|
value_type = EXCLUDED.value_type,
|
|
description = EXCLUDED.description,
|
|
is_secret = EXCLUDED.is_secret,
|
|
updated_at = NOW()
|
|
""",
|
|
(
|
|
config_key,
|
|
tenant_id,
|
|
json.dumps(value if not is_secret else {"ref": "env"}),
|
|
value_type,
|
|
description,
|
|
is_secret,
|
|
),
|
|
)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
log_audit(
|
|
"config.entry.changed",
|
|
user_id=user_id,
|
|
tenant_id=tenant_id,
|
|
details={"config_key": config_key, "scope": scope, "is_secret": is_secret},
|
|
)
|
|
result = get_config(config_key, scope=scope, tenant_id=tenant_id)
|
|
assert result is not None
|
|
return result
|
|
|
|
|
|
def sync_default_configs(defaults: list[dict]) -> int:
|
|
if not defaults:
|
|
return 0
|
|
for item in defaults:
|
|
existing = get_config(item["config_key"], scope=item.get("scope", "global"))
|
|
if existing:
|
|
continue
|
|
upsert_config(
|
|
config_key=item["config_key"],
|
|
scope=item.get("scope", "global"),
|
|
value=item["value"],
|
|
value_type=item.get("value_type", "string"),
|
|
description=item.get("description", ""),
|
|
is_secret=item.get("is_secret", False),
|
|
)
|
|
print(f"[config_service] Default configs ensured — {len(defaults)} item(s)")
|
|
return 0
|