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.