"""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