Personnalisation de l'authentification utilisateur dans un système CRM

13.1. Personnalisation de l'authentification utilisateur

  • class Meta:
  • abstract = True (ne crée pas de table, transmet uniquement les champs aux classes enfants)
  • Méthode de cryptage Django : md5 + sel
  • Compte
  • LADP : protocole de gestion de comptes de répertoire léger (gestion centralisée des comptes) : vérification sur le serveur LDAP via le réseau
  • SSO : Single Sign on (connexion unique)

(1) settings.py

AUTH_USER_MODEL = 'crm.UserProfile'

(2) crm/models.py

from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models

class UserProfileManager(BaseUserManager):
    def create_user(self, email, name, password=None):
        if not email:
            raise ValueError('Les utilisateurs doivent avoir une adresse e-mail')

        user = self.model(
            email=self.normalize_email(email),
            name=name,
        )

        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, name, password):
        user = self.create_user(
            email,
            password=password,
            name=name,
        )
        user.is_admin = True
        user.save(using=self._db)
        return user

class UserProfile(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(
        verbose_name='adresse e-mail',
        max_length=255,
        unique=True,
    )
    name = models.CharField(max_length=64)
    role = models.ManyToManyField('Role', blank=True, null=True)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=False)
    objects = UserProfileManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['name']

    def __str__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, app_label):
        return True

    def get_full_name(self):
        return self.email

    def get_short_name(self):
        return self.email

    @property
    def is_staff(self):
        return self.is_admin

(3) crm/admin.py

from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.forms import ReadOnlyPasswordHashField
from crm.models import UserProfile

class UserCreationForm(forms.ModelForm):
    password1 = forms.CharField(label='Mot de passe', widget=forms.PasswordInput)
    password2 = forms.CharField(label='Confirmation du mot de passe', widget=forms.PasswordInput)

    class Meta:
        model = UserProfile
        fields = ('email', 'name')

    def clean_password2(self):
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Les mots de passe ne correspondent pas")
        return password2

    def save(self, commit=True):
        user = super(UserCreationForm, self).save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user

class UserChangeForm(forms.ModelForm):
    password = ReadOnlyPasswordHashField()

    class Meta:
        model = UserProfile
        fields = ('email', 'password', 'name', 'is_active', 'is_superuser')

    def clean_password(self):
        return self.initial["password"]

class UserProfileAdmin(BaseUserAdmin):
    form = UserChangeForm
    add_form = UserCreationForm

    list_display = ('email', 'name', 'is_superuser')
    list_filter = ('is_superuser',)
    fieldsets = (
        (None, {'fields': ('email', 'password')}),
        ('Informations personnelles', {'fields': ('name',)}),
        ('Permissions', {'fields': ('is_staff', 'is_active', 'role', 'user_permissions', 'groups', 'is_superuser')}),
    )
    add_fieldsets = (
        (None, {
            'classes': ('wide',),
            'fields': ('email', 'name', 'password1', 'password2')}
        ),
    )
    search_fields = ('email',)
    ordering = ('email',)
    filter_horizontal = ('role', 'user_permissions', 'groups')

admin.site.register(UserProfile, UserProfileAdmin)

14.1. Conception d'un cadre de permissions générique

(1) kingadmin/permission_list.py

# kingadmin/permission_list.py

perm_dic = {
    'crm_table_list': ['table_obj_list', 'GET', [], {}],  # Peut voir toutes les données de chaque table
    'crm_table_list_view': ['table_obj_change', 'GET', [], {}],  # Peut accéder à la page de modification de chaque enregistrement
    'crm_table_list_change': ['table_obj_change', 'POST', [], {}],  # Peut modifier chaque enregistrement
    'crm_table_list_add_view': ['table_obj_add', 'GET', [], {}],  # Peut accéder à la page d'ajout de données
    'crm_table_list_add': ['table_obj_add', 'POST', [], {}],  # Peut ajouter des données à la table
}

(2) kingadmin/permissions.py

# kingadmin/permissions.py

from django.urls import resolve
from django.shortcuts import render, redirect
from kingadmin.permission_list import perm_dic
from django.conf import settings

def perm_check(*args, **kwargs):
    request = args[0]
    resolve_url_obj = resolve(request.path)
    current_url_name = resolve_url_obj.url_name
    match_results = [None,]
    match_key = None

    if not request.user.is_authenticated:
        return redirect(settings.LOGIN_URL)

    for permission_key, permission_val in perm_dic.items():
        per_url_name = permission_val[0]
        per_method = permission_val[1]
        perm_args = permission_val[2]
        perm_kwargs = permission_val[3]

        if per_url_name == current_url_name and per_method == request.method:
            args_matched = False
            for item in perm_args:
                request_method_func = getattr(request, per_method)
                if request_method_func.get(item, None):
                    args_matched = True
                else:
                    args_matched = False
                    break
            else:
                args_matched = True

            kwargs_matched = False
            for k, v in perm_kwargs.items():
                request_method_func = getattr(request, per_method)
                arg_val = request_method_func.get(k, None)
                if arg_val == str(v):
                    kwargs_matched = True
                else:
                    kwargs_matched = False
                    break
            else:
                kwargs_matched = True

            match_results = [args_matched, kwargs_matched]
            if all(match_results):
                match_key = permission_key
                break

    if all(match_results):
        app_name, *per_name = match_key.split('_')
        perm_obj = '%s.%s' % (app_name, match_key)
        if request.user.has_perm(perm_obj):
            return True
        else:
            return False
    else:
        return False

def check_permission(func):
    def inner(*args, **kwargs):
        if not perm_check(*args, **kwargs):
            request = args[0]
            return render(request, 'kingadmin/page_403.html')
        return func(*args, **kwargs)
    return inner

(3) page_403.html

{% extends 'kingadmin/base.html' %}

{% block body %}
<div class="row col-lg-offset-2">
    <h1 style="font-size: 200px;">403</h1>
    <h4>Vous n'avez pas la permission d'accéder à cette page</h4>
</div>
{% endblock %}

(4) crm/models.py

class UserProfile(AbstractBaseUser, PermissionsMixin):
    email = models.EmailField(
        verbose_name='adresse e-mail',
        max_length=255,
        unique=True,
    )
    name = models.CharField(max_length=64)
    role = models.ManyToManyField('Role', blank=True, null=True)
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    is_staff = models.BooleanField(default=True)
    objects = UserProfileManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['name']

    def __str__(self):
        return self.email

    class Meta:
        permissions = (
            ('crm_table_list', 'Peut voir toutes les données de chaque table'),
            ('crm_table_list_view', 'Peut accéder à la page de modification de chaque enregistrement'),
            ('crm_table_list_change', 'Peut modifier chaque enregistrement'),
            ('crm_table_list_add_view', 'Peut accéder à la page d\'ajout de données'),
            ('crm_table_list_add', 'Peut ajouter des données à la table'),
        )

(5) Ajoutez le décorateur @check_permission dans kingadmin/views.py.

(6) Gestion des permissions dans l'interface d'administration

  • Pour accéder à la liste des clients (ainsi qu'aux pages d'ajout et de modification), vous devez attribuer les permissions appropriées dans l'interface d'administration.

14.2. Implémentation d'un hook de permissions personnalisé

Permettre aux utilisateurs d'accéder uniquement aux données qu'ils ont créées, par exemple, permettre aux commerciaux d'accéder uniquement à leurs propres clients :

(1) kingadmin/permission_list.py

'crm_table_list': ['table_obj_list', 'GET', [], {}, permission_hook.view_my_own_customers],

(2) kingadmin/permission_hook.py

# kingadmin/permission_hook.py

def view_my_own_customers(request):
    if str(request.user.id) == request.GET.get('consultant'):
        return True
    else:
        return False

Avec ces modifications, les commmerciaux ne pourront voir que leurs propres clients. Ce cadre de permissions générique est maintenant prêt à être utilisé, offrant un contrôle flexible qui peut être facilement transplanté dans d'autres projets Django.

Étiquettes: Django Authentification permissions modèle utilisateur gestion des rôles

Publié le 29 septembre à 16h31