feat: Enhance session management and logging, sanitize parameters for JSON serialization

This commit is contained in:
Miguel 2025-09-14 13:06:48 +02:00
parent a655e68b71
commit 79f496df34
14 changed files with 124 additions and 35 deletions

7
.gitignore vendored
View File

@ -214,3 +214,10 @@ __marimo__/
# Streamlit
.streamlit/secrets.toml
# Windows compatibility - files with problematic characters
*:Zone.Identifier
*:*
desktop.ini
Thumbs.db
*.lnk

View File

@ -1,4 +0,0 @@
[ZoneTransfer]
ZoneId=3
ReferrerUrl=http://127.0.0.1:5001/
HostUrl=http://127.0.0.1:5001/

View File

@ -1,4 +0,0 @@
[ZoneTransfer]
ZoneId=3
ReferrerUrl=http://127.0.0.1:5001/
HostUrl=http://127.0.0.1:5001/

View File

@ -584,6 +584,7 @@ def register_routes(app):
# If no project_id specified, use active project from session
if not project_id:
print(f"[API_EXEC] Session contents: {dict(session)}")
active_project_id = session.get("active_project_id")
print(f"[API_EXEC] Active project ID from session: {active_project_id}")
if active_project_id:
@ -598,6 +599,7 @@ def register_routes(app):
print(f"[API_EXEC] Using verified active project: {project_id}")
else:
print(f"[API_EXEC] Active project verification failed")
print(f"[API_EXEC] Searched for: id={active_project_id}, user_id={current_user.id}, group_id={script.group_id}")
print(f"[API_EXEC] Final project_id: {project_id}")
print(f"[API_EXEC] Calling script_executor.execute_script_with_proxy...")
@ -929,16 +931,22 @@ def register_routes(app):
def api_set_active_project(project_id):
"""Set active project for current session."""
try:
print(f"[SET_ACTIVE] Setting project {project_id} as active for user {current_user.id}")
project = UserProject.query.filter_by(
id=project_id, user_id=current_user.id
).first()
if not project:
print(f"[SET_ACTIVE] Project {project_id} not found for user {current_user.id}")
return jsonify({"error": "Project not found"}), 404
# Store in session
session["active_project_id"] = project_id
session["active_group_id"] = project.group_id
print(f"[SET_ACTIVE] Stored in session: active_project_id={project_id}, active_group_id={project.group_id}")
print(f"[SET_ACTIVE] Session contents after setting: {dict(session)}")
# Update last accessed
project.last_accessed = datetime.utcnow()
@ -947,6 +955,7 @@ def register_routes(app):
return jsonify({"success": True, "active_project": project.to_dict()})
except Exception as e:
print(f"[SET_ACTIVE] Error: {e}")
return jsonify({"error": str(e)}), 500
@app.route("/api/projects/active", methods=["GET"])

View File

@ -1,4 +0,0 @@
[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://duckduckgo.com/
HostUrl=https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fwww.ergon3.de%2Fwp-content%2Fuploads%2F2021%2F02%2FSidel-Logo-1.png&f=1&nofb=1&ipt=ae165a508e4f2fe11472494b47e56f55e34ed8ba1c8f0e8eecd0e41000d84c9a

View File

@ -106,8 +106,11 @@ def start_script(project_id: int, script_id: int, user_id: int):
if success:
# Crear o actualizar registro en base de datos
# Solo pasar tipos básicos serializables para evitar errores de pickle
safe_parameters = parameters if isinstance(parameters, (dict, list, str, int, float, bool)) else {}
safe_environment = environment if isinstance(environment, (dict, list, str, int, float, bool)) else {}
proxy_execution = _create_proxy_execution_record(
project_id, script_id, user_id, port, parameters, environment
project_id, script_id, user_id, port, safe_parameters, safe_environment
)
proxy_url = f"/project/{project_id}/script/{script_id}/user/{user_id}/"
@ -296,6 +299,8 @@ def _get_or_create_proxy_execution(project_id: int, script_id: int, user_id: int
Obtiene una ejecución existente o crea una nueva
"""
try:
logger.error(f"_get_or_create_proxy_execution: Inicio para {project_id}, {script_id}, {user_id}")
# Buscar ejecución existente
proxy_execution = ScriptProxyExecution.query.filter_by(
project_id=project_id,
@ -303,30 +308,40 @@ def _get_or_create_proxy_execution(project_id: int, script_id: int, user_id: int
user_id=user_id
).first()
logger.error(f"_get_or_create_proxy_execution: Ejecución existente encontrada: {proxy_execution is not None}")
# Si existe y está activa, devolverla
if proxy_execution and proxy_execution.status == 'running':
logger.error(f"_get_or_create_proxy_execution: Verificando script existente...")
proxy_service = get_proxy_service()
script_info = proxy_service.get_script_info(
str(project_id), str(script_id), str(user_id)
)
if script_info:
logger.error(f"_get_or_create_proxy_execution: Script existente válido, retornando")
return proxy_execution
else:
# El script no está ejecutándose, limpiar registro
logger.error(f"_get_or_create_proxy_execution: Limpiando script muerto")
proxy_execution.status = 'stopped'
proxy_execution.stopped_at = datetime.utcnow()
db.session.commit()
logger.error(f"_get_or_create_proxy_execution: Iniciando nuevo script...")
# Intentar iniciar nuevo script
script = Script.query.get(script_id)
if not script:
logger.error(f"_get_or_create_proxy_execution: Script {script_id} no encontrado")
return None
script_content = _get_script_content(script)
if not script_content:
logger.error(f"_get_or_create_proxy_execution: No se pudo leer contenido del script")
return None
logger.error(f"_get_or_create_proxy_execution: Llamando a proxy_service.start_script")
proxy_service = get_proxy_service()
success, message, port = proxy_service.start_script(
project_id=str(project_id),
@ -336,13 +351,19 @@ def _get_or_create_proxy_execution(project_id: int, script_id: int, user_id: int
script_name=script.display_name or script.filename
)
logger.error(f"_get_or_create_proxy_execution: start_script result: success={success}, port={port}")
if success:
logger.error(f"_get_or_create_proxy_execution: Creando registro de ejecución")
return _create_proxy_execution_record(project_id, script_id, user_id, port)
logger.error(f"_get_or_create_proxy_execution: start_script falló: {message}")
return None
except Exception as e:
logger.error(f"Error obteniendo/creando ejecución proxy: {e}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
return None
def _create_proxy_execution_record(project_id: int, script_id: int, user_id: int,
@ -361,6 +382,7 @@ def _create_proxy_execution_record(project_id: int, script_id: int, user_id: int
if existing:
db.session.delete(existing)
db.session.commit() # Commit del delete explícitamente
# Crear nuevo registro
proxy_execution = ScriptProxyExecution(

View File

@ -241,6 +241,18 @@ class ScriptProxyService:
environment: Dict = None) -> str:
"""Prepara el archivo del script con configuración Flask"""
# Función para sanitizar datos complejos para JSON
def sanitize_for_json(obj):
if obj is None:
return None
if isinstance(obj, (str, int, float, bool)):
return obj
if isinstance(obj, dict):
return {k: sanitize_for_json(v) for k, v in obj.items() if isinstance(k, str)}
if isinstance(obj, list):
return [sanitize_for_json(item) for item in obj]
return str(obj) # Convert complex objects to string
# Wrappear el script del usuario en una aplicación Flask
flask_wrapper = f'''
import sys
@ -253,8 +265,8 @@ os.chdir(WORKSPACE_PATH)
# Variables disponibles para el script del usuario
PROJECT_WORKSPACE = WORKSPACE_PATH
PARAMETERS = {parameters or {}}
ENVIRONMENT = {environment or {}}
PARAMETERS = {sanitize_for_json(parameters) or {}}
ENVIRONMENT = {sanitize_for_json(environment) or {}}
# Importar Flask y configurar aplicación
from flask import Flask, request, jsonify, render_template, send_file, redirect, url_for
@ -282,7 +294,7 @@ signal.signal(signal.SIGTERM, lambda s, f: sys.exit(0))
# === FIN CÓDIGO DEL USUARIO ===
if __name__ == "__main__":
app.run(host="127.0.0.1", port={port}, debug=False, threaded=True)
app.run(host="0.0.0.0", port={port}, debug=False, threaded=True)
'''
script_file_path = os.path.join(workspace_path, "script_app.py")
@ -290,11 +302,23 @@ if __name__ == "__main__":
f.write(flask_wrapper)
# Guardar metadatos
# Sanitizar parameters y environment para evitar errores de serialización
def sanitize_for_json(obj):
if obj is None:
return None
if isinstance(obj, (str, int, float, bool)):
return obj
if isinstance(obj, dict):
return {k: sanitize_for_json(v) for k, v in obj.items() if isinstance(k, str)}
if isinstance(obj, list):
return [sanitize_for_json(item) for item in obj]
return str(obj) # Convert complex objects to string
metadata = {
'port': port,
'workspace': workspace_path,
'parameters': parameters,
'environment': environment,
'parameters': sanitize_for_json(parameters),
'environment': sanitize_for_json(environment),
'created_at': datetime.now().isoformat()
}

View File

@ -1,4 +0,0 @@
[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://duckduckgo.com/
HostUrl=https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fwww.ergon3.de%2Fwp-content%2Fuploads%2F2021%2F02%2FSidel-Logo-1.png&f=1&nofb=1&ipt=ae165a508e4f2fe11472494b47e56f55e34ed8ba1c8f0e8eecd0e41000d84c9a

5
cookies.txt Normal file
View File

@ -0,0 +1,5 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 0 session .eJwljjEOwzAIAP_iuQNgQ0w-E4HBatekmar-vZa6ne6W-5Rjnnk9y_4-73yU4xVlL2GGPsGmhjv07NFgMBkLS6K2pAWarZKlWgjChtIq0GwuosBo1KtCbrTF6k662gQUT0-tmlYjhxunS-tsHYIHtFhqVLCyRu4rz_8Nlu8P5SAwBg.aMad0g.YqG1qIJkAwn2Aak665qKlLWULEQ

5
session_cookies.txt Normal file
View File

@ -0,0 +1,5 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 0 session .eJw9jjkOwzAMBP-iOgV10aI_Y5AilaOxIdtpgvw9QgKk251p5uWW1m2_ufnop13cclc3O2X20oAbqQgUK5qg5sAZM5qnZGEMshQDG7Gih8ljihBaEkSC7DmUSGBTmHR4CTRcA49iYhTJOKpV4WyCqWQuoLlC0oFqBHYj5Nyt_2r8uFyP-9OWa1_P7Uvjn219fVg9vtS_PwOAPvA.aMahNw.9DmJiYinfGT6xyor48i0ZaZSb9Q

5
test_cookies.txt Normal file
View File

@ -0,0 +1,5 @@
# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
#HttpOnly_localhost FALSE / FALSE 0 session .eJwljjEOwzAIAP_iuQNgQ0w-E4HBatekmar-vZa6ne6W-5Rjnnk9y_4-73yU4xVlL2GGPsGmhjv07NFgMBkLS6K2pAWarZKlWgjChtIq0GwuosBo1KtCbrTF6k662gQUT0-tmlYjhxunS-tsHYIHtFhqVLCyRu4rz_8Nlu8P5SAwBg.aMaeaw.Va6uJHBawC1aA03FgdQft7GRDH0

36
test_session_persistence.sh Executable file
View File

@ -0,0 +1,36 @@
#!/bin/bash
# Test session persistence with a single curl session
echo "Testing session persistence with single curl session..."
# Using session with -c and -b to maintain cookies across requests
COOKIE_JAR="./test_cookies.txt"
echo "=== Step 1: Login ==="
curl -c "$COOKIE_JAR" -b "$COOKIE_JAR" \
-X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=admin&password=admin123" \
-v \
http://localhost:5003/login
echo -e "\n=== Step 2: Set Active Project ==="
curl -c "$COOKIE_JAR" -b "$COOKIE_JAR" \
-X POST \
-H "Content-Type: application/json" \
-v \
http://localhost:5003/api/projects/1/set-active
echo -e "\n=== Step 3: Execute Script ==="
curl -c "$COOKIE_JAR" -b "$COOKIE_JAR" \
-X POST \
-H "Content-Type: application/json" \
-d '{}' \
-v \
http://localhost:5003/api/scripts/7/execute
echo -e "\n=== Cookies file content ==="
cat "$COOKIE_JAR"
echo -e "\n=== Cleanup ==="
rm -f "$COOKIE_JAR"

View File

@ -1,15 +1,7 @@
{
"port": 5200,
"workspace": "/app/workspaces/user_1/project_1/script_7",
"parameters": {},
"environment": {
"CONDA_DEFAULT_ENV": "tsnet",
"USER_LEVEL": "admin",
"PROJECT_ID": "1",
"PROJECT_NAME": "Hola",
"USER_THEME": "light",
"USER_LANGUAGE": "en",
"SCRIPT_GROUP_NAME": "Hydraulic Analysis Tools"
},
"created_at": "2025-09-14T10:08:46.733111"
"parameters": null,
"environment": null,
"created_at": "2025-09-14T11:04:23.433786"
}

View File

@ -10,7 +10,7 @@ os.chdir(WORKSPACE_PATH)
# Variables disponibles para el script del usuario
PROJECT_WORKSPACE = WORKSPACE_PATH
PARAMETERS = {}
ENVIRONMENT = {'CONDA_DEFAULT_ENV': 'tsnet', 'USER_LEVEL': 'admin', 'PROJECT_ID': '1', 'PROJECT_NAME': 'Hola', 'USER_THEME': 'light', 'USER_LANGUAGE': 'en', 'SCRIPT_GROUP_NAME': 'Hydraulic Analysis Tools'}
ENVIRONMENT = {}
# Importar Flask y configurar aplicación
from flask import Flask, request, jsonify, render_template, send_file, redirect, url_for
@ -944,4 +944,4 @@ if __name__ == "__main__":
# === FIN CÓDIGO DEL USUARIO ===
if __name__ == "__main__":
app.run(host="127.0.0.1", port=5200, debug=False, threaded=True)
app.run(host="0.0.0.0", port=5200, debug=False, threaded=True)