feat: sécurité HIGH — token Bearer, validation, rate limiting, headers
- Token Bearer auth sur le streaming server (auto-généré ou env var) - Validation actions replay (types, longueurs, coordonnées 0-1) - Rate limiting in-memory (10 replays/min, 200 images/min) - Security headers Flask (nosniff, SAMEORIGIN, XSS) - Validation uploads (50MB max, MIME type) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,10 @@ SERVER_URL = os.getenv("RPA_SERVER_URL", "http://localhost:5005/api/v1")
|
||||
UPLOAD_ENDPOINT = f"{SERVER_URL}/traces/upload"
|
||||
STREAMING_ENDPOINT = f"{SERVER_URL}/traces/stream"
|
||||
|
||||
# Token d'authentification API (doit correspondre au token du serveur)
|
||||
# Configurable via variable d'environnement RPA_API_TOKEN
|
||||
API_TOKEN = os.environ.get("RPA_API_TOKEN", "")
|
||||
|
||||
# Paramètres de session
|
||||
MAX_SESSION_DURATION_S = 60 * 60 # 1 heure
|
||||
SESSIONS_ROOT = BASE_DIR / "sessions"
|
||||
|
||||
@@ -10,6 +10,7 @@ Supporte deux modes :
|
||||
|
||||
import base64
|
||||
import io
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
|
||||
@@ -65,6 +66,14 @@ class ActionExecutorV1:
|
||||
self._poll_backoff_min = 1.0 # Delai minimal (reset apres succes)
|
||||
self._poll_backoff_max = 30.0 # Delai maximal
|
||||
self._poll_backoff_factor = 1.5 # Multiplicateur en cas d'echec
|
||||
# Token d'authentification API
|
||||
self._api_token = os.environ.get("RPA_API_TOKEN", "")
|
||||
|
||||
def _auth_headers(self) -> dict:
|
||||
"""Headers d'authentification Bearer pour les requetes au serveur."""
|
||||
if self._api_token:
|
||||
return {"Authorization": f"Bearer {self._api_token}"}
|
||||
return {}
|
||||
|
||||
@property
|
||||
def sct(self):
|
||||
@@ -285,7 +294,7 @@ class ActionExecutorV1:
|
||||
"screen_height": screen_height,
|
||||
}
|
||||
|
||||
resp = requests.post(resolve_url, json=payload, timeout=60)
|
||||
resp = requests.post(resolve_url, json=payload, headers=self._auth_headers(), timeout=60)
|
||||
if resp.ok:
|
||||
data = resp.json()
|
||||
method = data.get("method", "?")
|
||||
@@ -333,6 +342,7 @@ class ActionExecutorV1:
|
||||
resp = requests.get(
|
||||
replay_next_url,
|
||||
params={"session_id": session_id, "machine_id": machine_id},
|
||||
headers=self._auth_headers(),
|
||||
timeout=5,
|
||||
)
|
||||
if not resp.ok:
|
||||
@@ -408,6 +418,7 @@ class ActionExecutorV1:
|
||||
resp2 = requests.post(
|
||||
replay_result_url,
|
||||
json=report,
|
||||
headers=self._auth_headers(),
|
||||
timeout=10,
|
||||
)
|
||||
if resp2.ok:
|
||||
|
||||
@@ -25,7 +25,7 @@ import time
|
||||
import requests
|
||||
from PIL import Image
|
||||
|
||||
from ..config import STREAMING_ENDPOINT
|
||||
from ..config import API_TOKEN, STREAMING_ENDPOINT
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -56,6 +56,13 @@ class TraceStreamer:
|
||||
self._health_thread = None
|
||||
self._server_available = True # Désactivé après trop d'échecs
|
||||
|
||||
@staticmethod
|
||||
def _auth_headers() -> dict:
|
||||
"""Headers d'authentification Bearer pour les requêtes API."""
|
||||
if API_TOKEN:
|
||||
return {"Authorization": f"Bearer {API_TOKEN}"}
|
||||
return {}
|
||||
|
||||
def start(self):
|
||||
"""Démarrer le streaming et enregistrer la session côté serveur."""
|
||||
self.running = True
|
||||
@@ -240,6 +247,7 @@ class TraceStreamer:
|
||||
try:
|
||||
resp = requests.get(
|
||||
f"{STREAMING_ENDPOINT}/stats",
|
||||
headers=self._auth_headers(),
|
||||
timeout=3,
|
||||
)
|
||||
if resp.ok:
|
||||
@@ -292,6 +300,7 @@ class TraceStreamer:
|
||||
"session_id": self.session_id,
|
||||
"machine_id": self.machine_id,
|
||||
},
|
||||
headers=self._auth_headers(),
|
||||
timeout=3,
|
||||
)
|
||||
if resp.ok:
|
||||
@@ -319,6 +328,7 @@ class TraceStreamer:
|
||||
"session_id": self.session_id,
|
||||
"machine_id": self.machine_id,
|
||||
},
|
||||
headers=self._auth_headers(),
|
||||
timeout=30, # Le build workflow peut prendre du temps
|
||||
)
|
||||
if resp.ok:
|
||||
@@ -343,6 +353,7 @@ class TraceStreamer:
|
||||
resp = requests.post(
|
||||
f"{STREAMING_ENDPOINT}/event",
|
||||
json=payload,
|
||||
headers=self._auth_headers(),
|
||||
timeout=2,
|
||||
)
|
||||
return resp.ok
|
||||
@@ -377,6 +388,7 @@ class TraceStreamer:
|
||||
f"{STREAMING_ENDPOINT}/image",
|
||||
files=files,
|
||||
params=params,
|
||||
headers=self._auth_headers(),
|
||||
timeout=5,
|
||||
)
|
||||
return resp.ok
|
||||
@@ -390,6 +402,7 @@ class TraceStreamer:
|
||||
f"{STREAMING_ENDPOINT}/image",
|
||||
files=files,
|
||||
params=params,
|
||||
headers=self._auth_headers(),
|
||||
timeout=5,
|
||||
)
|
||||
return resp.ok
|
||||
|
||||
Reference in New Issue
Block a user