fix: scope GenAI/OCI config resolution to user — close cross-user fallback leaks
- _resolve_embed_config: new user_id param, scopes all fallback queries to user's own configs - _get_adb_and_genai: propagates user_id to config resolution - _chat_start: verify ownership of genai_config_id and oci_config_id - TF prompt generator: scope default GenAI query to user_id - Consult embeddings: scope GenAI resolution to user_id - _search_cis_remediation: include global ADB configs (is_global=1)
This commit is contained in:
@@ -2948,15 +2948,20 @@ def _get_adb_connection(cfg: dict):
|
||||
params["tcp_connect_timeout"] = 15 # 15s connection timeout
|
||||
return oracledb.connect(**params)
|
||||
|
||||
def _resolve_embed_config(oci_config_id: str = None, genai_cfg: dict = None) -> dict:
|
||||
"""Resolve embedding config from genai_cfg or oci_config. Returns dict with oci_config_id, endpoint, genai_region, compartment_id."""
|
||||
def _resolve_embed_config(oci_config_id: str = None, genai_cfg: dict = None, user_id: str = None) -> dict:
|
||||
"""Resolve embedding config from genai_cfg or oci_config. Scoped to user_id when provided.
|
||||
Returns dict with oci_config_id, endpoint, genai_region, compartment_id."""
|
||||
if genai_cfg:
|
||||
return genai_cfg
|
||||
if oci_config_id:
|
||||
with db() as c:
|
||||
# Try linked genai config first
|
||||
gc = c.execute("SELECT * FROM genai_configs WHERE oci_config_id=? ORDER BY is_default DESC, created_at DESC",
|
||||
(oci_config_id,)).fetchone()
|
||||
# Try linked genai config first (scoped to user if provided)
|
||||
if user_id:
|
||||
gc = c.execute("SELECT * FROM genai_configs WHERE oci_config_id=? AND user_id=? ORDER BY is_default DESC, created_at DESC",
|
||||
(oci_config_id, user_id)).fetchone()
|
||||
else:
|
||||
gc = c.execute("SELECT * FROM genai_configs WHERE oci_config_id=? ORDER BY is_default DESC, created_at DESC",
|
||||
(oci_config_id,)).fetchone()
|
||||
if gc: return dict(gc)
|
||||
# Fallback: build from oci_config directly
|
||||
oc = c.execute("SELECT * FROM oci_configs WHERE id=?", (oci_config_id,)).fetchone()
|
||||
@@ -2967,12 +2972,17 @@ def _resolve_embed_config(oci_config_id: str = None, genai_cfg: dict = None) ->
|
||||
"endpoint": f"https://inference.generativeai.{oc['region']}.oci.oraclecloud.com",
|
||||
"compartment_id": _safe_dec(dict(oc).get("compartment_id") or oc["tenancy_ocid"]),
|
||||
}
|
||||
# Last resort: any genai config
|
||||
# Last resort: user's own genai/oci config (never cross-user)
|
||||
with db() as c:
|
||||
gc = c.execute("SELECT * FROM genai_configs ORDER BY is_default DESC, created_at DESC").fetchone()
|
||||
if user_id:
|
||||
gc = c.execute("SELECT * FROM genai_configs WHERE user_id=? ORDER BY is_default DESC, created_at DESC", (user_id,)).fetchone()
|
||||
else:
|
||||
gc = c.execute("SELECT * FROM genai_configs ORDER BY is_default DESC, created_at DESC").fetchone()
|
||||
if gc: return dict(gc)
|
||||
# Or any oci config
|
||||
oc = c.execute("SELECT * FROM oci_configs ORDER BY created_at DESC").fetchone()
|
||||
if user_id:
|
||||
oc = c.execute("SELECT * FROM oci_configs WHERE user_id=? ORDER BY created_at DESC", (user_id,)).fetchone()
|
||||
else:
|
||||
oc = c.execute("SELECT * FROM oci_configs ORDER BY created_at DESC").fetchone()
|
||||
if oc:
|
||||
return {
|
||||
"oci_config_id": oc["id"],
|
||||
@@ -2980,7 +2990,7 @@ def _resolve_embed_config(oci_config_id: str = None, genai_cfg: dict = None) ->
|
||||
"endpoint": f"https://inference.generativeai.{oc['region']}.oci.oraclecloud.com",
|
||||
"compartment_id": _safe_dec(dict(oc).get("compartment_id") or oc["tenancy_ocid"]),
|
||||
}
|
||||
raise HTTPException(400, "Nenhuma credencial OCI configurada para gerar embeddings.")
|
||||
raise HTTPException(400, "Nenhuma credencial OCI/GenAI configurada para gerar embeddings.")
|
||||
|
||||
def _embed_text(text: str, genai_cfg: dict, embedding_model_id: str, max_chars: int = 8000) -> list:
|
||||
"""Generate embedding using OCI GenAI embed endpoint."""
|
||||
@@ -4128,9 +4138,9 @@ def _chunk_text_file(text: str, filename: str, chunk_size: int = 1000, overlap:
|
||||
documents.append({"content": current_chunk, "source": filename, "metadata": f"chunk: {chunk_num}"})
|
||||
return documents
|
||||
|
||||
def _get_adb_and_genai(vid: str, oci_config_id: str = None):
|
||||
"""Load ADB config and resolve embed config.
|
||||
Priority: ADB.genai_config_id → genai by oci_config_id → oci_config directly → any available."""
|
||||
def _get_adb_and_genai(vid: str, oci_config_id: str = None, user_id: str = None):
|
||||
"""Load ADB config and resolve embed config (scoped to user_id).
|
||||
Priority: ADB.genai_config_id → genai by oci_config_id → oci_config directly → user's default."""
|
||||
with db() as c:
|
||||
cfg = c.execute("SELECT * FROM adb_vector_configs WHERE id=?", (vid,)).fetchone()
|
||||
if not cfg: raise HTTPException(404, "ADB config not found")
|
||||
@@ -4140,7 +4150,7 @@ def _get_adb_and_genai(vid: str, oci_config_id: str = None):
|
||||
with db() as c:
|
||||
row = c.execute("SELECT * FROM genai_configs WHERE id=?", (cfg["genai_config_id"],)).fetchone()
|
||||
if row: genai_cfg = dict(row)
|
||||
gc = _resolve_embed_config(oci_config_id=oci_config_id, genai_cfg=genai_cfg)
|
||||
gc = _resolve_embed_config(oci_config_id=oci_config_id, genai_cfg=genai_cfg, user_id=user_id or cfg.get("user_id"))
|
||||
return cfg, gc
|
||||
|
||||
@app.get("/api/embeddings/preview/{rid}")
|
||||
@@ -4360,7 +4370,7 @@ async def embed_report(rid: str, req: dict, bg: BackgroundTasks, u=Depends(requi
|
||||
first = next(csvmod.DictReader(f), {})
|
||||
extract_date = first.get("extract_date", "")
|
||||
|
||||
cfg, gc = _get_adb_and_genai(vid, oci_config_id=r["config_id"] if r["config_id"] else None)
|
||||
cfg, gc = _get_adb_and_genai(vid, oci_config_id=r["config_id"] if r["config_id"] else None, user_id=u["id"])
|
||||
|
||||
# Load registered tables for validation
|
||||
with db() as c:
|
||||
@@ -4540,7 +4550,7 @@ async def embed_report_section(rid: str, req: dict, bg: BackgroundTasks, u=Depen
|
||||
first = next(csvmod.DictReader(f), {})
|
||||
extract_date = first.get("extract_date", "")
|
||||
|
||||
cfg, gc = _get_adb_and_genai(vid, oci_config_id=r["config_id"] if r["config_id"] else None)
|
||||
cfg, gc = _get_adb_and_genai(vid, oci_config_id=r["config_id"] if r["config_id"] else None, user_id=u["id"])
|
||||
|
||||
# Load registered tables for validation
|
||||
with db() as c:
|
||||
@@ -4667,7 +4677,7 @@ async def embed_report_file(rid: str, fid: str, req: dict, bg: BackgroundTasks,
|
||||
if not content.strip(): raise HTTPException(400, "File is empty")
|
||||
documents = _chunk_text_file(content, f["file_name"])
|
||||
if not documents: raise HTTPException(400, "No content chunks found")
|
||||
cfg, gc = _get_adb_and_genai(vid, oci_config_id=r["config_id"] if r["config_id"] else None)
|
||||
cfg, gc = _get_adb_and_genai(vid, oci_config_id=r["config_id"] if r["config_id"] else None, user_id=u["id"])
|
||||
target_table = req.get("table_name") or None
|
||||
tenancy = r["tenancy_name"] or "unknown"
|
||||
task_id = str(uuid.uuid4())
|
||||
@@ -4726,7 +4736,7 @@ async def embed_upload(adb_config_id: str = Form(...), table_name: str = Form(""
|
||||
else:
|
||||
documents = _chunk_text_file(content, file.filename)
|
||||
if not documents: raise HTTPException(400, "No content chunks found")
|
||||
cfg, gc = _get_adb_and_genai(adb_config_id)
|
||||
cfg, gc = _get_adb_and_genai(adb_config_id, user_id=u["id"])
|
||||
bg.add_task(_ingest_documents_task, cfg, gc, documents, u["id"], u["username"], table_name=target_table)
|
||||
_audit(u["id"], u["username"], "embed_upload", file.filename, f"{len(documents)} chunks")
|
||||
return {"ok": True, "message": f"Embedding de {len(documents)} chunks iniciado", "chunks": len(documents), "filename": file.filename}
|
||||
@@ -4772,7 +4782,7 @@ async def embed_upload_url(
|
||||
documents = _chunk_text_file(content, url)
|
||||
if not documents:
|
||||
raise HTTPException(400, "Nenhum chunk gerado do conteúdo")
|
||||
cfg, gc = _get_adb_and_genai(adb_config_id)
|
||||
cfg, gc = _get_adb_and_genai(adb_config_id, user_id=u["id"])
|
||||
target_table = table_name.strip() or None
|
||||
bg.add_task(_ingest_documents_task, cfg, gc, documents, u["id"], u["username"], table_name=target_table)
|
||||
_audit(u["id"], u["username"], "embed_url", url, f"{len(documents)} chunks")
|
||||
@@ -4813,7 +4823,7 @@ async def consult_embeddings(req: ConsultQuery, u=Depends(current_user)):
|
||||
rag_errors = []
|
||||
for adb_cfg in adb_configs:
|
||||
try:
|
||||
emb_genai = _resolve_embed_config(oci_config_id=adb_cfg.get("oci_config_id"))
|
||||
emb_genai = _resolve_embed_config(oci_config_id=adb_cfg.get("oci_config_id"), user_id=u["id"])
|
||||
except Exception as e:
|
||||
log.warning(f"Consult: resolve config failed for {adb_cfg['id']}: {e}")
|
||||
continue
|
||||
@@ -4869,15 +4879,15 @@ async def consult_embeddings(req: ConsultQuery, u=Depends(current_user)):
|
||||
# Get GenAI config for answering — try saved config first, then auto-resolve from OCI
|
||||
gc = None
|
||||
with db() as c:
|
||||
gc_row = c.execute("SELECT * FROM genai_configs WHERE is_default=1 ORDER BY created_at DESC").fetchone()
|
||||
gc_row = c.execute("SELECT * FROM genai_configs WHERE user_id=? AND is_default=1 ORDER BY created_at DESC", (u["id"],)).fetchone()
|
||||
if not gc_row:
|
||||
gc_row = c.execute("SELECT * FROM genai_configs ORDER BY created_at DESC").fetchone()
|
||||
gc_row = c.execute("SELECT * FROM genai_configs WHERE user_id=? ORDER BY created_at DESC", (u["id"],)).fetchone()
|
||||
if gc_row:
|
||||
gc = dict(gc_row)
|
||||
else:
|
||||
# Auto-resolve: build GenAI config from OCI credentials + default model
|
||||
try:
|
||||
resolved = _resolve_embed_config()
|
||||
resolved = _resolve_embed_config(user_id=u["id"])
|
||||
gc = {
|
||||
"oci_config_id": resolved["oci_config_id"],
|
||||
"endpoint": resolved.get("endpoint", f"https://inference.generativeai.{resolved.get('genai_region','us-ashburn-1')}.oci.oraclecloud.com"),
|
||||
@@ -5404,7 +5414,7 @@ def _search_cis_remediation(title: str, rec_num: str, user_id: str) -> dict:
|
||||
try:
|
||||
with db() as c:
|
||||
adb_cfgs = c.execute(
|
||||
"SELECT * FROM adb_vector_configs WHERE user_id=? AND is_active=1", (user_id,)
|
||||
"SELECT * FROM adb_vector_configs WHERE is_active=1 AND (user_id=? OR is_global=1)", (user_id,)
|
||||
).fetchall()
|
||||
if not adb_cfgs:
|
||||
return {"description": "", "remediation": ""}
|
||||
@@ -5425,7 +5435,7 @@ def _search_cis_remediation(title: str, rec_num: str, user_id: str) -> dict:
|
||||
with db() as c:
|
||||
row = c.execute("SELECT * FROM genai_configs WHERE id=?", (adb_cfg["genai_config_id"],)).fetchone()
|
||||
if row: genai_linked = dict(row)
|
||||
emb_genai = _resolve_embed_config(genai_cfg=genai_linked)
|
||||
emb_genai = _resolve_embed_config(genai_cfg=genai_linked, user_id=user_id)
|
||||
|
||||
# Search across cisrecom + section-specific tables
|
||||
all_docs = []
|
||||
@@ -7993,6 +8003,7 @@ def _chat_start(msg: ChatMsg, u, attachments: list = None, agent_type: str = "ch
|
||||
|
||||
genai_cfg = None
|
||||
if msg.genai_config_id:
|
||||
_verify_config_access("genai", msg.genai_config_id, u)
|
||||
with db() as c:
|
||||
row = c.execute("SELECT * FROM genai_configs WHERE id=?", (msg.genai_config_id,)).fetchone()
|
||||
if row:
|
||||
@@ -8005,6 +8016,7 @@ def _chat_start(msg: ChatMsg, u, attachments: list = None, agent_type: str = "ch
|
||||
if msg.presence_penalty is not None: genai_cfg["presence_penalty"] = msg.presence_penalty
|
||||
if msg.reasoning_effort is not None: genai_cfg["reasoning_effort"] = msg.reasoning_effort
|
||||
elif msg.model_id and msg.oci_config_id:
|
||||
_verify_config_access("oci", msg.oci_config_id, u)
|
||||
with db() as c:
|
||||
oci_row = c.execute("SELECT * FROM oci_configs WHERE id=?", (msg.oci_config_id,)).fetchone()
|
||||
if not oci_row:
|
||||
@@ -8101,7 +8113,7 @@ async def _chat_background(mid: str, sid: str, msg: ChatMsg, user: dict, genai_c
|
||||
with db() as c:
|
||||
row = c.execute("SELECT * FROM genai_configs WHERE id=?", (adb_cfg["genai_config_id"],)).fetchone()
|
||||
if row: genai_linked = dict(row)
|
||||
emb_genai = _resolve_embed_config(oci_config_id=active_oci_for_rag, genai_cfg=genai_linked)
|
||||
emb_genai = _resolve_embed_config(oci_config_id=active_oci_for_rag, genai_cfg=genai_linked, user_id=user["id"])
|
||||
if not emb_genai:
|
||||
continue
|
||||
default_model = adb_cfg.get("embedding_model_id", "cohere.embed-v4.0")
|
||||
@@ -8922,9 +8934,9 @@ async def tf_generate_prompt(req: TfPromptReq, bg: BackgroundTasks, u=Depends(cu
|
||||
}
|
||||
if not gc:
|
||||
with db() as c:
|
||||
row = c.execute("SELECT * FROM genai_configs WHERE is_default=1").fetchone()
|
||||
row = c.execute("SELECT * FROM genai_configs WHERE user_id=? AND is_default=1", (u["id"],)).fetchone()
|
||||
if not row:
|
||||
row = c.execute("SELECT * FROM genai_configs LIMIT 1").fetchone()
|
||||
row = c.execute("SELECT * FROM genai_configs WHERE user_id=? LIMIT 1", (u["id"],)).fetchone()
|
||||
if row: gc = dict(row)
|
||||
if not gc:
|
||||
raise HTTPException(400, "Nenhuma configuração GenAI disponível")
|
||||
|
||||
Reference in New Issue
Block a user