Files
A-Team-Security-Infra-Agent…/backend/auth/oidc.py
nogueiraguh 1135e9d6a9 refactor: decompose monolith — app.py 10,600→143 lines, 30+ modules
Fase 0 complete: extract all business logic, auth, database, and 176
endpoints from monolithic app.py into dedicated modules.

Structure:
- app.py: FastAPI hub (CORS, routers, startup/shutdown)
- models.py: 13 Pydantic request models
- utils.py: shared utilities (embed status, upload validation, process registries)
- config.py: constants, env vars, model catalogs
- auth/: crypto, jwt_auth, oidc, rate_limit
- database/: db(), init_db(), schema DDL
- services/: genai, compliance, chat, terraform, embeddings, cis_reports, mcp
- routes/: 13 APIRouter modules (auth, users, oci_config, oci_explorer,
  genai, mcp, adb, embeddings, reports, chat, terraform, settings, cis_engine)

Also: README updated with OCIR auth instructions for manual docker run.

86 tests passing.
2026-04-06 15:20:10 -03:00

94 lines
3.1 KiB
Python

"""OIDC helpers — discovery, JWKS, state management, token validation."""
import json
import time
import threading
import jwt as pyjwt
from fastapi import HTTPException
from database import db
from auth.crypto import _safe_dec
# ── Caches ───────────────────────────────────────────────────────────────────
_oidc_discovery_cache: dict = {}
_oidc_jwks_cache: dict = {}
_OIDC_CACHE_TTL = 3600
_oidc_pending: dict = {}
_oidc_pending_lock = threading.Lock()
_OIDC_STATE_TTL = 300
def _get_oidc_config() -> dict:
keys = ("auth_mode", "oidc_issuer", "oidc_client_id", "oidc_client_secret",
"oidc_redirect_uri", "oidc_group_admin", "oidc_group_user", "oidc_group_viewer")
cfg = {}
with db() as c:
for k in keys:
row = c.execute("SELECT value FROM app_settings WHERE key=?", (k,)).fetchone()
cfg[k] = row["value"] if row else ""
if cfg.get("oidc_client_secret"):
cfg["oidc_client_secret"] = _safe_dec(cfg["oidc_client_secret"])
return cfg
def _oidc_discover(issuer: str) -> dict:
import requests as req
now = time.time()
cached = _oidc_discovery_cache.get(issuer)
if cached and now - cached[1] < _OIDC_CACHE_TTL:
return cached[0]
url = issuer.rstrip("/") + "/.well-known/openid-configuration"
resp = req.get(url, timeout=10)
resp.raise_for_status()
data = resp.json()
_oidc_discovery_cache[issuer] = (data, now)
return data
def _oidc_get_jwks(jwks_uri: str) -> list:
import requests as req
now = time.time()
cached = _oidc_jwks_cache.get(jwks_uri)
if cached and now - cached[1] < _OIDC_CACHE_TTL:
return cached[0]
resp = req.get(jwks_uri, timeout=10)
resp.raise_for_status()
keys = resp.json().get("keys", [])
_oidc_jwks_cache[jwks_uri] = (keys, now)
return keys
def _oidc_store_state(state: str, nonce: str):
with _oidc_pending_lock:
now = time.time()
expired = [k for k, v in _oidc_pending.items() if now - v["created"] > _OIDC_STATE_TTL]
for k in expired:
del _oidc_pending[k]
_oidc_pending[state] = {"nonce": nonce, "created": now}
def _oidc_pop_state(state: str) -> dict | None:
with _oidc_pending_lock:
return _oidc_pending.pop(state, None)
def _validate_id_token(id_token: str, issuer: str, client_id: str, nonce: str) -> dict:
from jwt.algorithms import RSAAlgorithm
disco = _oidc_discover(issuer)
jwks = _oidc_get_jwks(disco["jwks_uri"])
header = pyjwt.get_unverified_header(id_token)
kid = header.get("kid")
key_data = None
for k in jwks:
if k.get("kid") == kid:
key_data = k
break
if not key_data:
raise HTTPException(401, "OIDC: chave não encontrada no JWKS")
public_key = RSAAlgorithm.from_jwk(json.dumps(key_data))
claims = pyjwt.decode(id_token, public_key, algorithms=["RS256"], audience=client_id, issuer=issuer)
if claims.get("nonce") != nonce:
raise HTTPException(401, "OIDC: nonce inválido")
return claims