Lo primero que tenemos que tener claro cuando queremos montar un sistema de Django + LDAP es el funcionamiento de la base de datos: ¿tenemos una base de datos local además de la remota o sólo existe la base de datos en LDAP? En función de esto la solución puede ser completamente distinta. Veremos el caso sin base de datos local, partiendo de la premisa de que queremos algo lo más 'de fábrica' posible:
La idea sería simplificar al máximo la implementación, usando para la autenticación un backend de django. Lo bueno de los backends es que se conectan directamente a la sesión, por lo que no tendríamos que re-escribir cómo funcionan estas, ni reescribir la vista de login, ya que todo se haría por debajo.
Los backends de autenticación han de implementar dos métodos: authenticate y get_user. El primero se puede hacer fácilmente, devolviendo un User de django que no se pilla de base de datos, sino que se crea el vuelo:
1 2 3 4 5 6 7 8 9 10 | |
Y en settings.py:
1 2 3 4 | |
Ahora tenemos que definir los métodos del backend, para lo que tenemos dos casos, usar un superusuario que comprueba la autenticación o intentar hacer bind directamente con las credenciales del usuario.
La idea sería hacer esto para hacer un search posterior del username comprobando que el hash de contraseña del formulario coincide con el valor obtenido de LDAP. Ésto lo haríamos en el método authenticate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
De este modo hacemos bind con el superusuario definido en private_settings.pyy buscamos los datos del usuario en cuestión. Ahora tenemos que comprobar el pass, para lo que tenemos que saber cómo calcula el hash ldap, de manera que hagamos la misma transformación (implementación HMAC y clave secreta si la hay). Comprobamos el pass y si la comprobación pasa creamos un usuario al vuelo, sin guardarlo en base de datos. Tenemos la cuestión de cómo se almacena el password.
El método authenticate cambia, ya que intentamos conectar directamente con las credenciales del login:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
Con esto resolveríamos el login. El problema ahora es acceder a una vista autenticada donde el decorador va a llamar al método get_user. Éste método devuelve un usuario dada una clave primaria. Podemos crear un modelo custom donde la clave primaria sea el username, sobreescribiendo el campo username heredado de AbstractUser (la definición del campo está copiada de allí, tras lo que se le ha añadido primary_key=True):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
Y especificarlo en settings.py:
1 2 | |
Pero encontramos ahora dos problemas. El primero es que cuando se hace login Django manda una señal y auth tiene un receiver que guarda en base de datos la fecha en que se ha hecho ese login, lo que exige... una base de datos. Si vemos el código de configuración de auth (que es donde se encuentran las señales normalmente) vemos:
1 2 3 4 | |
Por lo que sólo tenemos que hacer que en nuestro usuario last_login no sea un campo. Para no romper la lógica OO podemos simplemente sobre-escribirlo como propiedad:
1 2 3 4 5 6 | |
Con lo que creamos el backend definitivo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |