Backend de Autenticación LDAP para Django

Django es un web framework basado en python, con muchas bondades(que no voy a detallar en este post), y que , particularmente posee un sistema de autenticación muy flexible, el cual nos permite autenticar contra una base de datos por defecto, pero como sabemos, nuestra fuente de autenticacion no suele ser siempre una base de datos.

En ese caso, necesitamos nuestra propio backend para poder autenticar contra esa fuente, Django nos permite escribir nuestro propio backend de autenticacion, de forma que podamos autenticar nuestros usuarios con nuestra fuente de autenticacion ya existente.

Nosotros podriamos, por ejemplo, tener un servidor LDAP , que almacene a nuestros usuarios y sus passwords, asi que podriamos usarlo para autenticar en nuestra aplicacion Django. Basicamente, un backend de autenticacion para Django consta de una clase que implementa dos metodos, get_user(user_id), el cual devuelve un objeto User de Django, y authenticate(**credentials), en el cual debemos verificar el usuario con su password, y retornar un objeto User de Django.

Lo primero que debemos hacer es definir nuestro nuevo backend de autenticacion ­en settings.py, de forma que Django lo use:

AUTHENTICATION_BACKENDS = (
'proyecto.utils.ldapauthbackend.LDAPBackend',
'django.contrib.auth.backends.ModelBackend',
);

Inplementando todo esto, nos queda la siguente clase:

import ldap
from django.contrib.auth.models import User

class LDAPBackend:

	#­Metodo base de autenticacion
	def authenticate(self, username=None, password=None):
		#base de nuestro arbol ldap
		AUTH_LDAP_BASE="dc=gerardo,dc=com,dc=ve"
		AUTH_LDAP_BASE="dc=gerardo,dc=com,dc=ve"
		AUTH_LDAP_SERVER = "el.servidor.ldap"
		#si ambos base_user y base_pass son vacios,
		#se realiza acceso anonimo al arbol ldap

		AUTH_LDAP_BASE_USER = ""
		AUTH_LDAP_BASE_PASS = ""
        base = AUTH_LDAP_BASE
        scope = ldap.SCOPE_SUBTREE
        #Filtro de busqueda en nuestro arbol ldap
        filter = "(&(objectclass=person) (uid=%s))" % username
        ret = ['dn']

        # Autenticamos el  usuario base
        try:
            l = ldap.open(AUTH_LDAP_SERVER)
            l.protocol_version = ldap.VERSION3
            l.simple_bind_s(AUTH_LDAP_BASE_USER,AUTH_LDAP_BASE_PASS)
        except ldap.LDAPError:
            return None

        try:
            result_id = l.search(base, scope, filter, ret)
            result_type, result_data = l.result(result_id, 0)

            # Si el usuario no existe, falla.
            if (len(result_data) != 1):
                return None

            # Autenticar con DN de usuario
            l.simple_bind_s(result_data[0][0],password)

            try:
                user = User.objects.get(username__exact=username)
            except:
            	#Este fragmento es importante, aun cuando autenticamos
            	#contra un servidor ldap, necesitamos crear un usuario
            	#en la bd de django, pero en este caso, creamos un
            	#password temporal, de forma que no pueda usar ese
            	#objeto para autenticarse posteriormente, sino nuestro
            	#Servidor LDAP
                from random import choice
                import string
                temp_pass = ""
                for i in range(8):
                    temp_pass = temp_pass + choice(string.letters)
                user = User.objects.create_user(username,username + '@gerardo.com.ve',temp_pass)
                user.is_staff = False
                user.save()
            return user

        except ldap.INVALID_CREDENTIALS:
            return None

    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

En este caso, he usado python-ldap, el cual esta en los repositorios de Debian. La implementación de este backend con ldaptor , la cual es una libreria python nativa para LDAP, resulta trivial de igual forma.


  • [quote comment=""]Interesante.

    ¿qué colocas en el DATABASE_ENGINE del setting.py?

    Lo pregunto porque me queda la duda de dónde sale la instancia de User que obtienes con "User.objects.get(pk=user_id)"

    ¿Lo obtienes del Ldap? en ese caso me gustaría saber qué engine usas.

    ¿Lo obtienes de otra fuente? entonces entendería que el método authenticate trabaja contra el ldap pero el get_user trabaja contra otra base de datos y supondría entonces que mantienes la información de los usuarios en dos lugares, por lo que me gustaría saber cómo los mantienes sincronizados.

    Muchas gracias de antemano y mantengamonos en contacto para cualquier tema que tenga que ver con django.

    Saludos.

    [/quote]

    Esto sirve con cualquier backend, realmente no sincronizamos LDAP con nuestro backend de datos, simplemente autenticamos con ldap, y creamos un user (dado que usamos django.contrib.auth, debemos hacerlo), pero no guardamos el password alli, sino que generamos un password aleatorio, ya que , aunque necesitamos crear una instancia User, no la usamos para autenticar.

    Lo del password aleatorio se debe a que en settings.AUTHENTICATION_BACKENDS se colocan tus backends, y django intenta autenticar buscando usuarios en cada uno de primero a ultimo hasta que lo encuentra.

    Obviamente, no deseamos que use la instancia User de nuestra bd directamente.

    En resumen, la autenticación en contra ldap, desacoplada de django(nada de middlewares de almacenaje), y guardamos un User por estar usando django.contrib.auth

    Saludos
  • Interesante.

    ¿qué colocas en el DATABASE_ENGINE del setting.py?

    Lo pregunto porque me queda la duda de dónde sale la instancia de User que obtienes con "User.objects.get(pk=user_id)"

    ¿Lo obtienes del Ldap? en ese caso me gustaría saber qué engine usas.

    ¿Lo obtienes de otra fuente? entonces entendería que el método authenticate trabaja contra el ldap pero el get_user trabaja contra otra base de datos y supondría entonces que mantienes la información de los usuarios en dos lugares, por lo que me gustaría saber cómo los mantienes sincronizados.

    Muchas gracias de antemano y mantengamonos en contacto para cualquier tema que tenga que ver con django.

    Saludos.
blog comments powered by Disqus