Roles y permisos
SoftSys Edu Analytics aplica una única superficie de política entre la autenticación y el acceso a datos. Cada operación expuesta por la API declara la capability que requiere; la política decide si el usuario está autorizado y filtra los datos al alcance que le corresponde.
Roles canónicos
Sección titulada «Roles canónicos»Hay exactamente cuatro roles en producción:
| Rol | Quién es | Qué ve por defecto |
|---|---|---|
superadmin | Operador de la plataforma | Cualquier tenant tras seleccionar contexto explícito |
admin | Administrador de un tenant cliente | Todo el tenant propio; nunca otros tenants |
teacher | Docente de un tenant | Sólo sus propios cursos y los estudiantes matriculados en ellos |
student | Aprendiz | Sólo su propia información |
Reseller se reconoce como rol futuro: la superficie de política es extensible (basta registrar el rol y sus reglas, sin tocar handlers existentes). La implementación concreta de reseller — sub-tenants, branding del partner, switch — queda para un spec posterior.
Valores legacy admitidos durante la transición: manager → admin, instructor / editingteacher / coursecreator → teacher, learner → student, owner → admin.
Matriz de capacidades
Sección titulada «Matriz de capacidades»Cada Capability declara: roles permitidos, dimensiones de scope que se aplican y nivel de auditoría. La tabla siguiente refleja el registro en producción.
| Capability | superadmin | admin | teacher | student | Scopes | Audit |
|---|---|---|---|---|---|---|
tenant.overview.read | ✅ | ✅ | ✅ | ❌ | tenant + course | always |
tenant.me.read | ✅ | ✅ | ✅ | ✅ | tenant | on-deny |
tenant.aggregate.read | ✅ | ✅ | ✅ | ❌ | tenant + course | on-deny |
tenant.plan.read | ✅ | ✅ | ✅ | ❌ | tenant | on-deny |
tenant.settings.read | ✅ | ✅ | ❌ | ❌ | tenant | always |
tenant.settings.write | ✅ | ✅ | ❌ | ❌ | tenant | always |
users.list | ✅ | ✅ | ✅ | ❌ | tenant + course | on-deny |
courses.list | ✅ | ✅ | ✅ | ❌ | tenant + course | on-deny |
atrisk.list | ✅ | ✅ | ✅ | ❌ | tenant + course | always |
atrisk.acknowledge | ✅ | ✅ | ✅ | ❌ | tenant | always |
alerts.list | ✅ | ✅ | ✅ | ❌ | tenant + course | on-deny |
student.detail.read | ✅ | ✅ | ✅ (own students) | ✅ (self) | tenant + course + self | always |
teacher.detail.read | ✅ | ✅ | ✅ (self) | ❌ | tenant + self | always |
course.detail.read | ✅ | ✅ | ✅ (own courses) | ✅ (enrolled) | tenant + course | on-deny |
monitors.list | ✅ | ✅ | ✅ | ❌ | tenant | on-deny |
monitors.create | ✅ | ✅ | ❌ | ❌ | tenant | always |
monitors.delete | ✅ | ✅ | ❌ | ❌ | tenant | always |
reports.snapshots.read | ✅ | ✅ | ✅ | ❌ | tenant + course | on-deny |
reports.latest.read | ✅ | ✅ | ✅ | ✅ (whitelisted types, own row) | tenant + course + student | on-deny |
reports.history.read | ✅ | ✅ | ✅ | ❌ | tenant | on-deny |
reports.exports.list | ✅ | ✅ | ✅ | ❌ | tenant | on-deny |
reports.jobs.list | ✅ | ✅ | ✅ | ❌ | tenant | on-deny |
reports.jobs.create | ✅ | ✅ | ❌ | ❌ | tenant | always |
reports.jobs.read | ✅ | ✅ | ✅ | ❌ | tenant | on-deny |
reports.download.csv | ✅ | ✅ | ✅ (scope-filtered) | ❌ | tenant + course | always |
reports.download.json | ✅ | ✅ | ✅ (scope-filtered) | ❌ | tenant + course | always |
reports.artifact.download | ✅ | ✅ | ❌ | ❌ | tenant | always |
tenants.list | ✅ | ❌ | ❌ | ❌ | — | always |
tenant.switch | ✅ | ❌ | ❌ | ❌ | — | always |
audit.list | ✅ | ✅ | ❌ | ❌ | tenant | always |
audit.download | ✅ | ✅ | ❌ | ❌ | tenant | always |
audit.operator.list | ✅ | ❌ | ❌ | ❌ | tenant | always |
infrastructure.read | ✅ | ✅ | ❌ | ❌ | tenant | on-deny |
tenant.online.read | ✅ | ✅ | ✅ | ❌ | tenant + course | on-deny |
tenant.devices.read | ✅ | ✅ | ❌ | ❌ | tenant | on-deny |
Garantías invariantes:
- Aislamiento multi-tenant: ningún rol distinto de
superadminpuede leer datos de otro tenant — ni con manipulación de URL ni con tokens cruzados. - Estudiantes nunca descargan: ninguna capability cuyo nombre coincide con
reports.download.*oreports.artifact.downloadadmite el rolstudent. Es estructural, validado en CI. - Filtros de scope se aplican en SQL: cuando un teacher pide datos a un endpoint, la cláusula que añade la política limita las filas a sus cursos antes de leer la base.
- Artifacts pre-computados se re-filtran al entregar: aunque un reporte se generó con datos de todo el tenant, al servirlo a un teacher se filtra de nuevo para no exponer cursos ajenos.
Cómo se ve una denegación
Sección titulada «Cómo se ve una denegación»Toda denegación devuelve la misma envolvente. El cliente nunca puede inferir si un recurso “existe en otro tenant” vs “existe pero no puede verlo”.
{ "ok": false, "code": "FORBIDDEN_SCOPE", "error": "You do not have access to this resource.", "request_id": "0e3f...c8"}Mapeo de código y status:
| Razón interna | code | HTTP | Mensaje |
|---|---|---|---|
| Sin autenticación | UNAUTHENTICATED | 401 | Authentication required. |
| Rol no permitido | FORBIDDEN_ROLE | 403 | Your role is not allowed to perform this action. |
| Fuera de scope | FORBIDDEN_SCOPE | 403 | You do not have access to this resource. |
| Cross-tenant | CROSS_TENANT | 403 | You do not have access to this resource. (idéntico a FORBIDDEN_SCOPE por diseño) |
| Recurso ausente | NOT_FOUND | 404 | Resource not found. |
| Sin datos para evaluar scope | MISSING_SCOPE_DATA | 403 | Your account is not configured for this action. |
Auditoría — policy_audit_log
Sección titulada «Auditoría — policy_audit_log»Cada decisión (allow + deny según el auditLevel) escribe una fila en la tabla policy_audit_log. Las inserciones son fire-and-forget (waitUntil); no bloquean la respuesta al cliente.
Columnas relevantes:
| Columna | Descripción |
|---|---|
ts | Unix ms del request |
tenant_id | Tenant cuyos datos se tocaron |
acting_as_tenant | Set sólo cuando un superadmin actúa fuera de su tenant home |
actor_role | Uno de los cuatro roles canónicos |
actor_subject | user_id (JWT) o moodle_user_id (plugin) |
capability | Identificador de la capability registrada |
endpoint | METHOD /path/template |
decision | allow | deny |
reason | allowed o el code interno (forbidden_role, cross_tenant, …) |
request_id | Correlación con logs estructurados |
Visor desde el dashboard
Sección titulada «Visor desde el dashboard»Los administradores pueden consultar el log directamente desde Dashboard → Auditoría (/audit-log). El visor permite filtrar por decisión (allow / deny), actor, capability, endpoint, rango temporal (1h / 24h / 7d / 30d), exportar el resultado a CSV y ver una pestaña “Solo actividad de operador” disponible exclusivamente para superadmin. Los profesores y estudiantes nunca acceden al visor — la entrada del menú lateral no aparece para ellos.
Cómo investigar una denegación (vía SQL directo)
Sección titulada «Cómo investigar una denegación (vía SQL directo)»“¿Por qué el usuario X recibió un 403 hace una hora?”
SELECT ts, capability, endpoint, decision, reason, request_idFROM policy_audit_logWHERE tenant_id = ?1 AND actor_subject = ?2 AND ts > ?3 -- now − 3600000 (ms)ORDER BY ts DESCLIMIT 100;“¿Qué accedió un superadmin en mi tenant ayer?”
SELECT ts, actor_subject, capability, endpoint, decision, resource_id, request_idFROM policy_audit_logWHERE acting_as_tenant = ?1 AND ts > ?2 -- last 24hORDER BY ts DESC;Retención automática (cron audit_retention, cada hora):
- Filas regulares (
acting_as_tenant IS NULL): 90 días. - Filas de operadores (
acting_as_tenant IS NOT NULL): 365 días.
El cron borra hasta 10 000 filas por categoría por ejecución para no agotar el presupuesto de CPU del worker. En instalaciones con volumen alto, las filas que excedan ese umbral se eliminan en runs sucesivos hasta converger. Cambios en las ventanas requieren un nuevo spec por sus implicaciones de cumplimiento.
Cómo opera un superadmin (spec 006)
Sección titulada «Cómo opera un superadmin (spec 006)»Un super-admin (operador de SoftSys) puede listar todos los tenants y entrar al contexto de cualquiera para soporte, debugging u onboarding. Toda la actividad cross-tenant queda registrada con acting_as_tenant populado, exactamente lo que el dashboard de auditoría rinde en su pestaña “Solo actividad de operador”.
- Listar tenants —
GET /v1/tenants(capabilitytenants.list). Solosuperadmin. - Entrar a un tenant —
POST /v1/tenants/:id/switch(capabilitytenant.switch). Devuelve un nuevo JWT con TTL de 1 hora con los claims:tenant_id= tenant destino (controla qué datos se leen)acting_as_tenant= mismo tenant destino (señala que es contexto de operador)home_tenant_id= tenant home del operador (para “Volver al hogar”)
- Operar — todas las lecturas siguientes producen filas en
policy_audit_logconactor_role='superadmin'yacting_as_tenant=<destino>. - Volver al hogar —
POST /v1/tenants/:home_id/switch. Emite un JWT limpio sinacting_as_tenant.
Garantías
Sección titulada «Garantías»- TTL fijo de 1 hora. No hay renovación; tras la expiración la sesión vuelve al login.
- No se puede falsificar — los claims viajan firmados. Modificar
acting_as_tenantinvalida la firma JWT y el request se rechaza como no autenticado. - Visibilidad bilateral del audit — las filas de actividad de operador son visibles tanto para el admin del tenant destino (vía
/audit-log) como para el superadmin via la pestaña “Solo actividad de operador”. Esto es intencional: el tenant destino merece ver qué hizo el operador en sus datos. - Banner visible — el dashboard renderiza un banner persistente “Actuando como” cuando
acting_as_tenant !== home_tenant_id, con un botón “Volver al hogar” en un click.
Propagación de cambios de rol
Sección titulada «Propagación de cambios de rol»Cambios efectuados en la herramienta de admin de la plataforma surten efecto en el siguiente request (≤ 1 segundo). Cambios efectuados directamente en Moodle se propagan a través del pipeline de sincronización (process_queue_task cada 5 min, migrate_data_task diaria); la ventana de propagación máxima es 5 minutos bajo operación normal.
Para revocaciones que requieren efecto inmediato, opera desde la consola de admin en el dashboard, no esperes a que Moodle sincronice.
Para profundizar
Sección titulada «Para profundizar»- Arquitectura — dónde vive la superficie de política dentro del sistema.
- Flujo de datos — el ciclo de vida de un request, paso a paso.
- Tipos de informe — capabilities aplicadas a cada reporte.