Makina Blog

Le blog Makina-corpus

Combiner une authentification LDAP et l'authentification classique Django


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',
    )

 

 

Formations associées

Formations Django

Formation Django avancé

À distance (FOAD) Du 9 au 13 décembre 2024

Voir la formation

Actualités en lien

Image
Django PostgreSQL
07/11/2023

Utiliser des fonctions PostgreSQL dans des contraintes Django

Cet article vous présente comment utiliser les fonctions et les check constraints PostgreSQL en tant que contrainte sur vos modèles Django.

Voir l'article
Image
Encart Django
06/11/2023

Comment migrer vers une version récente de Django ?

Que ce soit pour avoir les dernières fonctionnalités ou les correctifs de sécurité, rester sur une version récente de Django est important pour la pérennité de son projet.

Voir l'article
Image
Agrégateur Geotrek
08/06/2023

Le projet Agrégateur : fusionner des bases de données Geotrek

Le partage et la diffusion des données font partie des problématiques historiques au cœur du projet Geotrek.

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus