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,13 +2948,18 @@ def _get_adb_connection(cfg: dict):
|
|||||||
params["tcp_connect_timeout"] = 15 # 15s connection timeout
|
params["tcp_connect_timeout"] = 15 # 15s connection timeout
|
||||||
return oracledb.connect(**params)
|
return oracledb.connect(**params)
|
||||||
|
|
||||||
def _resolve_embed_config(oci_config_id: str = None, genai_cfg: dict = None) -> dict:
|
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. Returns dict with oci_config_id, endpoint, genai_region, compartment_id."""
|
"""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:
|
if genai_cfg:
|
||||||
return genai_cfg
|
return genai_cfg
|
||||||
if oci_config_id:
|
if oci_config_id:
|
||||||
with db() as c:
|
with db() as c:
|
||||||
# Try linked genai config first
|
# 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",
|
gc = c.execute("SELECT * FROM genai_configs WHERE oci_config_id=? ORDER BY is_default DESC, created_at DESC",
|
||||||
(oci_config_id,)).fetchone()
|
(oci_config_id,)).fetchone()
|
||||||
if gc: return dict(gc)
|
if gc: return dict(gc)
|
||||||
@@ -2967,11 +2972,16 @@ def _resolve_embed_config(oci_config_id: str = None, genai_cfg: dict = None) ->
|
|||||||
"endpoint": f"https://inference.generativeai.{oc['region']}.oci.oraclecloud.com",
|
"endpoint": f"https://inference.generativeai.{oc['region']}.oci.oraclecloud.com",
|
||||||
"compartment_id": _safe_dec(dict(oc).get("compartment_id") or oc["tenancy_ocid"]),
|
"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:
|
with db() as c:
|
||||||
|
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()
|
gc = c.execute("SELECT * FROM genai_configs ORDER BY is_default DESC, created_at DESC").fetchone()
|
||||||
if gc: return dict(gc)
|
if gc: return dict(gc)
|
||||||
# Or any oci config
|
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()
|
oc = c.execute("SELECT * FROM oci_configs ORDER BY created_at DESC").fetchone()
|
||||||
if oc:
|
if oc:
|
||||||
return {
|
return {
|
||||||
@@ -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",
|
"endpoint": f"https://inference.generativeai.{oc['region']}.oci.oraclecloud.com",
|
||||||
"compartment_id": _safe_dec(dict(oc).get("compartment_id") or oc["tenancy_ocid"]),
|
"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:
|
def _embed_text(text: str, genai_cfg: dict, embedding_model_id: str, max_chars: int = 8000) -> list:
|
||||||
"""Generate embedding using OCI GenAI embed endpoint."""
|
"""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}"})
|
documents.append({"content": current_chunk, "source": filename, "metadata": f"chunk: {chunk_num}"})
|
||||||
return documents
|
return documents
|
||||||
|
|
||||||
def _get_adb_and_genai(vid: str, oci_config_id: str = None):
|
def _get_adb_and_genai(vid: str, oci_config_id: str = None, user_id: str = None):
|
||||||
"""Load ADB config and resolve embed config.
|
"""Load ADB config and resolve embed config (scoped to user_id).
|
||||||
Priority: ADB.genai_config_id → genai by oci_config_id → oci_config directly → any available."""
|
Priority: ADB.genai_config_id → genai by oci_config_id → oci_config directly → user's default."""
|
||||||
with db() as c:
|
with db() as c:
|
||||||
cfg = c.execute("SELECT * FROM adb_vector_configs WHERE id=?", (vid,)).fetchone()
|
cfg = c.execute("SELECT * FROM adb_vector_configs WHERE id=?", (vid,)).fetchone()
|
||||||
if not cfg: raise HTTPException(404, "ADB config not found")
|
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:
|
with db() as c:
|
||||||
row = c.execute("SELECT * FROM genai_configs WHERE id=?", (cfg["genai_config_id"],)).fetchone()
|
row = c.execute("SELECT * FROM genai_configs WHERE id=?", (cfg["genai_config_id"],)).fetchone()
|
||||||
if row: genai_cfg = dict(row)
|
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
|
return cfg, gc
|
||||||
|
|
||||||
@app.get("/api/embeddings/preview/{rid}")
|
@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), {})
|
first = next(csvmod.DictReader(f), {})
|
||||||
extract_date = first.get("extract_date", "")
|
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
|
# Load registered tables for validation
|
||||||
with db() as c:
|
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), {})
|
first = next(csvmod.DictReader(f), {})
|
||||||
extract_date = first.get("extract_date", "")
|
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
|
# Load registered tables for validation
|
||||||
with db() as c:
|
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")
|
if not content.strip(): raise HTTPException(400, "File is empty")
|
||||||
documents = _chunk_text_file(content, f["file_name"])
|
documents = _chunk_text_file(content, f["file_name"])
|
||||||
if not documents: raise HTTPException(400, "No content chunks found")
|
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
|
target_table = req.get("table_name") or None
|
||||||
tenancy = r["tenancy_name"] or "unknown"
|
tenancy = r["tenancy_name"] or "unknown"
|
||||||
task_id = str(uuid.uuid4())
|
task_id = str(uuid.uuid4())
|
||||||
@@ -4726,7 +4736,7 @@ async def embed_upload(adb_config_id: str = Form(...), table_name: str = Form(""
|
|||||||
else:
|
else:
|
||||||
documents = _chunk_text_file(content, file.filename)
|
documents = _chunk_text_file(content, file.filename)
|
||||||
if not documents: raise HTTPException(400, "No content chunks found")
|
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)
|
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")
|
_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}
|
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)
|
documents = _chunk_text_file(content, url)
|
||||||
if not documents:
|
if not documents:
|
||||||
raise HTTPException(400, "Nenhum chunk gerado do conteúdo")
|
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
|
target_table = table_name.strip() or None
|
||||||
bg.add_task(_ingest_documents_task, cfg, gc, documents, u["id"], u["username"], table_name=target_table)
|
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")
|
_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 = []
|
rag_errors = []
|
||||||
for adb_cfg in adb_configs:
|
for adb_cfg in adb_configs:
|
||||||
try:
|
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:
|
except Exception as e:
|
||||||
log.warning(f"Consult: resolve config failed for {adb_cfg['id']}: {e}")
|
log.warning(f"Consult: resolve config failed for {adb_cfg['id']}: {e}")
|
||||||
continue
|
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
|
# Get GenAI config for answering — try saved config first, then auto-resolve from OCI
|
||||||
gc = None
|
gc = None
|
||||||
with db() as c:
|
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:
|
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:
|
if gc_row:
|
||||||
gc = dict(gc_row)
|
gc = dict(gc_row)
|
||||||
else:
|
else:
|
||||||
# Auto-resolve: build GenAI config from OCI credentials + default model
|
# Auto-resolve: build GenAI config from OCI credentials + default model
|
||||||
try:
|
try:
|
||||||
resolved = _resolve_embed_config()
|
resolved = _resolve_embed_config(user_id=u["id"])
|
||||||
gc = {
|
gc = {
|
||||||
"oci_config_id": resolved["oci_config_id"],
|
"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"),
|
"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:
|
try:
|
||||||
with db() as c:
|
with db() as c:
|
||||||
adb_cfgs = c.execute(
|
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()
|
).fetchall()
|
||||||
if not adb_cfgs:
|
if not adb_cfgs:
|
||||||
return {"description": "", "remediation": ""}
|
return {"description": "", "remediation": ""}
|
||||||
@@ -5425,7 +5435,7 @@ def _search_cis_remediation(title: str, rec_num: str, user_id: str) -> dict:
|
|||||||
with db() as c:
|
with db() as c:
|
||||||
row = c.execute("SELECT * FROM genai_configs WHERE id=?", (adb_cfg["genai_config_id"],)).fetchone()
|
row = c.execute("SELECT * FROM genai_configs WHERE id=?", (adb_cfg["genai_config_id"],)).fetchone()
|
||||||
if row: genai_linked = dict(row)
|
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
|
# Search across cisrecom + section-specific tables
|
||||||
all_docs = []
|
all_docs = []
|
||||||
@@ -7993,6 +8003,7 @@ def _chat_start(msg: ChatMsg, u, attachments: list = None, agent_type: str = "ch
|
|||||||
|
|
||||||
genai_cfg = None
|
genai_cfg = None
|
||||||
if msg.genai_config_id:
|
if msg.genai_config_id:
|
||||||
|
_verify_config_access("genai", msg.genai_config_id, u)
|
||||||
with db() as c:
|
with db() as c:
|
||||||
row = c.execute("SELECT * FROM genai_configs WHERE id=?", (msg.genai_config_id,)).fetchone()
|
row = c.execute("SELECT * FROM genai_configs WHERE id=?", (msg.genai_config_id,)).fetchone()
|
||||||
if row:
|
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.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
|
if msg.reasoning_effort is not None: genai_cfg["reasoning_effort"] = msg.reasoning_effort
|
||||||
elif msg.model_id and msg.oci_config_id:
|
elif msg.model_id and msg.oci_config_id:
|
||||||
|
_verify_config_access("oci", msg.oci_config_id, u)
|
||||||
with db() as c:
|
with db() as c:
|
||||||
oci_row = c.execute("SELECT * FROM oci_configs WHERE id=?", (msg.oci_config_id,)).fetchone()
|
oci_row = c.execute("SELECT * FROM oci_configs WHERE id=?", (msg.oci_config_id,)).fetchone()
|
||||||
if not oci_row:
|
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:
|
with db() as c:
|
||||||
row = c.execute("SELECT * FROM genai_configs WHERE id=?", (adb_cfg["genai_config_id"],)).fetchone()
|
row = c.execute("SELECT * FROM genai_configs WHERE id=?", (adb_cfg["genai_config_id"],)).fetchone()
|
||||||
if row: genai_linked = dict(row)
|
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:
|
if not emb_genai:
|
||||||
continue
|
continue
|
||||||
default_model = adb_cfg.get("embedding_model_id", "cohere.embed-v4.0")
|
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:
|
if not gc:
|
||||||
with db() as c:
|
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:
|
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 row: gc = dict(row)
|
||||||
if not gc:
|
if not gc:
|
||||||
raise HTTPException(400, "Nenhuma configuração GenAI disponível")
|
raise HTTPException(400, "Nenhuma configuração GenAI disponível")
|
||||||
|
|||||||
Reference in New Issue
Block a user