Ce que vous devez savoir pour gérer les utilisateurs dans Django Admin – Real Python

By | août 5, 2019

trouver un expert Python

La gestion des utilisateurs dans Django admin est un sujet délicat. Si vous imposez trop d'autorisations, vous pouvez interférer avec les opérations quotidiennes. Si vous permettez que les autorisations soient accordées librement et sans supervision, vous mettez votre système en danger.

Django fournit un bon cadre d'authentification avec une intégration étroite à l'administrateur Django. Dès le départ, l’administrateur Django n’applique pas de restrictions particulières à l’administrateur de l’utilisateur. Cela peut conduire à des scénarios dangereux pouvant compromettre votre système.

Saviez-vous que les utilisateurs qui gèrent d'autres utilisateurs de l'administrateur peuvent modifier leurs propres autorisations? Saviez-vous qu'ils peuvent aussi se rendre superutilisateurs? Rien dans Django admin n’empêche cela, c’est donc à vous de jouer!

À la fin de ce didacticiel, vous saurez comment protéger votre système:

  • Protéger contre l'escalade des autorisations en empêchant les utilisateurs de modifier leurs propres autorisations
  • Gardez les autorisations bien rangées et maintenables en forçant uniquement les utilisateurs à gérer les autorisations uniquement à l'aide de groupes
  • Empêcher les autorisations de fuir via des actions personnalisées en appliquant explicitement les autorisations nécessaires

Autorisations de modèle

Les autorisations sont délicates. Si vous ne définissez pas les autorisations, vous exposez votre système à des intrusions, des fuites de données et des erreurs humaines. Si vous abusez des autorisations ou si vous les utilisez trop, vous risquez d'interférer avec les opérations quotidiennes.

Django est livré avec un système d'authentification intégré. Le système d'authentification comprend des utilisateurs, des groupes et des autorisations.

Lorsqu'un modèle est créé, Django crée automatiquement quatre autorisations par défaut pour les actions suivantes:

  1. ajouter: Les utilisateurs disposant de cette autorisation peuvent ajouter une instance du modèle.
  2. effacer: Les utilisateurs disposant de cette autorisation peuvent supprimer une instance du modèle.
  3. changement: Les utilisateurs disposant de cette autorisation peuvent mettre à jour une instance du modèle.
  4. vue: Les utilisateurs disposant de cette autorisation peuvent afficher les instances de ce modèle. Cette autorisation était très attendue et elle a finalement été ajoutée à Django 2.1.

Les noms d'autorisations suivent une convention de dénomination très spécifique: ._.

Décomposons cela:

  • est le nom de l'application. Par exemple, le Utilisateur le modèle est importé du auth app (django.contrib.auth).
  • est l'une des actions ci-dessus (ajouter, effacer, changement, ou vue).
  • est le nom du modèle, en toutes lettres minuscules.

La connaissance de cette convention de dénomination peut vous aider à gérer les autorisations plus facilement. Par exemple, le nom de l’autorisation de changer d’utilisateur est auth.change_user.

Comment vérifier les autorisations

Les autorisations de modèle sont accordées à des utilisateurs ou à des groupes. Pour vérifier si un utilisateur dispose d'une autorisation donnée, vous pouvez procéder comme suit:

>>>

>>> de django.contrib.auth.models importation Utilisateur
>>> vous = Utilisateur.objets.Créer un utilisateur(Nom d'utilisateur='haki')
>>> vous.has_perm('auth.change_user')
Faux

Il convient de mentionner que .has_perm () reviendra toujours Vrai pour le superutilisateur actif, même si l’autorisation n’existe pas vraiment:

>>>

>>> de django.contrib.auth.models importation Utilisateur
>>> super-utilisateur = Utilisateur.objets.create_superuser(
...     Nom d'utilisateur='superhaki',
...     email='me@hakibenita.com',
...     mot de passe='secret',
)
>>> super-utilisateur.has_perm('n'existe pas')
Vrai

Comme vous pouvez le constater, lorsque vous vérifiez les autorisations d’un superutilisateur, elles ne sont pas réellement vérifiées.

Comment appliquer les autorisations

Les modèles Django n'appliquent pas les autorisations eux-mêmes. Django Admin est par défaut l’autorisation d’emplacement.

La raison pour laquelle les modèles n’appliquent pas les autorisations est que, normalement, le modèle n’a pas conscience que l’utilisateur exécute l’action. Dans les applications Django, l'utilisateur est généralement obtenu à partir de la demande. C'est pourquoi, la plupart du temps, les autorisations sont appliquées au niveau de la couche de vue.

Par exemple, pour empêcher un utilisateur de ne pas voir les autorisations sur le Utilisateur Pour que le modèle accède à une vue contenant des informations sur l'utilisateur, procédez comme suit:

de django.core.exceptions importation Permission refusée

def users_list_view(demande):
    si ne pas demande.utilisateur.has_perm('auth.view_user'):
        élever Permission refusée()

Si l'utilisateur à l'origine de la demande s'est connecté et a été authentifié, request.user tiendra une instance de Utilisateur. Si l'utilisateur ne s'est pas connecté, alors request.user sera une instance de Utilisateur anonyme. C'est un objet spécial utilisé par Django pour indiquer un utilisateur non authentifié. En utilisant has_perm sur Utilisateur anonyme reviendra toujours Faux.

Si l'utilisateur qui fait la demande n'a pas le view_user permission, alors vous soulevez un Permission refusée exception, et une réponse avec le statut 403 est retourné au client.

Pour faciliter l’application des autorisations dans les vues, Django fournit un décorateur de raccourcis appelé permission_required cela fait la même chose:

de django.contrib.auth.decorators importation permission_required

@permission_required('auth.view_user')
def users_list_view(demande):
    passer

Pour appliquer les autorisations dans les modèles, vous pouvez accéder aux autorisations des utilisateurs actuels via une variable de modèle spéciale appelée permanentes. Par exemple, si vous souhaitez afficher un bouton de suppression uniquement pour les utilisateurs disposant de l'autorisation de suppression, procédez comme suit:

% si perms.auth.delete_user %
<bouton>Supprimer l'utilisateur!</bouton>
% fin si %

Certaines applications tierces populaires telles que la structure de repos Django fournissent également une intégration utile avec les autorisations du modèle Django.

Django Admin et autorisations de modèle

L'administrateur Django a une intégration très étroite avec le système d'authentification intégré, et les autorisations de modèle en particulier. Django admin applique immédiatement les autorisations de modèle:

  • Si l'utilisateur ne dispose d'aucune autorisation sur un modèle, il ne pourra ni le voir ni y accéder via l'administrateur.
  • Si l'utilisateur dispose des autorisations d'affichage et de modification sur un modèle, il pourra afficher et mettre à jour les instances, mais ne pourra pas ajouter de nouvelles instances ni supprimer celles qui existent.

Avec les autorisations appropriées en place, les utilisateurs administrateurs sont moins susceptibles de commettre des erreurs et les intrus auront plus de mal à causer des torts.

Implémenter des rôles commerciaux personnalisés dans Django Admin

Le système d'authentification est l'un des endroits les plus vulnérables de chaque application. Dans les applications Django, c’est le Utilisateur modèle. Donc, pour mieux protéger votre application, vous allez commencer par le Utilisateur modèle.

Tout d'abord, vous devez prendre le contrôle de la Utilisateur modèle page d'administration. Django est déjà livré avec une très belle page d’administration pour gérer les utilisateurs. Pour tirer parti de cet excellent travail, vous allez étendre la fonction intégrée Utilisateur modèle d'admin.

Configuration: Un administrateur utilisateur personnalisé

Pour fournir un administrateur personnalisé pour le Utilisateur modèle, vous devez annuler l’enregistrement du modèle existant fourni par Django et enregistrer l’un des vôtres:

de django.contrib importation admin
de django.contrib.auth.models importation Utilisateur
de django.contrib.auth.admin importation UserAdmin

# Annuler l'enregistrement du modèle fourni
admin.site.se désinscrire(Utilisateur)

# Enregistrer son propre modèle d'administrateur, basé sur le UserAdmin par défaut
@admin.registre(Utilisateur)
classe CustomUserAdmin(UserAdmin):
    passer

Votre CustomUserAdmin étend la gamme de Django UserAdmin. Vous avez fait cela pour que vous puissiez tirer parti de tout le travail déjà réalisé par les développeurs de Django.

À ce stade, si vous vous connectez à votre administrateur Django à l’adresse suivante: http://127.0.0.1:8000/admin/auth/user, vous devriez voir l'utilisateur admin inchangé:

Administrateur Django

En étendant UserAdmin, vous pouvez utiliser toutes les fonctionnalités intégrées fournies par l’administrateur Django.

Empêcher la mise à jour des champs

Les formulaires d’administration sans surveillance sont des candidats de choix pour d’horribles erreurs. Un utilisateur du personnel peut facilement mettre à jour une instance de modèle via l'administrateur d'une manière inattendue par l'application. La plupart du temps, l'utilisateur ne remarquera même pas que quelque chose ne va pas. De telles erreurs sont généralement très difficiles à repérer et à corriger.

Pour éviter que de telles erreurs ne se produisent, vous pouvez empêcher les utilisateurs administrateurs de modifier certains champs du modèle.

Si vous souhaitez empêcher tout utilisateur, y compris les superutilisateurs, de mettre à jour un champ, vous pouvez le marquer comme étant en lecture seule. Par exemple, le champ date_joined est défini lorsqu'un utilisateur s'enregistre. Ces informations ne doivent jamais être modifiées par un utilisateur. Vous devez donc les marquer en lecture seule:

de django.contrib importation admin
de django.contrib.auth.models importation Utilisateur
de django.contrib.auth.admin importation UserAdmin

@admin.registre(Utilisateur)
classe CustomUserAdmin(UserAdmin):
    readonly_fields = [[[[
        'date_joined',
    ]

Lorsqu'un champ est ajouté à readonly_fields, il ne sera pas éditable dans le formulaire de modification par défaut de l'administrateur. Lorsqu'un champ est marqué comme étant en lecture seule, Django rendra l'élément d'entrée comme étant désactivé.

Mais que faire si vous voulez empêcher seulement certains utilisateurs de mettre à jour un champ?

Empêcher conditionnellement la mise à jour des champs

Parfois, il est utile de mettre à jour les champs directement dans l’administrateur. Mais vous ne voulez laisser aucun utilisateur le faire: vous voulez autoriser uniquement les superutilisateurs à le faire.

Supposons que vous souhaitiez empêcher les non-utilisateurs de modifier le nom d'utilisateur d'un utilisateur. Pour ce faire, vous devez modifier le formulaire de modification généré par Django et désactiver le champ Nom d'utilisateur en fonction de l'utilisateur actuel:

de django.contrib importation admin
de django.contrib.auth.models importation Utilisateur
de django.contrib.auth.admin importation UserAdmin

@admin.registre(Utilisateur)
classe CustomUserAdmin(UserAdmin):
    def get_form(soi, demande, obj=Aucun, **Kwargs):
        forme = super().get_form(demande, obj, **Kwargs)
        is_superuser = demande.utilisateur.is_superuser

        si ne pas is_superuser:
            forme.base_fields[[[['Nom d'utilisateur'].désactivé = Vrai

        revenir forme

Décrivons-le:

  • Pour apporter des modifications au formulaire, vous remplacez get_form (). Django utilise cette fonction pour générer un formulaire de modification par défaut pour un modèle.
  • Pour désactiver le champ de manière conditionnelle, vous devez d'abord extraire le formulaire par défaut généré par Django, puis si l'utilisateur n'est pas un superutilisateur, désactivez le champ nom d'utilisateur.

Désormais, lorsqu'un non-superutilisateur tente de modifier un utilisateur, le champ Nom d'utilisateur est désactivé. Toute tentative de modification du nom d'utilisateur via Django Admin échouera. Lorsqu'un super-utilisateur tente de modifier l'utilisateur, le champ du nom d'utilisateur est éditable et se comporte comme prévu.

Empêcher les non-superutilisateurs d'octroyer des droits de superutilisateur

Superuser est une autorisation très forte qui ne devrait pas être accordée à la légère. Cependant, tout utilisateur avec une autorisation de modification sur le Utilisateur model peut transformer n'importe quel utilisateur en superutilisateur, y compris lui-même. Cela va à l’encontre de l’objet du système de permission, vous voulez donc fermer ce trou.

Sur la base de l'exemple précédent, pour empêcher les non-superutilisateurs de se faire superutilisateurs, ajoutez la restriction suivante:

de dactylographie importation Ensemble

de django.contrib importation admin
de django.contrib.auth.models importation Utilisateur
de django.contrib.auth.admin importation UserAdmin

@admin.registre(Utilisateur)
classe CustomUserAdmin(UserAdmin):
    def get_form(soi, demande, obj=Aucun, **Kwargs):
        forme = super().get_form(demande, obj, **Kwargs)
        is_superuser = demande.utilisateur.is_superuser
        Champs_handicapés = ensemble()  # type: Set[str]

        si ne pas is_superuser:
            Champs_handicapés | = 
                'Nom d'utilisateur',
                'is_superuser',
            

        pour F dans Champs_handicapés:
            si F dans forme.base_fields:
                forme.base_fields[[[[F].désactivé = Vrai

        revenir forme

En plus de l'exemple précédent, vous avez ajouté les éléments suivants:

  1. Vous avez initialisé un ensemble vide Champs_handicapés qui contiendra les champs à désactiver. ensemble est une structure de données qui contient des valeurs uniques. Dans ce cas, il est judicieux d’utiliser un ensemble, car il suffit de désactiver un champ une fois. L'opérateur | = est utilisé pour effectuer un sur place OU mettre à jour. Pour plus d'informations sur les ensembles, consultez Ensembles en Python.

  2. Ensuite, si l’utilisateur est un superutilisateur, vous ajoutez deux champs à l’ensemble (Nom d'utilisateur de l'exemple précédent, et is_superuser). Ils empêcheront les non-superutilisateurs de se faire superutilisateurs.

  3. Enfin, vous parcourez les champs de l'ensemble, les marquez tous comme désactivés et vous retournez le formulaire.

Django User Admin Formulaire en deux étapes

Lorsque vous créez un nouvel utilisateur dans l'administrateur Django, vous passez par un formulaire en deux étapes. Dans le premier formulaire, vous indiquez le nom d'utilisateur et le mot de passe. Dans le deuxième formulaire, vous mettez à jour le reste des champs.

Ce processus en deux étapes est unique à la Utilisateur modèle. Pour gérer ce processus unique, vous devez vérifier que le champ existe avant de tenter de le désactiver. Sinon, vous pourriez obtenir un KeyError. Cela n'est pas nécessaire si vous personnalisez d'autres administrateurs de modèle.

Pour plus d'informations sur KeyError, consultez les exceptions Python KeyError et comment les gérer.

Accorder des autorisations uniquement à l'aide de groupes

La manière dont les autorisations sont gérées est très spécifique à chaque équipe, produit et entreprise. J'ai trouvé qu'il est plus facile de gérer les autorisations dans des groupes. Dans mes propres projets, je crée des groupes de support, des éditeurs de contenu, des analystes, etc. J'ai constaté que la gestion des autorisations au niveau de l'utilisateur peut être une vraie galère. Lorsque de nouveaux modèles sont ajoutés ou lorsque les exigences de l’entreprise changent, il est fastidieux de mettre à jour chaque utilisateur.

Pour gérer les autorisations uniquement à l'aide de groupes, vous devez empêcher les utilisateurs d'attribuer des autorisations à des utilisateurs spécifiques. Au lieu de cela, vous souhaitez uniquement autoriser l'association d'utilisateurs à des groupes. Pour ce faire, désactivez le champ user_permissions pour tous les non-utilisateurs:

de dactylographie importation Ensemble

de django.contrib importation admin
de django.contrib.auth.models importation Utilisateur
de django.contrib.auth.admin importation UserAdmin

@admin.registre(Utilisateur)
classe CustomUserAdmin(UserAdmin):
    def get_form(soi, demande, obj=Aucun, **Kwargs):
        forme = super().get_form(demande, obj, **Kwargs)
        is_superuser = demande.utilisateur.is_superuser
        Champs_handicapés = ensemble()  # type: Set[str]

        si ne pas is_superuser:
            Champs_handicapés | = 
                'Nom d'utilisateur',
                'is_superuser',
                'user_permissions',
            

        pour F dans Champs_handicapés:
            si F dans forme.base_fields:
                forme.base_fields[[[[F].désactivé = Vrai

        revenir forme

Vous avez utilisé exactement la même technique que dans les sections précédentes pour implémenter une autre règle de gestion. Dans les sections suivantes, vous allez implémenter des règles métier plus complexes pour protéger votre système.

Empêcher les non-utilisateurs de modifier leurs propres autorisations

Les utilisateurs puissants sont souvent un point faible. Ils possèdent de fortes autorisations et les dommages potentiels qu’ils peuvent causer sont importants. Pour empêcher l’augmentation des autorisations en cas d’intrusion, vous pouvez empêcher les utilisateurs de modifier leurs propres autorisations:

de dactylographie importation Ensemble

de django.contrib importation admin
de django.contrib.auth.models importation Utilisateur
de django.contrib.auth.admin importation UserAdmin

@admin.registre(Utilisateur)
classe CustomUserAdmin(UserAdmin):
    def get_form(soi, demande, obj=Aucun, **Kwargs):
        forme = super().get_form(demande, obj, **Kwargs)
        is_superuser = demande.utilisateur.is_superuser
        Champs_handicapés = ensemble()  # type: Set[str]

        si ne pas is_superuser:
            Champs_handicapés | = 
                'Nom d'utilisateur',
                'is_superuser',
                'user_permissions',
            

        # Empêcher les non-utilisateurs de modifier leurs propres autorisations
        si (
            ne pas is_superuser
            et obj est ne pas Aucun
            et obj == demande.utilisateur
        ):
            Champs_handicapés | = 
                'is_staff',
                'is_superuser',
                'groupes',
                'user_permissions',
            

        pour F dans Champs_handicapés:
            si F dans forme.base_fields:
                forme.base_fields[[[[F].désactivé = Vrai

        revenir forme

L'argument obj est l'instance de l'objet sur lequel vous opérez actuellement:

  • Quand obj est aucun, le formulaire est utilisé pour créer un nouvel utilisateur.
  • Quand obj n'est pas Aucun, le formulaire est utilisé pour éditer un utilisateur existant.

Pour vérifier si l’utilisateur qui fait la demande fonctionne sur lui-même, vous comparez request.user avec obj. Parce que c'est l'utilisateur admin, obj est soit une instance de Utilisateur, ou Aucun. Lorsque l'utilisateur qui fait la demande, request.user, est égal à obj, alors cela signifie que l'utilisateur se met à jour. Dans ce cas, vous désactivez tous les champs sensibles pouvant être utilisés pour obtenir des autorisations.

La possibilité de personnaliser le formulaire en fonction de l'objet est très utile. Il peut être utilisé pour implémenter des rôles métiers élaborés.

Ignorer les autorisations

Il peut parfois être utile de remplacer complètement les autorisations dans l'administrateur Django. Un scénario courant survient lorsque vous utilisez des autorisations ailleurs et que vous ne souhaitez pas que les utilisateurs du personnel apportent des modifications à l'administrateur.

Django utilise des points d'ancrage pour les quatre autorisations intégrées. En interne, les points d’accès utilisent les autorisations de l’utilisateur actuel pour prendre une décision. Vous pouvez remplacer ces crochets et donner une décision différente.

Pour empêcher les utilisateurs du personnel de supprimer une instance de modèle, quelles que soient leurs autorisations, vous pouvez procéder comme suit:

de django.contrib importation admin
de django.contrib.auth.models importation Utilisateur
de django.contrib.auth.admin importation UserAdmin

@admin.registre(Utilisateur)
classe CustomUserAdmin(UserAdmin):
    def has_delete_permission(soi, demande, obj=Aucun):
        revenir Faux

Comme avec get_form (), obj est l'instance sur laquelle vous opérez actuellement:

  • Quand obj est Aucun, l'utilisateur a demandé la vue liste.
  • Quand obj n'est pas Aucun, l’utilisateur a demandé la vue de modification d’une instance spécifique.

Avoir l'instance de l'objet dans ce raccordement est très utile pour implémenter des autorisations au niveau objet pour différents types d'actions. Voici d'autres cas d'utilisation:

  • Prévenir les changements pendant les heures de bureau
  • Implémentation d'autorisations au niveau objet

Restreindre l'accès aux actions personnalisées

Les actions d'administration personnalisées nécessitent une attention particulière. Django ne les connaît pas, donc il ne peut pas restreindre leur accès par défaut. Une action personnalisée sera accessible à tout utilisateur administrateur avec n'importe quelle autorisation sur le modèle.

Pour illustrer cela, ajoutez une action d’administrateur pratique pour marquer plusieurs utilisateurs comme actifs:

de django.contrib importation admin
de django.contrib.auth.models importation Utilisateur
de django.contrib.auth.admin importation UserAdmin

@admin.registre(Utilisateur)
classe CustomUserAdmin(UserAdmin):
    actes = [[[[
        'activate_users',
    ]

    def activate_users(soi, demande, Requête):
        cnt = Requête.filtre(c'est actif=Faux).mettre à jour(c'est actif=Vrai)
        soi.message_user(demande, 'Activé     utilisateurs.'.format(cnt))
    activate_users.brève description = 'Activer les utilisateurs'  # type: ignorer

En utilisant cette action, un utilisateur du personnel peut marquer un ou plusieurs utilisateurs et les activer tous en même temps. Ceci est utile dans toutes sortes de cas, par exemple si vous aviez un bogue dans le processus d'enregistrement et si vous deviez activer les utilisateurs en bloc.

Cette action met à jour les informations utilisateur, de sorte que seuls les utilisateurs disposant d'autorisations de modification puissent l'utiliser.

Django admin utilise une fonction interne pour obtenir des actions. Cacher activate_users () des utilisateurs sans autorisation de changement, remplacer get_actions ():

de django.contrib importation admin
de django.contrib.auth.models importation Utilisateur
de django.contrib.auth.admin importation UserAdmin

@admin.registre(Utilisateur)
classe CustomUserAdmin(UserAdmin):
    actes = [[[[
        'activate_users',
    ]

    def activate_users(soi, demande, Requête):
        affirmer demande.utilisateur.has_perm('auth.change_user')
        cnt = Requête.filtre(c'est actif=Faux).mettre à jour(c'est actif=Vrai)
        soi.message_user(demande, 'Activé     utilisateurs.'.format(cnt))
    activate_users.brève description = 'Activer les utilisateurs'  # type: ignorer

    def get_actions(soi, demande):
        actes = super().get_actions(demande)
        si ne pas demande.utilisateur.has_perm('auth.change_user'):
            del actes[[[['activate_users']
        revenir actes

get_actions () renvoie un OrdreDict. La clé est le nom de l'action et la valeur est la fonction d'action. Pour ajuster la valeur de retour, remplacez la fonction, récupérez la valeur d'origine et, en fonction des autorisations de l'utilisateur, supprimez l'action personnalisée. activate_users du dict. Pour plus de sécurité, vous devez également définir l'autorisation de l'utilisateur dans l'action.

Pour les utilisateurs du personnel sans changer d'utilisateur() autorisations, l'action activate_users n'apparaîtra pas dans la liste déroulante des actions.

Conclusion

Django admin est un excellent outil pour gérer un projet Django. De nombreuses équipes en dépendent pour rester productif dans la gestion des opérations quotidiennes. Si vous utilisez l’administrateur Django pour effectuer des opérations sur des modèles, il est important de connaître les autorisations. Les techniques décrites dans cet article sont utiles pour tout administrateur de modèle, pas seulement Utilisateur modèle.

Dans ce tutoriel, vous avez protégé votre système en effectuant les ajustements suivants dans Django Admin:

  • Vous protégé contre l'escalade des autorisations en empêchant les utilisateurs de modifier leurs propres autorisations.
  • Vous garder les autorisations bien rangées et maintenables en forçant uniquement les utilisateurs à gérer les autorisations uniquement à l'aide de groupes.
  • Vous empêché les autorisations de fuir par des actions personnalisées en appliquant explicitement les autorisations nécessaires.

Votre Utilisateur modèle admin est maintenant beaucoup plus sûr que lorsque vous avez commencé!