Resumen
Refactor arquitectónico de autorización (auditoría s187 §3.3-P2): 52 gates de endpoints Ninja migrados del helper legacy is_admin() a require_perm(request, "<scope>", "admin"). Con roles por defecto el comportamiento es idéntico (solo admin alcanza nivel admin); la mejora deliberada es que los overrides por módulo (ModulePermission / TemporaryAccess) ahora se evalúan contra el scope correcto del módulo, en lugar de siempre exigir users:admin.
Versión: v1.45.0 (2026-07-04)
Scopes migrados: users, racks, network, fleet
Archivos afectados: 15 (core/api, racks/api, network/api, terminal/api)
Cambios por módulo
Nota de curación (s197): la primera versión de esta sección listaba RUTAS inventadas por el escritor; se reemplazó por el inventario real a nivel de función, verificado contra el diff del PR #270.
Core → scope users (6 ficheros, 22 gates)
core/api/users.py:_list_users_impl,_create_user_impl,get_users_mfa_status,delete_user,get_user_mfa_devices,delete_user_mfa_device(+update_userusahas_permissionen su lógica admin-o-self).core/api/admin.py:get_permissions,set_permissions,reset_permissions,grant_temp_access,revoke_temp_access.core/api/settings.py:update_company_settings,upload_company_logo(los endpoints de onboarding y session-timeout NO tenían gate y siguen igual).core/api/logs.py:get_logs_data,export_logs_csv.core/api/login_history.py:get_login_history.core/api/backup.py:download_latest_backup.
Racks → scope racks (7 ficheros, 10 gates)
racks/api/library.py:move_stencil,delete_library_category,rename_library_category.racks/api/library_files.py:analyze_visio.racks/api/export/restore.py:restore_full·export/backup.py:backup_full·export/csv_export.py:export_csv·export/json_export.py:import_racks·export/network_config.py:save_device_network_config,import_image.
Network → scope network (1 fichero, 7 gates)
network/api/vendor.py:create_vendor_profile,update_vendor_profile,upload_mib,delete_custom_mib,apply_custom_oids,mib_assistant_analyze,mib_assistant_apply(catálogo VendorProfile GLOBAL → admin only).
Terminal → scope fleet (1 fichero, 5 gates)
terminal/api/fleet.py:set_fleet_config,promote_agent,demote_agent,delete_agent,reauth_agent.
Mejoras de autorización
Antes
- Todos estos 52 endpoints exigían
is_admin(user)→ verificaban solo si el usuario tenía roladminglobal. - Los overrides por módulo (ModulePermission: dar a alguien nivel admin SOLO de Network, racks, etc.) se ignoraban.
- Respuestas 403 inconsistentes: convivían
{"message"},{"error"}, HttpError con textos distintos.
Después
- Cada endpoint pide
require_perm(request, "<scope>", "admin")del módulo correcto. - Overrides por módulo (
ModulePermission,TemporaryAccess) se evalúan contra el scope del módulo:require_perm(request, "racks", "admin")respeta permisos tiporacks:admin.require_perm(request, "network", "admin")respeta permisos tiponetwork:admin.
- Respuesta 403 unificada:
{"detail": "Forbidden: requires 'admin' permission on '<scope>'"}(formato estándar de Ninja). is_admin()queda SOLO para las vistas Django clásicas (core/views.py,core/htmx_views.py), donderequire_permde Ninja no aplica.
Fixes colaterales
GET /api/backup/latest (latent 500)
- Problema: endpoint declaraba en
response=solo{200, 404}pero devolvía tuplas403(rechazo) y400(error de validación) → error de configuración de Ninja (500 silencioso al negar acceso). - Solución: agregado
400: ErrorSchemaenresponse=; el403ahora sale limpio porrequire_perm.
Deprecación
ChartDefaultsIn.refresh_interval (retirada)
- Toggle de refresco por pestaña de Observatory retirado en v1.43.0 (cadencia centralizada por dispositivo, task #175).
- Ningún consumidor —ni código JS ni backend— leía ya este campo.
- Acción: removido de
monitoring/api/schemas.py.
Impacto usuario
- Roles estándar (admin/operator/readonly): sin cambios visibles. El comportamiento es idéntico.
- Overrides por módulo (permisos finos): ahora se respetan en todas partes, incluso en estos 52 endpoints antiguos.
- Accesos temporales (TemporaryAccess): granularidad mejorada — ya no necesitan nivel
users:adminglobal para operar en un módulo específico. - Mensajes de error: más consistentes y legibles.
Testing
- Sin tests nuevos: los tests de autorización EXISTENTES cubren el sweep y siguen en verde (
test_network_sa3.py24 ✓ ·test_racks_security.py7 ✓ ·test_auth.py) — asertan códigos 403, no cuerpos, y las semánticas por rol son idénticas. - Suite completa verde en el gate pre-push local (1131 tests) + CI del PR #270 (2 shards en runners self-hosted).
Véase también
- [[entity—core—service—require-perm]]
- [[entity—core—service—has-permission]]
- [[decision—20260609—require-perm-unified-authorization]]
- [[concept—saas—multi-tenancy]]
- [[concept—saas—rbac-granular]]