Permisos por pestana de reportes y ruido de serializacion en presencia

Auditoria de 260 visitas (26 rutas x 10 roles) sobre el build actual: 0 hallazgos.
Dos defectos reales que salieron del barrido anterior se corrigen aqui.

1) Reportes: la matriz guarda claves '/reportes?tab=x' y puede conceder una
   pestana suelta (Horas agenda al medico, Exportar a cosmetologia) sin dar el
   modulo. Nadie la respetaba:
   - App.tsx: nuevo ReportesRoute que evalua la pestana del URL; si el rol no
     tiene '/reportes' pero si alguna pestana, aterriza en la primera permitida.
   - Reportes.tsx: familias, favoritos y el boton "Exportar datos" se filtran
     por canSee('/reportes?tab=x'). 'exportar' no pertenece a ninguna familia,
     por eso se lista en TABS_FUERA_DE_FAMILIA (si no, el guard lo reescribia a
     'general' y admin perdia la vista). El query se sincroniza con lo renderizado.
   - SkeenLayout/HomeNestLayout: "Horas agenda" y "Exportar datos" aparecen en
     Analitica para que el permiso sea alcanzable desde el menu, no solo a
     mano. Clasico ya los traia.
   - auth.py: _medico_reporte_permitido consulta la matriz antes de vetar
     /reports/ al medico. Sin ese puente el menu ofrecia la opcion y la API
     respondia 403. Las demas rutas vetadas (sales, payments, wallets, pos)
     siguen cerradas: verificado comisiones=403, horas-agenda=200.

2) skeen_chat/_touch: 211 "could not serialize access due to concurrent update"
   en un barrido. Bandeja, badge y heartbeat salen en paralelo y cada uno
   emitia el UPDATE de last_seen aunque no aplicara cambios, abortando la
   transaccion de otra peticion. Se corta antes de tocar la fila cuando el
   registro es reciente; el savepoint queda para el caso de empate exacto.
   Ráfaga de 128 peticiones concurrentes: todas 200, presencia sigue
   actualizando (online=true, last_seen avanza) y el ruido bajo de 211 a 5.
This commit is contained in:
2026-09-30 01:26:37 +00:00
parent 5a16f694de
commit 85a281add1
6 changed files with 120 additions and 32 deletions

View File

@@ -15,7 +15,7 @@ import time
from odoo.http import request, Response
from ..roles import ROLE_RANKS
from ..roles import ROLE_RANKS, get_menu_roles
TTL_SECONDS = 60 * 60 * 12 # 12 horas
SECRET_KEY = 'skeen.frontend.secret'
@@ -36,6 +36,26 @@ MEDICO_BLOCKED_PREFIXES = (
)
def _medico_reporte_permitido(env, path):
"""True si la matriz de menus concede al medico ESA pestana de reportes.
El veto de 'medico' sobre /reports/ es por diseno (no ve dinero), pero la
matriz permite habilitar pestañas puntuales (/reportes?tab=horas-agenda).
Sin este puente el menu mostraria la opcion y la API la rechazaria con 403.
Las demas rutas vetadas (sales, payments, wallets...) siguen cerradas.
"""
prefijo = '/skeen/frontend/v1/reports/'
if not path.startswith(prefijo):
return False
tab = path[len(prefijo):].strip('/')
if not tab or '/' in tab:
return False
matriz = get_menu_roles(env)
if 'medico' in (matriz.get('/reportes') or []):
return True
return 'medico' in (matriz.get('/reportes?tab=%s' % tab) or [])
def _cors_headers():
return {
'Access-Control-Allow-Origin': '*',
@@ -156,7 +176,7 @@ def require_role(min_role='lectura'):
request.httprequest.path == p or request.httprequest.path.startswith(
p if p.endswith('/') else p + '/')
for p in MEDICO_BLOCKED_PREFIXES
):
) and not _medico_reporte_permitido(request.env, request.httprequest.path):
return _json({'status': 'error', 'message': 'Sin permiso'}, 403)
request.frontend_user = user
return func(self, *args, **kw)