Autenticación CAS (auth_casattras)
Configuración de administración del inicio de sesión contra el CAS del Ministerio
(cas.colombiaaprende.edu.co). El plugin autentica al usuario contra el CAS y
puebla su perfil con los atributos que el servidor libera en la respuesta de
validación del ticket.
Ruta: Administración del sitio → Extensiones → Autenticación → CAS-Attras (o buscar "casattras" en el buscador de administración).
Por qué casattras y no cas
En Moodle 5.0 el plugin auth_cas fue removido del core (MDL-78778) y trasladado
a moodlehq/moodle-auth_cas. Este proyecto corre 5.1, así que no existe
public/auth/cas/. La vía instalada es auth_casattras, que obtiene los
atributos desde la propia respuesta del CAS (phpCAS::getAttributes()) en lugar
de consultarlos por LDAP.
Los dos plugins no pueden convivir habilitados: comparten la librería phpCAS y
entran en conflicto. El propio auth_casattras se autodeshabilita si detecta
auth_cas activo.
Ajustes del servidor
| Ajuste | Clave | Valor | Notas |
|---|---|---|---|
| Nombre del servidor | hostname | cas.colombiaaprende.edu.co | Mismo host en todos los ambientes; lo que cambia entre ellos es el service |
| Ruta base | baseuri | cas | phpCAS normaliza cas, /cas y /cas/ al mismo valor |
| Puerto | port | 443 | |
| Versión del protocolo | casversion | CAS_VERSION_3_0 | La 3.0 es la que libera atributos de forma estándar, que es lo que el plugin necesita |
| Autenticación múltiple | multiauth | 0 | Con 1 el formulario local se mantiene y el CAS aparece como enlace adicional. Con 0, toda visita a /login/index.php se redirige al CAS y no queda salida por navegador si el CAS falla. Ver "Acceso de emergencia sin CAS" |
| Validar certificado | certificatecheck | 1 | |
| Ruta del certificado | certificatepath | /etc/ssl/certs/ca-certificates.crt | El plugin solo distingue entre "un archivo CA concreto" y "no validar nada"; apuntar al bundle del sistema equivale a usar la cadena de CA pública. El certificado del CAS es de una CA pública, así que no hace falta un CA cert propio |
| Proxy CAS | proxycas | 0 | |
| Logout centralizado | logoutcas | 1 | prelogout_hook() llama a phpCAS::logoutWithRedirectService(), que redirige al slug / del wwwroot (o a logoutreturnurl si está configurado) tras cerrar la sesión del CAS. Es necesario con multiauth = 0: si la sesión del CAS sobrevive al logout de Moodle, el usuario que vuelva a /login/index.php es re-autenticado al instante y percibe que no puede salir. Cierra la sesión en todos los servicios que compartan el CAS, incluido el Drupal |
| URL de retorno tras logout | logoutreturnurl | vacío (usa wwwroot) | A dónde vuelve el usuario después del logout centralizado. El hook añade una barra final si falta: el CAS solo redirige cuando el service termina en / o en una ruta, no cuando es un host a secas |
| Nombre del método | auth_name | CAS | Es la etiqueta del botón en la página de login |
| Logo del método | auth_logo | vacío | Sin logo no se renderiza icono. Subir una imagen aquí es la única forma de que aparezca |
El service que se envía al CAS no se configura: se deriva de
$CFG->wwwroot. Debe ser https, sin barra final, y detrás de
nginx-proxy-manager requiere $CFG->sslproxy = true o el service sale en http://
y el CAS rechaza el ticket.
Mapeo de atributos
| Campo de Moodle | Atributo CAS | Notas |
|---|---|---|
username | — | No se mapea: sale de phpCAS::getUser(), que devuelve el uid |
firstname | givenname | |
lastname | sn | |
email | mail2 | El correo viene en mail2, no en mail. Mapear mail deja a todos los usuarios sin correo |
idnumber | uid | Equivalente del "Número de documento" |
profile_field_tipousuario | tipousuario | Campo de perfil personalizado; ver abajo |
Los cinco llevan field_updatelocal_* = onlogin y field_lock_* = locked.
Los dos son necesarios, no opcionales: update_user_record_by_id() descarta el
campo si cualquiera de los dos está vacío, y solo actualiza cuando
field_updatelocal es exactamente onlogin. La combinación produce el
comportamiento "aplicar en cada login y sobrescribir": el CAS es la fuente de
verdad y el usuario no puede editar esos campos en Moodle.
get_userinfo() hace $casattras[$field], una búsqueda de clave exacta. Si el
CAS devuelve givenName, mapear givenname no trae nada. El síntoma es
característico: usuario creado, sesión iniciada y campos vacíos.
Los nombres en minúsculas de la tabla están confirmados contra el CAS del Ministerio. Si en otro entorno hay dudas, poner el sitio en modo desarrollador y leer el log: el plugin registra el atributo esperado y los que sí llegaron.
Cuentas preexistentes: el requisito que no es evidente
Un usuario que ya existe en Moodle no entra por CAS aunque el CAS lo autentique
correctamente, salvo que su campo auth sea casattras.
authenticate_user_login() restringe la autenticación al plugin del propio
usuario. Con auth = manual, el plugin CAS nunca llega a consultarse: el hook de
login deja una contraseña ficticia y manual la compara contra el hash local.
El síntoma engaña, porque todo el recorrido externo funciona:
- El usuario pulsa el botón CAS y es redirigido.
- Se autentica correctamente en el CAS.
- Vuelve a Moodle con un ticket válido.
- Moodle le muestra la página de login con "Nombre de usuario o contraseña incorrectos".
Cómo distinguirlo de un problema real del CAS: en el log del contenedor aparece
Failed Login: <uid> en una petición cuyo referer es el propio CAS. Si el
referer es el CAS, la autenticación externa funcionó y el rechazo es de Moodle.
La solución es migrar la cuenta — cambiar el campo auth de una cuenta que ya
existe en este mismo ambiente, nunca traer ni copiar la cuenta desde otro
ambiente:
UPDATE mdl_user SET auth = 'casattras' WHERE username = '<uid>';
Capturar antes los valores actuales de auth, firstname, lastname, email e
idnumber: con field_updatelocal_* = onlogin el CAS los sobrescribe en el
primer login. El hash de contraseña local se conserva en la fila, así que volver
a manual restaura el login local.
Si el sitio se pobló con usuarios importados cuyos usernames son los mismos
identificadores que emite el CAS, entonces la creación automática de cuentas
casi no se ejercita y el camino que importa es el de actualización — el que
habilita field_updatelocal_* = onlogin. Con el valor por defecto oncreate, un
usuario que ya existe no recibiría ningún dato del CAS.
Antes de una migración masiva, dos comprobaciones: excluir las cuentas que no
existen en el CAS (administración, cuentas de servicio, usuarios de prueba), y
revisar si las cuentas a migrar tienen una contraseña local utilizable. Una
columna password que no empieza por $ no es un hash en formato crypt, y
validate_internal_user_password() valida con password_verify(): esas cuentas
no pueden autenticarse localmente hoy, así que migrarlas no les quita acceso —
se lo da.
Esta migración es siempre intra-ambiente: cambia cuentas que ya existen en ese Moodle. No es una vía para llevar cuentas de Local o Pruebas hacia Certificación o Producción — ver qué NO incluye la entrega.
Campo de perfil tipousuario
El atributo tipousuario se guarda en un campo de perfil personalizado y no
dispara ninguna asignación automática de rol. Esto es deliberado.
En Drupal los roles son globales, y por eso su módulo puede mapear atributo → rol
directamente. Moodle no funciona así: student, teacher y editingteacher son
asignaciones contextuales, solo asignables en contexto de curso o de módulo.
Lo que determina qué ve un usuario es en qué cursos está matriculado, no una
etiqueta en su cuenta; el equivalente funcional de "darle acceso de estudiante" es
matricularlo. Esa separación entre autenticación (quién eres) y matriculación
(a qué entras) es el modelo estándar de Moodle: el CAS resuelve la identidad y la
matriculación sigue por los medios actuales del sitio (manual, self,
cohort).
Ruta: Administración del sitio → Usuarios → Campos de perfil de usuario.
Tipo texto, shortname tipousuario. Debe existir antes de configurar
field_map_profile_field_tipousuario: el plugin lee la lista de campos
personalizados desde la base de datos, y un mapeo hacia un campo inexistente se
guarda pero no se usa.
El campo funciona además como instrumento de descubrimiento. Tras varios logins reales entrega el dato necesario para diseñar la asignación de roles:
SELECT d.data AS tipousuario, COUNT(*) AS usuarios
FROM mdl_user_info_data d
JOIN mdl_user_info_field f ON f.id = d.fieldid
WHERE f.shortname = 'tipousuario'
GROUP BY d.data ORDER BY usuarios DESC;
Por cada valor hay que definir con el cliente a qué cursos debe entrar y con qué
rol. El camino estándar es una cohorte por valor y matriculación por cohorte
(enrol_cohort); el core de Moodle no tiene cohortes dinámicas, así que la
pertenencia automática requiere un plugin comunitario de cohortes por reglas.
Habilitar y deshabilitar
La lista de plugins de autenticación habilitados es el ajuste auth, una lista
completa separada por comas, no un añadido:
docker exec colombiaaprende_web php /var/www/admin/cli/cfg.php --name=auth --set=email,casattras
docker exec colombiaaprende_web php /var/www/admin/cli/purge_caches.php
Para que el auto-registro de usuarios nuevos ocurra, authpreventaccountcreation
debe estar en 0.
Deshabilitar (--set=email) es el rollback completo del inicio de sesión por CAS.
Conviene mantener al menos una cuenta administradora con auth manual y
contraseña conocida, y tener su sesión abierta mientras se toca cualquier ajuste
de autenticación.
Acceso de emergencia sin CAS (solo Pruebas)
Con multiauth = 0 el formulario local de Moodle deja de ser alcanzable desde el
navegador. auth_casattras no implementa un bypass web: el auth_cas del
core histórico aceptaba /login/index.php?authCAS=NOCAS, pero casattras no tiene
equivalente — los únicos optional_param de loginpage_hook() son username,
ticket y authCASattras.
Por eso el ambiente de Pruebas lleva un parche propio que restaura ese bypass:
https://campusvirtual.metaversodenegocios.com/login/index.php?nocas=1
Ese enlace omite el redirect y devuelve el formulario local de usuario y contraseña. Sirve para poder seguir trabajando en el desarrollo cuando el CAS está caído o no responde; sin él, la única vía de acceso en esa situación es SSH al servidor.
Este bypass no existe ni debe existir en Certificación ni en Producción. Es una herramienta del periodo de desarrollo y pruebas, no parte de la entrega.
- No está en el árbol de código fuente: vive únicamente en el servidor de
Pruebas. Por eso cualquier despliegue de
auth/casattrasdesde el repositorio lo revierte en silencio, y conmultiauth = 0eso deja SSH como única vía de acceso si el CAS falla. Si hay que redesplegar ese plugin en Pruebas, reaplicar el parche después. - En Certificación y Producción el acceso de rescate se resuelve por los medios del MEN, no por una URL pública. Un parámetro que devuelve el formulario local en un sitio donde el CAS es el único método de identidad es una puerta que no corresponde abrir fuera de un ambiente de desarrollo.
Aun con el bypass, solo entran las cuentas que tengan una contraseña local
utilizable: una columna password que no empieza por $ no es un hash en
formato crypt y password_verify() la rechaza. Ver "Cuentas preexistentes".
El procedimiento completo —parche, despliegue y verificación— está en el runbook
pruebas/CAS_FORZAR_LOGIN_PRUEBAS.md del repositorio.
Verificación
-
/login/index.phpresponde302haciahttps://cas.colombiaaprende.edu.co/cas/login?service=<wwwroot urlencoded>..., sin mostrar el formulario local. El botón de login del tema lleva al mismo sitio:get_login_url()resuelve a/login/index.php, así que botón y slug son un único destino. Si el service sale con otro esquema, host o puerto, revisarwwwrootysslproxy. -
El
Locationincluye&gateway=true, que añadephpCAS::checkAuthentication()antes deforceAuthentication(). La cadena completa son tres saltos: Moodle → CAS congateway→ rebote a Moodle sin ticket → CAS singateway→ formulario del CAS.No verificar concurl -Lsin cookie jarphpCAS guarda el resultado del chequeo de gateway en
$_SESSION. Sin persistir la sesión, el rebote se repite indefinidamente y aparenta un bucle de redirección que no existe en un navegador. Usarcurl -c cookies.txt -b cookies.txt -L. -
Tras autenticarse, el usuario vuelve a Moodle con sesión iniciada y con
firstname,lastname,emaileidnumberpoblados:SELECT id, username, auth, firstname, lastname, email, idnumberFROM mdl_user WHERE auth = 'casattras'; -
El usuario no tendrá matrículas: es el comportamiento esperado, no un fallo. Ver la sección del campo
tipousuario. -
Con
logoutcas = 1, cerrar sesión (/login/logout.php, consesskey) responde con un302en cadena que termina en el slug/delwwwroot(o enlogoutreturnurlsi está configurado), con el usuario deslogeado tanto de Moodle como del CAS. Probar sin cookie jar previo para no arrastrar una sesión de CAS ya vigente:curl -c cookies.txt -b cookies.txt -sS -o /dev/null -D - -L \"$WWWROOT/login/logout.php?sesskey=<sesskey de la sesión activa>" \| grep -iE "^HTTP/|^location:"
Problemas comunes
- "Usuario o contraseña incorrectos" después de autenticarse en el CAS: la
cuenta ya existe con otro
auth. Ver "Cuentas preexistentes". Es el fallo más probable en un sitio con usuarios importados. - Usuario creado pero campos vacíos: casing del atributo. Poner el sitio en
modo desarrollador (
debug = 32767) dejandodebugdisplay = 0, repetir el login y leerdocker logs colombiaaprende_web: el plugin registra el atributo esperado y los nombres de los que sí llegaron. Corregir elfield_map_*correspondiente; no requiere redespliegue. Devolverdebuga0al terminar. - Login correcto y ningún atributo: la política de liberación de atributos en
CAS se define por servicio, así que el login puede funcionar y
phpCAS::getAttributes()volver vacío. Es una solicitud al administrador del CAS, no un problema de Moodle. - Error de SSL al validar el ticket: confirmar que el contenedor alcanza el
CAS (
curlahttps://cas.colombiaaprende.edu.co/cas/logindebe dar200). Si el certificado no valida contra la cadena pública, copiar el CA cert del cliente al árbol montado, fuera del docroot, y apuntarcertificatepathahí. - Un campo muestra
Array: el CAS devolvió un atributo multivaluado. phpCAS entrega un array y Moodle lo castea a string al guardarlo. Se resuelve decidiendo con el cliente qué valor tomar. - Icono roto junto al botón de CAS:
auth_logovacío en una versión del plugin sin el guard correspondiente. Subir un logo o aplicar el guard.
Paridad con producción
Pruebas replica el comportamiento de producción (campus.colombiaaprende.edu.co)
y del Drupal, donde el enlace al CAS en el formulario estándar está desactivado:
multiauth = 0, logoutcas = 1, sin acceso de invitado
(guestloginbutton = 0), sin auto-registro (registerauth vacío) y con
forgottenpasswordurl apuntando a la recuperación de Colombia Aprende, porque la
contraseña vive en el CAS y el flujo interno de Moodle no aplica.
El hostname ya coincide en todos los ambientes; lo único que cambia es el
service, que se deriva de wwwroot, así que el CAS debe tener registrados
todos los dominios.
Dos diferencias deliberadas frente a Certificación y Producción, ninguna forma parte de la entrega:
- El bypass
?nocas=1, exclusivo de Pruebas. Ver "Acceso de emergencia sin CAS". - El parche de
prelogout_hook()que usaphpCAS::logoutWithRedirectService()en vez dephpCAS::logoutWithURL(). En Certificación y Producción el logout centralizado ya redirige correctamente al slug/del sitio: esos ambientes no comparten el despliegue de código de este proyecto paraauth/casattras—el plugin queda fuera de los cinco componentes propios que cubre el RFC de entrega (ver Integración y despliegue)—, así que el fix no debe transferirse ahí. Aplicarlo sería redundante en el mejor caso y, si esos ambientes usan una versión distinta del plugin o dellogoutreturnurl, un cambio de comportamiento no solicitado por el cliente en un ambiente donde el CAS ya funciona como se requiere.
El commit que corrige prelogout_hook() vive en el repositorio (a diferencia
del bypass ?nocas=1, que nunca se versiona), así que sí viajaría por una
sincronización de código normal si alguien copiara auth/casattras completo
hacia Certificación o Producción. El procedimiento estándar de despliegue no
lo hace —ver la tabla de componentes en
Integración y despliegue—, pero cualquier transferencia manual
de ese directorio fuera de Pruebas debe excluir explícitamente este cambio.
Antes del cambio hay que definir qué pasa con las cuentas existentes: los
usuarios creados con auth email o manual no entran por CAS salvo que se
migren, y esa migración se acuerda con el cliente, no se ejecuta por defecto.
Historial de cambios
Ver CHANGELOG.md en la raíz del sitio de documentación.