Introduction
Lors de la création d'un CustomUser dans Django, une erreur de sécurité courante passe souvent inaperçue : l'oubli d'utiliser UserAdmin dans l'interface d'administration. Cette négligence peut avoir des conséquences, comme l'exposition des mots de passe en clair.
Dans cet article, nous allons voir pourquoi l'héritage de UserAdmin est indispensable et comment l'implémenter correctement pour garantir la sécurité de votre application Django.
Pourquoi personnaliser le modèle User ?
Le modèle User par défaut de Django est excellent pour démarrer rapidement un projet. Cependant, dans la plupart des applications, vous aurez besoin d'ajouter des champs supplémentaires ou de modifier le comportement des utilisateurs. Par exemple, vous voudrez peut-être :
- Utiliser l'email comme identifiant de connexion,
- Ajouter des champs spécifiques à votre utilisateur.
Le modèle utilisateur
La première étape pour créer un CustomUser est d'hériter de AbstractUser. Voici un exemple simple :
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomManager(BaseUserManager):
def create_user(self, username, email, password, **kwargs):
if not username:
raise ValueError("Username must be set")
if not email:
raise ValueError("Email must be set")
user = self.model(username=username, email=self.normalize_email(email), **kwargs)
user.set_password(password)
user.save()
return user
def create_superuser(self, username, email, password, **kwargs):
kwargs.setdefault("is_staff", True)
kwargs.setdefault("is_superuser", True)
if kwargs.get("is_staff") is not True:
raise ValueError("Superuser must have is_staff=True.")
if kwargs.get("is_superuser") is not True:
raise ValueError("Superuser must have is_superuser=True.")
return self.create_user(username, email, password, **kwargs)
class CustomUser(AbstractUser):
email = models.EmailField(unique=True)
zip_code = models.CharField(max_length=5)
REQUIRED_FIELDS = ["email"]
objects = CustomManager()
Dans cet exemple, nous créons un CustomUser plus élaboré avec :
- Un champ email unique obligatoire
- Un champ code postal personnalisé
- Un manager personnalisé pour gérer la création des utilisateurs
Le CustomManager nous permet de :
- Contrôler la création des utilisateurs standards et super-utilisateurs
- Valider la présence des champs obligatoires
- Assurer le hachage correct des mots de passe via `set_password()`
- Normaliser les emails
Jusqu'ici, tout va bien ! Notre modèle est robuste et suit les bonnes pratiques. Mais... il faut penser à ce que l'on va faire dans notre admin.py !
L'administration
Généralement, la première intention est d'enregistrer ce modèle dans l'interface d'administration de Django de cette façon :
from django.contrib import admin
from .models import CustomUser
admin.site.register(CustomUser)
Ou même :
from django.contrib import admin
from .models import CustomUser
@admin.register(CustomUser)
class CustomUserAdmin(admin.ModelAdmin):
list_display = ('username', 'email', 'zip_code', 'is_staff')
search_fields = ('username', 'email')
Malgré tous nos efforts de sécurisation dans le modèle et le manager, cette simple erreur dans l'administration va créer une faille de sécurité majeure, car le formulaire pour modifier le mot de passe est absent.
De ce fait, les mots de passe seront visibles en clair dans l'interface d'administration.
Exemple, ici je crée un super-utilisateur avec la commande :
python manage.py createsuperuser
On voit bien que le mot de passe est correctement haché :
Cependant, si je modifie le mot de passe directement dans l'interface d'administration :
Le mot de passe est visible...
Utiliser UserAdmin
Pour résoudre ces problèmes de sécurité, il faut hériter de UserAdmin et non de ModelAdmin :
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from account.models import CustomUser
class CustomUserAdmin(UserAdmin):
model = CustomUser
# Configuration de la liste des utilisateurs
list_display = ("email", "username", "zip_code", "is_staff", "is_active",)
list_filter = ("email", "is_staff", "is_active",)
search_fields = ("email", "username", "zip_code",)
ordering = ("email",)
# Configuration du formulaire d'édition
fieldsets = UserAdmin.fieldsets + (
("Informations complémentaires", {"fields": ("zip_code",)}),
)
# Configuration du formulaire de création
add_fieldsets = (
(None, {
"classes": ("wide",),
"fields": (
"email", "username", "zip_code", "password1", "password2",
"is_staff", "is_active", "groups", "user_permissions", "is_superuser"
)}
),
)
admin.site.register(CustomUser, CustomUserAdmin)
Maintenant, si j'actualise ma page utilisateur :
Ici, le mécanisme de sécurité de Django fonctionne ! On a un joli message qui explique que le format du mot de passe est invalide, et il refuse de l'afficher.
Je peux donc cliquer sur Reset Password :
Une fois le mot de passe modifié, le hachage a bien fonctionné :
La personnalisation du modèle User est une pratique courante dans Django, mais elle doit être accompagnée des bonnes pratiques de sécurité. L'utilisation de UserAdmin n'est pas qu'une simple formalité : c'est une nécessité pour protéger les données sensibles de vos utilisateurs.
Retour