fix: code review — security fixes, dedup, cleanup
- setup: validate roles against users.VALID_ROLES (Supervisor was missing) - files: fix path traversal prefix-match edge case (is_relative_to), dedupe path validation into resolve_upload_path(), use logging not print - measurements: extract shared _build_measurement_filters() helper - client app: prevent open redirect via Referer on /set-language - maker: guard resp.json() in parse-technical-sheet proxy - measure/maker: extract shared file proxy into services/file_proxy.py - measure: localize supervisor validation error messages - annotation-editor: remove global keydown listener in destroy() Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
"""MeasurementTec blueprint - recipe selection and measurement execution."""
|
||||
import requests as http_requests
|
||||
|
||||
from flask import (
|
||||
Blueprint, Response, flash, jsonify, redirect, render_template,
|
||||
Blueprint, flash, jsonify, redirect, render_template,
|
||||
request, session, url_for,
|
||||
)
|
||||
from flask_babel import gettext as _
|
||||
@@ -10,6 +8,7 @@ from flask_babel import gettext as _
|
||||
from blueprints.auth import login_required, role_required
|
||||
from config import Config
|
||||
from services.api_client import api_client
|
||||
from services.file_proxy import proxy_file
|
||||
|
||||
measure_bp = Blueprint("measure", __name__)
|
||||
|
||||
@@ -339,17 +338,17 @@ def validate_supervisor():
|
||||
password = data.get("password", "")
|
||||
|
||||
if not username or not password:
|
||||
return jsonify({"error": True, "detail": "Username e password richiesti"}), 400
|
||||
return jsonify({"error": True, "detail": _("Username e password richiesti")}), 400
|
||||
|
||||
resp = api_client.post("/api/auth/login", data={"username": username, "password": password})
|
||||
|
||||
if resp.get("error"):
|
||||
return jsonify({"error": True, "detail": "Credenziali non valide"}), 401
|
||||
return jsonify({"error": True, "detail": _("Credenziali non valide")}), 401
|
||||
|
||||
user = resp.get("user", {})
|
||||
is_supervisor = "Supervisor" in (user.get("roles") or [])
|
||||
if not (is_supervisor or user.get("is_admin")):
|
||||
return jsonify({"error": True, "detail": "Utente non autorizzato (richiesto capoturno)"}), 403
|
||||
return jsonify({"error": True, "detail": _("Utente non autorizzato (richiesto capoturno)")}), 403
|
||||
|
||||
return jsonify({"authorized": True, "supervisor": user.get("display_name", username)}), 200
|
||||
|
||||
@@ -361,16 +360,4 @@ def validate_supervisor():
|
||||
@login_required
|
||||
def api_get_file(file_path: str):
|
||||
"""Proxy: Serve file from API server (browser can't send X-API-Key)."""
|
||||
api_key = session.get("api_key", "")
|
||||
base_url = Config.API_SERVER_URL.rstrip("/")
|
||||
resp = http_requests.get(
|
||||
f"{base_url}/api/files/{file_path}",
|
||||
headers={"X-API-Key": api_key},
|
||||
timeout=30,
|
||||
)
|
||||
if resp.status_code != 200:
|
||||
return Response(resp.text, status=resp.status_code)
|
||||
return Response(
|
||||
resp.content,
|
||||
content_type=resp.headers.get("content-type", "application/octet-stream"),
|
||||
)
|
||||
return proxy_file(file_path)
|
||||
|
||||
Reference in New Issue
Block a user