Phase 1 securite: permission checks sur tous les routers

- auth: verification is_active au login (compte desactive = bloque)
- settings: enforcement backend can_edit(settings) + role/section
- servers: can_view/can_edit(servers) sur toutes les routes
- planning: can_view/can_edit(planning) sur toutes les routes
- specifics: can_view/can_edit(specifics) sur toutes les routes
- contacts: rattache au module servers (can_view/can_edit)
- campaigns: can_view/can_edit(campaigns) sur toutes les routes manquantes
- audit/audit_full: can_view/can_edit(audit) sur toutes les routes
- qualys: can_view/can_edit(qualys) sur toutes les routes
- safe_patching: perm checks + authentification sur SSE stream
- quickwin: can_view/can_edit(campaigns|quickwin) sur toutes les routes

97 points d'injection securises, 0 route sans controle

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Khalid MOUTAOUAKIL 2026-04-08 16:46:05 +02:00
parent 5cc10c5b6c
commit 13290c1ebb
12 changed files with 266 additions and 12 deletions

View File

@ -18,6 +18,9 @@ async def audit_page(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "audit"):
return RedirectResponse(url="/dashboard")
where = ["1=1"] where = ["1=1"]
params = {} params = {}
@ -223,6 +226,9 @@ async def audit_realtime_save(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "audit"):
return RedirectResponse(url="/audit")
results = getattr(request.app.state, "last_audit_results", None) results = getattr(request.app.state, "last_audit_results", None)
if not results: if not results:
@ -238,6 +244,9 @@ async def audit_export_csv(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "audit"):
return RedirectResponse(url="/audit")
where = ["1=1"] where = ["1=1"]
params = {} params = {}

View File

@ -4,7 +4,7 @@ from fastapi import APIRouter, Request, Depends, UploadFile, File
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from sqlalchemy import text from sqlalchemy import text
from ..dependencies import get_db, get_current_user, get_user_perms, can_view, base_context from ..dependencies import get_db, get_current_user, get_user_perms, can_view, can_edit, base_context
from ..services.server_audit_full_service import ( from ..services.server_audit_full_service import (
import_json_report, get_latest_audits, get_audit_detail, import_json_report, get_latest_audits, get_audit_detail,
get_flow_map, get_flow_map_for_server, get_app_map, get_flow_map, get_flow_map_for_server, get_app_map,
@ -208,6 +208,9 @@ async def audit_full_import(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "audit"):
return RedirectResponse(url="/audit-full")
try: try:
content = await file.read() content = await file.read()
@ -229,6 +232,9 @@ async def audit_full_patching(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "audit"):
return RedirectResponse(url="/dashboard")
year = int(request.query_params.get("year", "2026")) year = int(request.query_params.get("year", "2026"))
search = request.query_params.get("q", "").strip() search = request.query_params.get("q", "").strip()
@ -413,6 +419,9 @@ async def patching_export_csv(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "audit"):
return RedirectResponse(url="/audit-full")
import io, csv import io, csv
year = int(request.query_params.get("year", "2026")) year = int(request.query_params.get("year", "2026"))
@ -484,6 +493,9 @@ async def audit_full_export_csv(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "audit"):
return RedirectResponse(url="/audit-full")
import io, csv import io, csv
filtre = request.query_params.get("filter", "") filtre = request.query_params.get("filter", "")
@ -558,6 +570,9 @@ async def audit_full_flow_map(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "audit"):
return RedirectResponse(url="/audit-full")
domain_filter = request.query_params.get("domain", "") domain_filter = request.query_params.get("domain", "")
server_filter = request.query_params.get("server", "").strip() server_filter = request.query_params.get("server", "").strip()
@ -648,6 +663,9 @@ async def audit_full_detail(request: Request, audit_id: int, db=Depends(get_db))
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "audit"):
return RedirectResponse(url="/audit-full")
audit = get_audit_detail(db, audit_id) audit = get_audit_detail(db, audit_id)
if not audit: if not audit:

View File

@ -18,7 +18,7 @@ async def login_page(request: Request):
@router.post("/login") @router.post("/login")
async def login(request: Request, username: str = Form(...), password: str = Form(...), db=Depends(get_db)): async def login(request: Request, username: str = Form(...), password: str = Form(...), db=Depends(get_db)):
row = db.execute(text("SELECT id, username, password_hash, role FROM users WHERE LOWER(username) = LOWER(:u)"), row = db.execute(text("SELECT id, username, password_hash, role, is_active FROM users WHERE LOWER(username) = LOWER(:u)"),
{"u": username}).fetchone() {"u": username}).fetchone()
if not row: if not row:
log_login_failed(db, request, username) log_login_failed(db, request, username)
@ -26,6 +26,12 @@ async def login(request: Request, username: str = Form(...), password: str = For
return templates.TemplateResponse("login.html", { return templates.TemplateResponse("login.html", {
"request": request, "app_name": APP_NAME, "version": APP_VERSION, "error": "Utilisateur inconnu" "request": request, "app_name": APP_NAME, "version": APP_VERSION, "error": "Utilisateur inconnu"
}) })
if not row.is_active:
log_login_failed(db, request, username)
db.commit()
return templates.TemplateResponse("login.html", {
"request": request, "app_name": APP_NAME, "version": APP_VERSION, "error": "Compte desactive"
})
try: try:
ok = verify_password(password, row.password_hash) ok = verify_password(password, row.password_hash)
except Exception: except Exception:

View File

@ -85,7 +85,10 @@ async def campaign_preview(request: Request, db=Depends(get_db),
year: int = Query(...), week: int = Query(...)): year: int = Query(...), week: int = Query(...)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return HTMLResponse("<p>Non autorise</p>") return HTMLResponse("<p>Non autorise</p>", status_code=401)
perms = get_user_perms(db, user)
if not can_view(perms, "campaigns"):
return HTMLResponse("<p>Acces interdit</p>", status_code=403)
servers, planning = get_servers_for_planning(db, year, week) servers, planning = get_servers_for_planning(db, year, week)
scope = ", ".join(set(f"{p.domain_name} ({p.env_scope})" for p in planning if p.domain_code)) scope = ", ".join(set(f"{p.domain_name} ({p.env_scope})" for p in planning if p.domain_code))
return templates.TemplateResponse("partials/campaign_preview.html", { return templates.TemplateResponse("partials/campaign_preview.html", {
@ -99,6 +102,9 @@ async def campaign_create(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return RedirectResponse(url="/campaigns")
form = await request.form() form = await request.form()
year = int(form.get("year", datetime.now().year)) year = int(form.get("year", datetime.now().year))
week = int(form.get("week_number", 0)) week = int(form.get("week_number", 0))
@ -128,6 +134,9 @@ async def campaign_detail(request: Request, campaign_id: int, db=Depends(get_db)
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "campaigns"):
return RedirectResponse(url="/dashboard")
campaign = get_campaign(db, campaign_id) campaign = get_campaign(db, campaign_id)
if not campaign: if not campaign:
return RedirectResponse(url="/campaigns") return RedirectResponse(url="/campaigns")
@ -212,6 +221,9 @@ async def session_prereq(request: Request, session_id: int, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "campaigns"):
return RedirectResponse(url="/campaigns")
validate_prereq(db, session_id, prereq_ssh, prereq_satellite, validate_prereq(db, session_id, prereq_ssh, prereq_satellite,
rollback_method or None, rollback_justif, user.get("sub")) rollback_method or None, rollback_justif, user.get("sub"))
row = db.execute(text("SELECT campaign_id FROM patch_sessions WHERE id = :id"), row = db.execute(text("SELECT campaign_id FROM patch_sessions WHERE id = :id"),
@ -224,6 +236,9 @@ async def campaign_check_prereqs(request: Request, campaign_id: int, db=Depends(
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return RedirectResponse(url=f"/campaigns/{campaign_id}")
checked, auto_excluded = check_prereqs_campaign(db, campaign_id) checked, auto_excluded = check_prereqs_campaign(db, campaign_id)
log_prereq_check(db, request, user, campaign_id, checked, auto_excluded) log_prereq_check(db, request, user, campaign_id, checked, auto_excluded)
db.commit() db.commit()
@ -235,6 +250,9 @@ async def session_check_prereq(request: Request, session_id: int, db=Depends(get
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "campaigns"):
return RedirectResponse(url="/campaigns")
check_single_prereq(db, session_id) check_single_prereq(db, session_id)
row = db.execute(text("SELECT campaign_id FROM patch_sessions WHERE id = :id"), row = db.execute(text("SELECT campaign_id FROM patch_sessions WHERE id = :id"),
{"id": session_id}).fetchone() {"id": session_id}).fetchone()
@ -247,6 +265,9 @@ async def session_exclude(request: Request, session_id: int, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return RedirectResponse(url="/campaigns")
exclude_session(db, session_id, reason, detail, user.get("sub")) exclude_session(db, session_id, reason, detail, user.get("sub"))
row = db.execute(text("SELECT campaign_id FROM patch_sessions WHERE id = :id"), row = db.execute(text("SELECT campaign_id FROM patch_sessions WHERE id = :id"),
{"id": session_id}).fetchone() {"id": session_id}).fetchone()
@ -258,6 +279,9 @@ async def session_restore(request: Request, session_id: int, db=Depends(get_db))
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return RedirectResponse(url="/campaigns")
restore_session(db, session_id) restore_session(db, session_id)
row = db.execute(text("SELECT campaign_id FROM patch_sessions WHERE id = :id"), row = db.execute(text("SELECT campaign_id FROM patch_sessions WHERE id = :id"),
{"id": session_id}).fetchone() {"id": session_id}).fetchone()
@ -272,6 +296,9 @@ async def session_take(request: Request, session_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "campaigns"):
return RedirectResponse(url="/campaigns")
row = db.execute(text("SELECT campaign_id, intervenant_id, forced_assignment FROM patch_sessions WHERE id = :id"), row = db.execute(text("SELECT campaign_id, intervenant_id, forced_assignment FROM patch_sessions WHERE id = :id"),
{"id": session_id}).fetchone() {"id": session_id}).fetchone()
if row.intervenant_id: if row.intervenant_id:
@ -292,6 +319,9 @@ async def session_release(request: Request, session_id: int, db=Depends(get_db))
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "campaigns"):
return RedirectResponse(url="/campaigns")
if is_forced(db, session_id): if is_forced(db, session_id):
row = db.execute(text("SELECT campaign_id FROM patch_sessions WHERE id = :id"), row = db.execute(text("SELECT campaign_id FROM patch_sessions WHERE id = :id"),
{"id": session_id}).fetchone() {"id": session_id}).fetchone()
@ -309,6 +339,9 @@ async def session_assign(request: Request, session_id: int, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return RedirectResponse(url="/campaigns")
oid = int(operator_id) if operator_id else None oid = int(operator_id) if operator_id else None
is_forced_flag = forced == "on" is_forced_flag = forced == "on"
if oid: if oid:
@ -329,6 +362,9 @@ async def set_op_limit(request: Request, campaign_id: int, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return RedirectResponse(url=f"/campaigns/{campaign_id}")
set_operator_limit(db, campaign_id, operator_id, max_servers, note or None) set_operator_limit(db, campaign_id, operator_id, max_servers, note or None)
return RedirectResponse(url=f"/campaigns/{campaign_id}?msg=limit_set", status_code=303) return RedirectResponse(url=f"/campaigns/{campaign_id}?msg=limit_set", status_code=303)
@ -375,6 +411,9 @@ async def assignment_add(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return RedirectResponse(url="/assignments")
try: try:
db.execute(text(""" db.execute(text("""
INSERT INTO default_assignments (rule_type, rule_value, user_id, priority, note) INSERT INTO default_assignments (rule_type, rule_value, user_id, priority, note)
@ -393,6 +432,9 @@ async def assignment_delete(request: Request, rule_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return RedirectResponse(url="/assignments")
db.execute(text("DELETE FROM default_assignments WHERE id = :id"), {"id": rule_id}) db.execute(text("DELETE FROM default_assignments WHERE id = :id"), {"id": rule_id})
db.commit() db.commit()
return RedirectResponse(url="/assignments?msg=deleted", status_code=303) return RedirectResponse(url="/assignments?msg=deleted", status_code=303)
@ -406,6 +448,9 @@ async def bulk_take(request: Request, campaign_id: int, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "campaigns"):
return RedirectResponse(url="/campaigns")
ids = [int(x) for x in session_ids.split(",") if x.strip().isdigit()] ids = [int(x) for x in session_ids.split(",") if x.strip().isdigit()]
limit = get_operator_limit(db, campaign_id, user.get("uid")) limit = get_operator_limit(db, campaign_id, user.get("uid"))
current = get_operator_count(db, campaign_id, user.get("uid")) current = get_operator_count(db, campaign_id, user.get("uid"))
@ -461,6 +506,9 @@ async def session_schedule(request: Request, session_id: int, db=Depends(get_db)
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return RedirectResponse(url="/campaigns")
update_session_schedule(db, session_id, date_prevue or None, heure_prevue or None) update_session_schedule(db, session_id, date_prevue or None, heure_prevue or None)
row = db.execute(text("SELECT campaign_id FROM patch_sessions WHERE id = :id"), row = db.execute(text("SELECT campaign_id FROM patch_sessions WHERE id = :id"),
{"id": session_id}).fetchone() {"id": session_id}).fetchone()

View File

@ -43,6 +43,9 @@ async def contacts_page(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "servers"):
return RedirectResponse(url="/dashboard")
where = ["1=1"] where = ["1=1"]
params = {} params = {}
@ -170,6 +173,9 @@ async def contact_add(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "servers"):
return RedirectResponse(url="/contacts")
try: try:
db.execute(text(""" db.execute(text("""
INSERT INTO contacts (name, email, role, is_active) INSERT INTO contacts (name, email, role, is_active)
@ -188,6 +194,9 @@ async def contact_edit(request: Request, contact_id: int, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "servers"):
return RedirectResponse(url="/contacts")
updates = []; params = {"id": contact_id} updates = []; params = {"id": contact_id}
if name: updates.append("name = :n"); params["n"] = name if name: updates.append("name = :n"); params["n"] = name
if email: updates.append("email = :e"); params["e"] = email.lower() if email: updates.append("email = :e"); params["e"] = email.lower()
@ -203,6 +212,9 @@ async def contact_toggle(request: Request, contact_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "servers"):
return RedirectResponse(url="/contacts")
db.execute(text("UPDATE contacts SET is_active = NOT is_active WHERE id = :id"), {"id": contact_id}) db.execute(text("UPDATE contacts SET is_active = NOT is_active WHERE id = :id"), {"id": contact_id})
db.commit() db.commit()
return RedirectResponse(url="/contacts?msg=toggled", status_code=303) return RedirectResponse(url="/contacts?msg=toggled", status_code=303)
@ -215,6 +227,9 @@ async def scope_add(request: Request, contact_id: int, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "servers"):
return RedirectResponse(url="/contacts")
try: try:
db.execute(text(""" db.execute(text("""
INSERT INTO contact_scopes (contact_id, scope_type, scope_value, env_scope) INSERT INTO contact_scopes (contact_id, scope_type, scope_value, env_scope)
@ -231,6 +246,9 @@ async def scope_delete(request: Request, scope_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "servers"):
return RedirectResponse(url="/contacts")
db.execute(text("DELETE FROM contact_scopes WHERE id = :id"), {"id": scope_id}) db.execute(text("DELETE FROM contact_scopes WHERE id = :id"), {"id": scope_id})
db.commit() db.commit()
return RedirectResponse(url="/contacts?msg=scope_deleted", status_code=303) return RedirectResponse(url="/contacts?msg=scope_deleted", status_code=303)
@ -241,6 +259,9 @@ async def contact_delete(request: Request, contact_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "servers"):
return RedirectResponse(url="/contacts")
db.execute(text("DELETE FROM contact_scopes WHERE contact_id = :cid"), {"cid": contact_id}) db.execute(text("DELETE FROM contact_scopes WHERE contact_id = :cid"), {"cid": contact_id})
db.execute(text("DELETE FROM contacts WHERE id = :cid"), {"cid": contact_id}) db.execute(text("DELETE FROM contacts WHERE id = :cid"), {"cid": contact_id})
db.commit() db.commit()

View File

@ -84,6 +84,8 @@ async def planning_page(request: Request, db=Depends(get_db),
next_week = 1 next_week = 1
perms = get_user_perms(db, user) perms = get_user_perms(db, user)
if not can_view(perms, "planning"):
return RedirectResponse(url="/dashboard")
return templates.TemplateResponse("planning.html", { return templates.TemplateResponse("planning.html", {
"request": request, "user": user, "perms": perms, "app_name": APP_NAME, "request": request, "user": user, "perms": perms, "app_name": APP_NAME,
"year": year, "domains": domains, "grid": grid, "year": year, "domains": domains, "grid": grid,
@ -104,6 +106,9 @@ async def planning_add(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "planning"):
return RedirectResponse(url="/planning")
y = int(year) y = int(year)
wn = int(week_number) if week_number else 0 wn = int(week_number) if week_number else 0
@ -146,6 +151,9 @@ async def planning_edit(request: Request, entry_id: int, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "planning"):
return RedirectResponse(url="/planning")
row = db.execute(text("SELECT year FROM patch_planning WHERE id = :id"), {"id": entry_id}).fetchone() row = db.execute(text("SELECT year FROM patch_planning WHERE id = :id"), {"id": entry_id}).fetchone()
cyc = int(cycle) if cycle.strip() else None cyc = int(cycle) if cycle.strip() else None
db.execute(text(""" db.execute(text("""
@ -163,6 +171,9 @@ async def planning_delete(request: Request, entry_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "planning"):
return RedirectResponse(url="/planning")
row = db.execute(text("SELECT year FROM patch_planning WHERE id = :id"), {"id": entry_id}).fetchone() row = db.execute(text("SELECT year FROM patch_planning WHERE id = :id"), {"id": entry_id}).fetchone()
db.execute(text("DELETE FROM patch_planning WHERE id = :id"), {"id": entry_id}) db.execute(text("DELETE FROM patch_planning WHERE id = :id"), {"id": entry_id})
db.commit() db.commit()
@ -177,6 +188,9 @@ async def planning_duplicate(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "planning"):
return RedirectResponse(url="/planning")
# Verifier que l'annee cible est vide # Verifier que l'annee cible est vide
existing = db.execute(text("SELECT COUNT(*) FROM patch_planning WHERE year = :y"), existing = db.execute(text("SELECT COUNT(*) FROM patch_planning WHERE year = :y"),

View File

@ -168,6 +168,9 @@ async def qualys_tags_resync(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "qualys"):
return RedirectResponse(url="/qualys/tags")
result = resync_all_tags(db) result = resync_all_tags(db)
msg = "resync_ok" if result["ok"] else "resync_ko" msg = "resync_ok" if result["ok"] else "resync_ko"
return RedirectResponse(url=f"/qualys/tags?msg={msg}", status_code=303) return RedirectResponse(url=f"/qualys/tags?msg={msg}", status_code=303)
@ -179,6 +182,9 @@ async def qualys_tag_create(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "qualys"):
return RedirectResponse(url="/qualys/tags")
result = create_tag_api(db, tag_name.strip()) result = create_tag_api(db, tag_name.strip())
msg = "created" if result["ok"] else "create_error" msg = "created" if result["ok"] else "create_error"
return RedirectResponse(url=f"/qualys/tags?msg={msg}", status_code=303) return RedirectResponse(url=f"/qualys/tags?msg={msg}", status_code=303)
@ -189,6 +195,9 @@ async def qualys_tag_delete(request: Request, tag_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "qualys"):
return RedirectResponse(url="/qualys/tags")
result = delete_tag_api(db, tag_id) result = delete_tag_api(db, tag_id)
msg = "deleted" if result["ok"] else "delete_error" msg = "deleted" if result["ok"] else "delete_error"
return RedirectResponse(url=f"/qualys/tags?msg={msg}", status_code=303) return RedirectResponse(url=f"/qualys/tags?msg={msg}", status_code=303)
@ -200,6 +209,9 @@ async def qualys_asset_tag_add(request: Request, asset_id: int, db=Depends(get_d
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "qualys"):
return RedirectResponse(url="/qualys/tags")
result = add_tag_to_asset_api(db, asset_id, int(tag_id)) result = add_tag_to_asset_api(db, asset_id, int(tag_id))
color = "text-cyber-green" if result["ok"] else "text-cyber-red" color = "text-cyber-green" if result["ok"] else "text-cyber-red"
return HTMLResponse(f'<span class="text-xs {color}">{result["msg"]}</span>') return HTMLResponse(f'<span class="text-xs {color}">{result["msg"]}</span>')
@ -211,6 +223,9 @@ async def qualys_asset_tag_remove(request: Request, asset_id: int, db=Depends(ge
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "qualys"):
return RedirectResponse(url="/qualys/tags")
result = remove_tag_from_asset_api(db, asset_id, int(tag_id)) result = remove_tag_from_asset_api(db, asset_id, int(tag_id))
color = "text-cyber-green" if result["ok"] else "text-cyber-red" color = "text-cyber-green" if result["ok"] else "text-cyber-red"
return HTMLResponse(f'<span class="text-xs {color}">{result["msg"]}</span>') return HTMLResponse(f'<span class="text-xs {color}">{result["msg"]}</span>')
@ -228,6 +243,9 @@ async def qualys_bulk_add_tag(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "qualys"):
return RedirectResponse(url="/qualys/tags")
form = await request.form() form = await request.form()
ids = [int(x) for x in form.get("asset_ids", "").split(",") if x.strip().isdigit()] ids = [int(x) for x in form.get("asset_ids", "").split(",") if x.strip().isdigit()]
tid = int(form.get("tag_id", "0") or "0") tid = int(form.get("tag_id", "0") or "0")
@ -244,6 +262,9 @@ async def qualys_bulk_remove_tag(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "qualys"):
return RedirectResponse(url="/qualys/tags")
form = await request.form() form = await request.form()
ids = [int(x) for x in form.get("asset_ids", "").split(",") if x.strip().isdigit()] ids = [int(x) for x in form.get("asset_ids", "").split(",") if x.strip().isdigit()]
tid = int(form.get("tag_id", "0") or "0") tid = int(form.get("tag_id", "0") or "0")
@ -260,6 +281,9 @@ async def qualys_resync_assets(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "qualys"):
return RedirectResponse(url="/qualys/search")
form = await request.form() form = await request.form()
ids = [int(x) for x in form.get("asset_ids", "").split(",") if x.strip().isdigit()] ids = [int(x) for x in form.get("asset_ids", "").split(",") if x.strip().isdigit()]
ok = 0 ok = 0
@ -303,6 +327,9 @@ async def qualys_tags_export(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "qualys"):
return RedirectResponse(url="/qualys/tags")
tags = db.execute(text("SELECT * FROM qualys_tags ORDER BY name")).fetchall() tags = db.execute(text("SELECT * FROM qualys_tags ORDER BY name")).fetchall()
output = io.StringIO() output = io.StringIO()
writer = csv.writer(output, delimiter=";") writer = csv.writer(output, delimiter=";")
@ -484,6 +511,9 @@ async def export_no_agent_csv(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "qualys"):
return RedirectResponse(url="/qualys/agents")
import io, csv as _csv import io, csv as _csv
rows = db.execute(text(""" rows = db.execute(text("""
SELECT s.hostname, s.os_family, s.etat, d.name as domain, e.name as env, z.name as zone SELECT s.hostname, s.os_family, s.etat, d.name as domain, e.name as env, z.name as zone
@ -512,6 +542,9 @@ async def export_inactive_csv(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "qualys"):
return RedirectResponse(url="/qualys/agents")
import io, csv as _csv import io, csv as _csv
rows = db.execute(text(""" rows = db.execute(text("""
SELECT qa.hostname, qa.os, qa.agent_version, qa.last_checkin, s.etat SELECT qa.hostname, qa.os, qa.agent_version, qa.last_checkin, s.etat
@ -536,7 +569,10 @@ async def qualys_vulns_detail(request: Request, ip: str, db=Depends(get_db)):
"""Retourne le detail des vulns severity 3,4,5 pour une IP (fragment HTMX)""" """Retourne le detail des vulns severity 3,4,5 pour une IP (fragment HTMX)"""
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return HTMLResponse("<p>Non autorise</p>") return HTMLResponse("<p>Non autorise</p>", status_code=401)
perms = get_user_perms(db, user)
if not can_view(perms, "qualys"):
return HTMLResponse("<p>Acces interdit</p>", status_code=403)
# Cache 10 min # Cache 10 min
from ..services import cache as _cache from ..services import cache as _cache
@ -693,7 +729,10 @@ async def qualys_vulns_detail(request: Request, ip: str, db=Depends(get_db)):
async def qualys_asset_detail(request: Request, asset_id: int, db=Depends(get_db)): async def qualys_asset_detail(request: Request, asset_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return HTMLResponse("<p>Non autorisé</p>") return HTMLResponse("<p>Non autorisé</p>", status_code=401)
perms = get_user_perms(db, user)
if not can_view(perms, "qualys"):
return HTMLResponse("<p>Acces interdit</p>", status_code=403)
asset = db.execute(text("SELECT * FROM qualys_assets WHERE qualys_asset_id = :aid"), asset = db.execute(text("SELECT * FROM qualys_assets WHERE qualys_asset_id = :aid"),
{"aid": asset_id}).fetchone() {"aid": asset_id}).fetchone()

View File

@ -108,6 +108,9 @@ async def quickwin_config_save(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns") and not can_edit(perms, "quickwin"):
return RedirectResponse(url="/quickwin/config")
if server_id: if server_id:
upsert_server_config(db, server_id, general_excludes.strip(), upsert_server_config(db, server_id, general_excludes.strip(),
specific_excludes.strip(), notes.strip()) specific_excludes.strip(), notes.strip())
@ -120,6 +123,9 @@ async def quickwin_config_delete(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns") and not can_edit(perms, "quickwin"):
return RedirectResponse(url="/quickwin/config")
if config_id: if config_id:
delete_server_config(db, config_id) delete_server_config(db, config_id)
return RedirectResponse(url="/quickwin/config?msg=deleted", status_code=303) return RedirectResponse(url="/quickwin/config?msg=deleted", status_code=303)
@ -133,6 +139,9 @@ async def quickwin_config_bulk_add(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns") and not can_edit(perms, "quickwin"):
return RedirectResponse(url="/quickwin/config")
ids = [int(x) for x in server_ids.split(",") if x.strip().isdigit()] ids = [int(x) for x in server_ids.split(",") if x.strip().isdigit()]
for sid in ids: for sid in ids:
upsert_server_config(db, sid, general_excludes.strip(), "", "") upsert_server_config(db, sid, general_excludes.strip(), "", "")
@ -189,6 +198,9 @@ async def quickwin_detail(request: Request, run_id: int, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "campaigns") and not can_view(perms, "quickwin"):
return RedirectResponse(url="/dashboard")
run = get_run(db, run_id) run = get_run(db, run_id)
if not run: if not run:
@ -265,6 +277,9 @@ async def quickwin_entry_update(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return JSONResponse({"error": "unauthorized"}, 401) return JSONResponse({"error": "unauthorized"}, 401)
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns") and not can_edit(perms, "quickwin"):
return JSONResponse({"error": "forbidden"}, 403)
body = await request.json() body = await request.json()
entry_id = body.get("id") entry_id = body.get("id")
field = body.get("field") field = body.get("field")
@ -280,6 +295,9 @@ async def quickwin_inject_yum(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return JSONResponse({"error": "unauthorized"}, 401) return JSONResponse({"error": "unauthorized"}, 401)
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return JSONResponse({"error": "forbidden"}, 403)
body = await request.json() body = await request.json()
if not isinstance(body, list): if not isinstance(body, list):
return JSONResponse({"error": "expected list"}, 400) return JSONResponse({"error": "expected list"}, 400)
@ -293,5 +311,8 @@ async def quickwin_prod_check(request: Request, run_id: int, db=Depends(get_db))
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return JSONResponse({"error": "unauthorized"}, 401) return JSONResponse({"error": "unauthorized"}, 401)
perms = get_user_perms(db, user)
if not can_view(perms, "campaigns") and not can_view(perms, "quickwin"):
return JSONResponse({"error": "forbidden"}, 403)
ok = can_start_prod(db, run_id) ok = can_start_prod(db, run_id)
return JSONResponse({"can_start_prod": ok}) return JSONResponse({"can_start_prod": ok})

View File

@ -89,6 +89,9 @@ async def safe_patching_detail(request: Request, campaign_id: int, db=Depends(ge
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "campaigns"):
return RedirectResponse(url="/dashboard")
campaign = get_campaign(db, campaign_id) campaign = get_campaign(db, campaign_id)
if not campaign: if not campaign:
@ -148,6 +151,9 @@ async def safe_patching_check_prereqs(request: Request, campaign_id: int, db=Dep
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return RedirectResponse(url=f"/safe-patching/{campaign_id}")
from ..services.prereq_service import check_prereqs_campaign from ..services.prereq_service import check_prereqs_campaign
checked, auto_excluded = check_prereqs_campaign(db, campaign_id) checked, auto_excluded = check_prereqs_campaign(db, campaign_id)
return RedirectResponse(url=f"/safe-patching/{campaign_id}?step=prereqs&msg=prereqs_done", status_code=303) return RedirectResponse(url=f"/safe-patching/{campaign_id}?step=prereqs&msg=prereqs_done", status_code=303)
@ -159,6 +165,9 @@ async def safe_patching_bulk_exclude(request: Request, campaign_id: int, db=Depe
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return RedirectResponse(url=f"/safe-patching/{campaign_id}")
from ..services.campaign_service import exclude_session from ..services.campaign_service import exclude_session
ids = [int(x) for x in session_ids.split(",") if x.strip().isdigit()] ids = [int(x) for x in session_ids.split(",") if x.strip().isdigit()]
for sid in ids: for sid in ids:
@ -173,6 +182,9 @@ async def safe_patching_execute(request: Request, campaign_id: int, db=Depends(g
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "campaigns"):
return RedirectResponse(url=f"/safe-patching/{campaign_id}")
# Récupérer les sessions pending de la branche # Récupérer les sessions pending de la branche
if branch == "hprod": if branch == "hprod":
@ -215,6 +227,9 @@ async def safe_patching_terminal(request: Request, campaign_id: int, db=Depends(
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "campaigns"):
return RedirectResponse(url="/safe-patching")
campaign = get_campaign(db, campaign_id) campaign = get_campaign(db, campaign_id)
ctx = base_context(request, db, user) ctx = base_context(request, db, user)
ctx.update({"app_name": APP_NAME, "c": campaign, "branch": branch}) ctx.update({"app_name": APP_NAME, "c": campaign, "branch": branch})
@ -222,8 +237,11 @@ async def safe_patching_terminal(request: Request, campaign_id: int, db=Depends(
@router.get("/safe-patching/{campaign_id}/stream") @router.get("/safe-patching/{campaign_id}/stream")
async def safe_patching_stream(request: Request, campaign_id: int): async def safe_patching_stream(request: Request, campaign_id: int, db=Depends(get_db)):
"""SSE endpoint — stream les logs en temps réel""" """SSE endpoint — stream les logs en temps réel"""
user = get_current_user(request)
if not user:
return StreamingResponse(iter([]), media_type="text/event-stream")
async def event_generator(): async def event_generator():
q = get_stream(campaign_id) q = get_stream(campaign_id)
while True: while True:

View File

@ -2,7 +2,7 @@
from fastapi import APIRouter, Request, Depends, Query, Form from fastapi import APIRouter, Request, Depends, Query, Form
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from ..dependencies import get_db, get_current_user from ..dependencies import get_db, get_current_user, get_user_perms, can_view, can_edit
from ..services.server_service import ( from ..services.server_service import (
get_server_full, get_server_tags, get_server_ips, get_server_full, get_server_tags, get_server_ips,
list_servers, update_server, get_reference_data list_servers, update_server, get_reference_data
@ -24,6 +24,9 @@ async def servers_list(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "servers"):
return RedirectResponse(url="/dashboard")
filters = {"domain": domain, "env": env, "tier": tier, "etat": etat, "os": os, "owner": owner, "search": search} filters = {"domain": domain, "env": env, "tier": tier, "etat": etat, "os": os, "owner": owner, "search": search}
servers, total = list_servers(db, filters, page, sort=sort, sort_dir=sort_dir) servers, total = list_servers(db, filters, page, sort=sort, sort_dir=sort_dir)
@ -47,6 +50,9 @@ async def servers_export_csv(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "servers"):
return RedirectResponse(url="/dashboard")
import io, csv import io, csv
filters = {"domain": domain, "env": env, "tier": tier, "etat": etat, "os": os, "owner": owner, "search": search} filters = {"domain": domain, "env": env, "tier": tier, "etat": etat, "os": os, "owner": owner, "search": search}
servers, total = list_servers(db, filters, page=1, per_page=99999, sort="hostname", sort_dir="asc") servers, total = list_servers(db, filters, page=1, per_page=99999, sort="hostname", sort_dir="asc")
@ -72,7 +78,10 @@ async def servers_export_csv(request: Request, db=Depends(get_db),
async def server_detail(request: Request, server_id: int, db=Depends(get_db)): async def server_detail(request: Request, server_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return HTMLResponse("<p>Non autorise</p>") return HTMLResponse("<p>Non autorise</p>", status_code=401)
perms = get_user_perms(db, user)
if not can_view(perms, "servers"):
return HTMLResponse("<p>Acces interdit</p>", status_code=403)
s = get_server_full(db, server_id) s = get_server_full(db, server_id)
if not s: if not s:
return HTMLResponse("<p>Serveur non trouve</p>") return HTMLResponse("<p>Serveur non trouve</p>")
@ -87,7 +96,10 @@ async def server_detail(request: Request, server_id: int, db=Depends(get_db)):
async def server_edit(request: Request, server_id: int, db=Depends(get_db)): async def server_edit(request: Request, server_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return HTMLResponse("<p>Non autorise</p>") return HTMLResponse("<p>Non autorise</p>", status_code=401)
perms = get_user_perms(db, user)
if not can_edit(perms, "servers"):
return HTMLResponse("<p>Acces interdit</p>", status_code=403)
s = get_server_full(db, server_id) s = get_server_full(db, server_id)
if not s: if not s:
return HTMLResponse("<p>Serveur non trouve</p>") return HTMLResponse("<p>Serveur non trouve</p>")
@ -111,7 +123,10 @@ async def server_update(request: Request, server_id: int, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return HTMLResponse("<p>Non autorise</p>") return HTMLResponse("<p>Non autorise</p>", status_code=401)
perms = get_user_perms(db, user)
if not can_edit(perms, "servers"):
return HTMLResponse("<p>Acces interdit</p>", status_code=403)
data = { data = {
"domain_code": domain_code, "env_code": env_code, "zone": zone, "domain_code": domain_code, "env_code": env_code, "zone": zone,
@ -139,6 +154,9 @@ async def servers_bulk(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "servers"):
return RedirectResponse(url="/servers", status_code=303)
if not server_ids or not bulk_field or not bulk_value: if not server_ids or not bulk_field or not bulk_value:
return RedirectResponse(url="/servers", status_code=303) return RedirectResponse(url="/servers", status_code=303)
@ -189,7 +207,10 @@ async def servers_bulk(request: Request, db=Depends(get_db),
async def server_sync_qualys(request: Request, server_id: int, db=Depends(get_db)): async def server_sync_qualys(request: Request, server_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return HTMLResponse("<p>Non autorise</p>") return HTMLResponse("<p>Non autorise</p>", status_code=401)
perms = get_user_perms(db, user)
if not can_edit(perms, "servers"):
return HTMLResponse("<p>Acces interdit</p>", status_code=403)
result = sync_server_qualys(db, server_id) result = sync_server_qualys(db, server_id)
s = get_server_full(db, server_id) s = get_server_full(db, server_id)
tags = get_server_tags(db, s.qid) if s else [] tags = get_server_tags(db, s.qid) if s else []

View File

@ -3,7 +3,7 @@ from fastapi import APIRouter, Request, Depends, Form
from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from sqlalchemy import text from sqlalchemy import text
from ..dependencies import get_db, get_current_user from ..dependencies import get_db, get_current_user, get_user_perms, can_view, can_edit
from ..services.secrets_service import get_secret, set_secret, list_secrets, init_secrets_from_config from ..services.secrets_service import get_secret, set_secret, list_secrets, init_secrets_from_config
from ..config import APP_NAME from ..config import APP_NAME
@ -134,6 +134,9 @@ async def settings_page(request: Request, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "settings"):
return RedirectResponse(url="/dashboard")
ctx = _build_context(db, user) ctx = _build_context(db, user)
ctx["request"] = request ctx["request"] = request
return templates.TemplateResponse("settings.html", ctx) return templates.TemplateResponse("settings.html", ctx)
@ -146,6 +149,12 @@ async def settings_save(request: Request, section: str, db=Depends(get_db)):
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
if section not in SECTIONS: if section not in SECTIONS:
return HTMLResponse("<p>Section inconnue</p>", status_code=400) return HTMLResponse("<p>Section inconnue</p>", status_code=400)
perms = get_user_perms(db, user)
if not can_edit(perms, "settings"):
return RedirectResponse(url="/settings")
role = user.get("role", "viewer")
if section in SECTION_ACCESS and role not in SECTION_ACCESS[section]["editable"]:
return RedirectResponse(url="/settings")
form = await request.form() form = await request.form()
for key, label, is_secret in SECTIONS[section]: for key, label, is_secret in SECTIONS[section]:
@ -174,6 +183,9 @@ async def vcenter_add(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "settings"):
return RedirectResponse(url="/settings")
db.execute(text( db.execute(text(
"INSERT INTO vcenters (name, endpoint, datacenter, description, responsable) VALUES (:n, :e, :dc, :desc, :resp)" "INSERT INTO vcenters (name, endpoint, datacenter, description, responsable) VALUES (:n, :e, :dc, :desc, :resp)"
), {"n": vc_name, "e": vc_endpoint, "dc": vc_datacenter or None, "desc": vc_description or None, "resp": vc_responsable or None}) ), {"n": vc_name, "e": vc_endpoint, "dc": vc_datacenter or None, "desc": vc_description or None, "resp": vc_responsable or None})
@ -188,6 +200,9 @@ async def vcenter_delete(request: Request, vc_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "settings"):
return RedirectResponse(url="/settings")
db.execute(text("UPDATE vcenters SET is_active = false WHERE id = :id"), {"id": vc_id}) db.execute(text("UPDATE vcenters SET is_active = false WHERE id = :id"), {"id": vc_id})
db.commit() db.commit()
ctx = _build_context(db, user, saved="vsphere") ctx = _build_context(db, user, saved="vsphere")
@ -203,6 +218,9 @@ async def secret_update(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "settings"):
return RedirectResponse(url="/settings")
if secret_value and secret_value != "********": if secret_value and secret_value != "********":
# Recuperer la description existante # Recuperer la description existante
existing = db.execute(text("SELECT description FROM app_secrets WHERE key = :k"), existing = db.execute(text("SELECT description FROM app_secrets WHERE key = :k"),
@ -240,6 +258,9 @@ async def network_add(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "settings"):
return RedirectResponse(url="/settings")
db.execute(text("INSERT INTO allowed_networks (cidr, description) VALUES (:c, :d)"), db.execute(text("INSERT INTO allowed_networks (cidr, description) VALUES (:c, :d)"),
{"c": cidr.strip(), "d": description or None}) {"c": cidr.strip(), "d": description or None})
db.commit() db.commit()
@ -254,6 +275,9 @@ async def network_delete(request: Request, net_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "settings"):
return RedirectResponse(url="/settings")
db.execute(text("DELETE FROM allowed_networks WHERE id = :id"), {"id": net_id}) db.execute(text("DELETE FROM allowed_networks WHERE id = :id"), {"id": net_id})
db.commit() db.commit()
_regen_nginx_acl(db) _regen_nginx_acl(db)
@ -267,6 +291,9 @@ async def network_toggle(request: Request, net_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "settings"):
return RedirectResponse(url="/settings")
db.execute(text("UPDATE allowed_networks SET is_active = NOT is_active WHERE id = :id"), {"id": net_id}) db.execute(text("UPDATE allowed_networks SET is_active = NOT is_active WHERE id = :id"), {"id": net_id})
db.commit() db.commit()
_regen_nginx_acl(db) _regen_nginx_acl(db)

View File

@ -47,6 +47,9 @@ async def specifics_list(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_view(perms, "specifics"):
return RedirectResponse(url="/dashboard")
entries = _list_specifics(db, app_type, search) entries = _list_specifics(db, app_type, search)
# Types en base # Types en base
types_in_db = db.execute(text( types_in_db = db.execute(text(
@ -65,6 +68,9 @@ async def specific_edit(request: Request, spec_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return HTMLResponse("<p>Non autorise</p>") return HTMLResponse("<p>Non autorise</p>")
perms = get_user_perms(db, user)
if not can_edit(perms, "specifics"):
return HTMLResponse("<p>Acces interdit</p>", status_code=403)
row = db.execute(text(""" row = db.execute(text("""
SELECT ss.*, s.hostname FROM server_specifics ss SELECT ss.*, s.hostname FROM server_specifics ss
JOIN servers s ON ss.server_id = s.id WHERE ss.id = :id JOIN servers s ON ss.server_id = s.id WHERE ss.id = :id
@ -81,6 +87,9 @@ async def specific_save(request: Request, spec_id: int, db=Depends(get_db)):
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "specifics"):
return RedirectResponse(url="/specifics")
form = await request.form() form = await request.form()
def val(k): v = form.get(k, ""); return v.strip() if v else None def val(k): v = form.get(k, ""); return v.strip() if v else None
@ -147,6 +156,9 @@ async def specific_add(request: Request, db=Depends(get_db),
user = get_current_user(request) user = get_current_user(request)
if not user: if not user:
return RedirectResponse(url="/login") return RedirectResponse(url="/login")
perms = get_user_perms(db, user)
if not can_edit(perms, "specifics"):
return RedirectResponse(url="/specifics")
row = db.execute(text("SELECT id FROM servers WHERE LOWER(hostname) = LOWER(:h)"), row = db.execute(text("SELECT id FROM servers WHERE LOWER(hostname) = LOWER(:h)"),
{"h": hostname.strip()}).fetchone() {"h": hostname.strip()}).fetchone()
if not row: if not row: