Ir al contenido

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.

Hay exactamente cuatro roles en producción:

RolQuién esQué ve por defecto
superadminOperador de la plataformaCualquier tenant tras seleccionar contexto explícito
adminAdministrador de un tenant clienteTodo el tenant propio; nunca otros tenants
teacherDocente de un tenantSólo sus propios cursos y los estudiantes matriculados en ellos
studentAprendizSó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: manageradmin, instructor / editingteacher / coursecreatorteacher, learnerstudent, owneradmin.

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.

CapabilitysuperadminadminteacherstudentScopesAudit
tenant.overview.readtenant + coursealways
tenant.me.readtenanton-deny
tenant.aggregate.readtenant + courseon-deny
tenant.plan.readtenanton-deny
tenant.settings.readtenantalways
tenant.settings.writetenantalways
users.listtenant + courseon-deny
courses.listtenant + courseon-deny
atrisk.listtenant + coursealways
atrisk.acknowledgetenantalways
alerts.listtenant + courseon-deny
student.detail.read✅ (own students)✅ (self)tenant + course + selfalways
teacher.detail.read✅ (self)tenant + selfalways
course.detail.read✅ (own courses)✅ (enrolled)tenant + courseon-deny
monitors.listtenanton-deny
monitors.createtenantalways
monitors.deletetenantalways
reports.snapshots.readtenant + courseon-deny
reports.latest.read✅ (whitelisted types, own row)tenant + course + studenton-deny
reports.history.readtenanton-deny
reports.exports.listtenanton-deny
reports.jobs.listtenanton-deny
reports.jobs.createtenantalways
reports.jobs.readtenanton-deny
reports.download.csv✅ (scope-filtered)tenant + coursealways
reports.download.json✅ (scope-filtered)tenant + coursealways
reports.artifact.downloadtenantalways
tenants.listalways
tenant.switchalways
audit.listtenantalways
audit.downloadtenantalways
audit.operator.listtenantalways
infrastructure.readtenanton-deny
tenant.online.readtenant + courseon-deny
tenant.devices.readtenanton-deny

Garantías invariantes:

  • Aislamiento multi-tenant: ningún rol distinto de superadmin puede 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.* o reports.artifact.download admite el rol student. 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.

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 internacodeHTTPMensaje
Sin autenticaciónUNAUTHENTICATED401Authentication required.
Rol no permitidoFORBIDDEN_ROLE403Your role is not allowed to perform this action.
Fuera de scopeFORBIDDEN_SCOPE403You do not have access to this resource.
Cross-tenantCROSS_TENANT403You do not have access to this resource. (idéntico a FORBIDDEN_SCOPE por diseño)
Recurso ausenteNOT_FOUND404Resource not found.
Sin datos para evaluar scopeMISSING_SCOPE_DATA403Your account is not configured for this action.

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:

ColumnaDescripción
tsUnix ms del request
tenant_idTenant cuyos datos se tocaron
acting_as_tenantSet sólo cuando un superadmin actúa fuera de su tenant home
actor_roleUno de los cuatro roles canónicos
actor_subjectuser_id (JWT) o moodle_user_id (plugin)
capabilityIdentificador de la capability registrada
endpointMETHOD /path/template
decisionallow | deny
reasonallowed o el code interno (forbidden_role, cross_tenant, …)
request_idCorrelación con logs estructurados

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_id
FROM policy_audit_log
WHERE tenant_id = ?1
AND actor_subject = ?2
AND ts > ?3 -- now − 3600000 (ms)
ORDER BY ts DESC
LIMIT 100;

“¿Qué accedió un superadmin en mi tenant ayer?”

SELECT ts, actor_subject, capability, endpoint, decision, resource_id, request_id
FROM policy_audit_log
WHERE acting_as_tenant = ?1
AND ts > ?2 -- last 24h
ORDER 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.

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”.

  1. Listar tenantsGET /v1/tenants (capability tenants.list). Solo superadmin.
  2. Entrar a un tenantPOST /v1/tenants/:id/switch (capability tenant.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”)
  3. Operar — todas las lecturas siguientes producen filas en policy_audit_log con actor_role='superadmin' y acting_as_tenant=<destino>.
  4. Volver al hogarPOST /v1/tenants/:home_id/switch. Emite un JWT limpio sin acting_as_tenant.
  • 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_tenant invalida 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.

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.