Accueil / Blog / Métier / 2014 / Combiner une authentification LDAP et l'authentification classique Django

Combiner une authentification LDAP et l'authentification classique Django

Par Florent Lebreton publié 10/01/2014
Contributeurs : Sylvain Boureliou
Comment mixer une authentification LDAP et l'authentification classique Django pour pouvoir garder la possibilité d'avoir des utilisateurs seulement dans Django et pouvoir pallier une éventuelle défaillance de l'annuaire LDAP ?

Toujours pour notre projet de GMAO full-web JOB, nous avons eu besoin de fournir un système d'authentification via un annuaire LDAP. Le module django-auth-ldap fait une grosse partie du travail lui-même mais nous avons décidé de combiner cette authentification LDAP avec l'authentification classique de Django pour deux raisons :

  • garder la possibilité de créer des utilisateurs dans Django qui n'ont pas à exister dans l'annuaire LDAP (pour les superusers par exemple) ;
  • être capable de se replier sur l'authentification Django pour tous les utilisateurs en cas d'une défaillance du serveur LDAP.

Pour cela, nous devons :

  • pouvoir différencier les utilisateurs provenant de l'annuaire LDAP des utilisateurs Django ;
  • stocker le mot de passe des utilisateurs LDAP dans la base de données Django pour pouvoir basculer sur l'authentification classique si besoin ;
  • écrire deux backends d'authentification personnalisés.

 

User model Django

Nous implémentons simplement un custom model user avec un champ ``from_ldap`` pour différencier les utilisateurs LDAP des utilisateurs Django classiques.

    class MyUser(AbstractUser):
        from_ldap = models.BooleanField(
            _('LDAP user'),
            editable=False,
            default=False)

 

Backend d'authentification LDAP

Ce backend LDAP a deux objectifs :

  • enregistrer le mot de passe de l'utilisateur dans la base de données Django, ainsi l'utilisateur pourra se connecter via le backend d'authentification classique lorsque le backend LDAP est désactivé ;
  • forcer le champ ``from_ldap`` à ``True`` quand un utilisateur est créé par ce biais.
    from django_auth_ldap.backend import LDAPBackend
    from django.contrib.auth import get_user_model

    class MyLDAPBackend(LDAPBackend):
        """ A custom LDAP authentication backend """

        def authenticate(self, username, password):
            """ Overrides LDAPBackend.authenticate to save user password in django """

            user = LDAPBackend.authenticate(self, username, password)

            # If user has successfully logged, save his password in django database
            if user:
                user.set_password(password)
                user.save()

            return user

        def get_or_create_user(self, username, ldap_user):
            """ Overrides LDAPBackend.get_or_create_user to force from_ldap to True """
            kwargs = {
                'username': username,
                'defaults': {'from_ldap': True}
            }
            user_model = get_user_model()
            return user_model.objects.get_or_create(**kwargs)

 

Backend d'authentification classique

Nous surchargeons django.contrib.auth.backends.ModelBackend pour s'assurer que les utilisateurs LDAP ne peuvent pas se connecter via ce backend tant que le backend LDAP est actif.

    from django.contrib.auth import get_backends, get_user_model
    from django.contrib.auth.backends import ModelBackend

    class MyAuthBackend(ModelBackend):
        """ A custom authentication backend overriding django ModelBackend """

        @staticmethod
        def _is_ldap_backend_activated():
            """ Returns True if MyLDAPBackend is activated """
            return MyLDAPBackend in [b.__class__ for b in get_backends()]

        def authenticate(self, username, password):
            """ Overrides ModelBackend to refuse LDAP users if MyLDAPBackend is activated """

            if self._is_ldap_backend_activated():
                user_model = get_user_model()
                try:
                    user_model.objects.get(username=username, from_ldap=False)
                except:
                    return None

            user = ModelBackend.authenticate(self, username, password)

            return user

 

Settings Django et solution de repli

En temps normal, nos deux backends sont activés :

  • les utilisateurs LDAP peuvent se connecter uniquement via MyLDAPBackend ;
  • les utilisateurs Django peuvent se connecter via MyAuthBackend.
    AUTHENTICATION_BACKENDS = (
        'accounts.backends.MyLDAPBackend',
        'accounts.backends.MyAuthBackend',
    )

 En cas de défaillance de l'annuaire LDAP, nous avons juste à désactiver MyLDAPBackend et tout le monde peut se connecter avec MyAuthBackend :

    AUTHENTICATION_BACKENDS = (
        #'accounts.backends.MyLDAPBackend',
        'accounts.backends.MyAuthBackend',
    )

 

 

ABONNEZ-VOUS À LA NEWSLETTER !
Voir aussi
Formation Django initiation à Toulouse du 13 au 15 mars Formation Django initiation à Toulouse du 13 au 15 mars 26/01/2017

Entrez de plain-pied dans l'univers de Django aux côtés de développeurs ayant une expérience de ...

Retour sur la PyConFr 2016 Retour sur la PyConFr 2016 18/10/2016

Nous étions présents à Rennes pour PyConFr 2016. Voici notre compte-rendu à chaud.

Wagtail: How to use the Page model and its manager (part 2) Wagtail: How to use the Page model and its manager (part 2) 08/08/2016

The Page model has several methods specific to Wagtail. This is also the case of its manager. We ...

Wagtail : How to make your own content type models (part 1) Wagtail : How to make your own content type models (part 1) 29/07/2016

We are used to initialize our CMS directly from a web interface, often including lots of complex ...

Presentation of the latest Django CMS: Wagtail Presentation of the latest Django CMS: Wagtail 22/07/2016

Wagtail is a quite recent Django CMS. However, its young age does not keep it from having a lot of ...