diff --git a/re2o/settings.py b/re2o/settings.py index 408ec0a0..fbd6c1d3 100644 --- a/re2o/settings.py +++ b/re2o/settings.py @@ -28,6 +28,8 @@ PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.PBKDF2PasswordHasher', ) +AUTH_USER_MODEL = 'users.User' + # Application definition diff --git a/users/admin.py b/users/admin.py index ceb37a66..47545a86 100644 --- a/users/admin.py +++ b/users/admin.py @@ -1,28 +1,79 @@ from django.contrib import admin +from django.contrib.auth.models import Group +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from .models import User, School, Right, ListRight, Ban, Whitelist +from .forms import UserChangeForm, UserCreationForm + class UserAdmin(admin.ModelAdmin): - list_display = ('name','surname','pseudo','room','email', 'school', 'state') + list_display = ( + 'name', + 'surname', + 'pseudo', + 'room', + 'email', + 'school', + 'state' + ) + class SchoolAdmin(admin.ModelAdmin): list_display = ('name',) + class ListRightAdmin(admin.ModelAdmin): list_display = ('listright',) + class RightAdmin(admin.ModelAdmin): list_display = ('user', 'right') + class BanAdmin(admin.ModelAdmin): list_display = ('user', 'raison', 'date_start', 'date_end') + class WhitelistAdmin(admin.ModelAdmin): list_display = ('user', 'raison', 'date_start', 'date_end') + +class UserAdmin(BaseUserAdmin): + # The forms to add and change user instances + form = UserChangeForm + add_form = UserCreationForm + + # The fields to be used in displaying the User model. + # These override the definitions on the base UserAdmin + # that reference specific fields on auth.User. + list_display = ('pseudo', 'name', 'surname', 'email', 'school', 'is_admin') + list_filter = () + fieldsets = ( + (None, {'fields': ('pseudo', 'password')}), + ('Personal info', {'fields': ('name', 'surname', 'email', 'school')}), + ('Permissions', {'fields': ('is_admin', )}), + ) + # add_fieldsets is not a standard ModelAdmin attribute. UserAdmin + # overrides get_fieldsets to use this attribute when creating a user. + add_fieldsets = ( + (None, { + 'classes': ('wide',), + 'fields': ('pseudo', 'name', 'surname', 'email', 'school', 'is_admin', 'password1', 'password2')} + ), + ) + search_fields = ('pseudo',) + ordering = ('pseudo',) + filter_horizontal = () + admin.site.register(User, UserAdmin) admin.site.register(School, SchoolAdmin) admin.site.register(Right, RightAdmin) admin.site.register(ListRight, ListRightAdmin) admin.site.register(Ban, BanAdmin) admin.site.register(Whitelist, WhitelistAdmin) +# Now register the new UserAdmin... +admin.site.unregister(User) +admin.site.register(User, UserAdmin) +# ... and, since we're not using Django's built-in permissions, +# unregister the Group model from admin. +admin.site.unregister(Group) diff --git a/users/forms.py b/users/forms.py index 13a4d363..aa7cbf94 100644 --- a/users/forms.py +++ b/users/forms.py @@ -2,8 +2,71 @@ from django import forms +from django.contrib.auth.forms import ReadOnlyPasswordHashField + +from .models import User, get_admin_right class PassForm(forms.Form): passwd1 = forms.CharField(label=u'Nouveau mot de passe', max_length=255, widget=forms.PasswordInput) passwd2 = forms.CharField(label=u'Saisir à nouveau le mot de passe', max_length=255, widget=forms.PasswordInput) + + +class UserCreationForm(forms.ModelForm): + """A form for creating new users. Includes all the required + fields, plus a repeated password.""" + password1 = forms.CharField(label='Password', widget=forms.PasswordInput) + password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput) + is_admin = forms.BooleanField(label='is admin') + + class Meta: + model = User + fields = ('pseudo', 'name', 'surname', 'email') + + def clean_password2(self): + # Check that the two password entries match + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise forms.ValidationError("Passwords don't match") + return password2 + + def save(self, commit=True): + # Save the provided password in hashed format + user = super(UserCreationForm, self).save(commit=False) + user.set_password(self.cleaned_data["password1"]) + user.save() + user.is_admin = self.cleaned_data.get("is_admin") + return user + + +class UserChangeForm(forms.ModelForm): + """A form for updating users. Includes all the fields on + the user, but replaces the password field with admin's + password hash display field. + """ + password = ReadOnlyPasswordHashField() + is_admin = forms.BooleanField(label='is admin', required=False) + + class Meta: + model = User + fields = ('pseudo', 'password', 'name', 'surname', 'email') + + def __init__(self, *args, **kwargs): + super(UserChangeForm, self).__init__(*args, **kwargs) + print("User is admin : %s" % kwargs['instance'].is_admin) + self.initial['is_admin'] = kwargs['instance'].is_admin + + def clean_password(self): + # Regardless of what the user provides, return the initial value. + # This is done here, rather than on the field, because the + # field does not have access to the initial value + return self.initial["password"] + + def save(self, commit=True): + # Save the provided password in hashed format + user = super(UserChangeForm, self).save(commit=False) + user.is_admin = self.cleaned_data.get("is_admin") + if commit: + user.save() + return user diff --git a/users/models.py b/users/models.py index c12def82..9cddc8df 100644 --- a/users/models.py +++ b/users/models.py @@ -4,9 +4,11 @@ from django import forms import re from django.utils import timezone +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager from topologie.models import Room + def remove_user_room(room): """ Déménage de force l'ancien locataire de la chambre """ try: @@ -16,16 +18,67 @@ def remove_user_room(room): user.room = None user.save() -def linux_user_validator(login): + +def linux_user_check(login): """ Validation du pseudo pour respecter les contraintes unix""" UNIX_LOGIN_PATTERN = re.compile("^[a-z_][a-z0-9_-]*[$]?$") - if not UNIX_LOGIN_PATTERN.match(login): + return UNIX_LOGIN_PATTERN.match(login) + + +def linux_user_validator(login): + if not linux_user_check(login): raise forms.ValidationError( ", ce pseudo ('%(label)s') contient des carractères interdits", params={'label': login}, ) -class User(models.Model): + +def get_admin_right(): + try: + admin_right = ListRight.objects.get(listright="admin") + except ListRight.DoesNotExist: + admin_right = ListRight(listright="admin") + admin_right.save() + return admin_right + + +class UserManager(BaseUserManager): + def _create_user(self, pseudo, name, surname, email, password=None, su=False): + if not pseudo: + raise ValueError('Users must have an username') + + if not linux_user_check(pseudo): + raise ValueError('Username shall only contain [a-z0-9_-]') + + user = self.model( + pseudo=pseudo, + name=name, + surname=surname, + email=self.normalize_email(email), + ) + + user.set_password(password) + user.save(using=self._db) + if su: + user.make_admin() + return user + + def create_user(self, pseudo, name, surname, email, password=None): + """ + Creates and saves a User with the given pseudo, name, surname, email, + and password. + """ + return self._create_user(pseudo, name, surname, email, password, False) + + def create_superuser(self, pseudo, name, surname, email, password): + """ + Creates and saves a superuser with the given pseudo, name, surname, + email, and password. + """ + return self._create_user(pseudo, name, surname, email, password, True) + + +class User(AbstractBaseUser): STATE_ACTIVE = 0 STATE_DEACTIVATED = 1 STATE_ARCHIVED = 2 @@ -42,45 +95,103 @@ class User(models.Model): school = models.ForeignKey('School', on_delete=models.PROTECT, null=False, blank=False) comment = models.CharField(help_text="Commentaire, promo", max_length=255, blank=True) room = models.OneToOneField('topologie.Room', on_delete=models.PROTECT, blank=True, null=True) - pwd_ssha = models.CharField(max_length=255) pwd_ntlm = models.CharField(max_length=255) state = models.IntegerField(choices=STATES, default=STATE_ACTIVE) registered = models.DateTimeField(auto_now_add=True) + USERNAME_FIELD = 'pseudo' + REQUIRED_FIELDS = ['name', 'surname', 'email'] + + objects = UserManager() + + @property + def is_active(self): + return self.state == self.STATE_ACTIVE + + @property + def is_staff(self): + return self.is_admin + + @property + def is_admin(self): + try: + Right.objects.get(user=self, right__listright='admin') + except Right.DoesNotExist: + return False + return True + + @is_admin.setter + def is_admin(self, value): + if value and not self.is_admin: + self.make_admin() + elif not value and self.is_admin: + self.un_admin() + + def get_full_name(self): + return '%s %s' % (self.name, self.surname) + + def get_short_name(self): + return self.name + + def has_perm(self, perm, obj=None): + # Simplest version + return True + + def has_module_perms(self, app_label): + # Simplest version again + return True + + def make_admin(self): + """ Make User admin """ + user_admin_right = Right(user=self, right=get_admin_right()) + user_admin_right.save() + + def un_admin(self): + try: + user_right = Right.objects.get(user=self,right=get_admin_right()) + except Right.DoesNotExist: + return + user_right.delete() + def __str__(self): return self.pseudo + class Right(models.Model): - user = models.ForeignKey('User', on_delete=models.PROTECT) + user = models.ForeignKey('User', on_delete=models.PROTECT) right = models.ForeignKey('ListRight', on_delete=models.PROTECT) - + class Meta: unique_together = ("user", "right") def __str__(self): return str(self.user) + " - " + str(self.right) + class School(models.Model): name = models.CharField(max_length=255) def __str__(self): return self.name + class ListRight(models.Model): - listright = models.CharField(max_length=255) + listright = models.CharField(max_length=255, unique=True) def __str__(self): return self.listright + class Ban(models.Model): user = models.ForeignKey('User', on_delete=models.PROTECT) raison = models.CharField(max_length=255) date_start = models.DateTimeField(auto_now_add=True) - date_end = models.DateTimeField(help_text='%d/%m/%y %H:%M:%S') + date_end = models.DateTimeField(help_text='%d/%m/%y %H:%M:%S') def __str__(self): return str(self.user) + ' ' + str(self.raison) + class Whitelist(models.Model): user = models.ForeignKey('User', on_delete=models.PROTECT) raison = models.CharField(max_length=255) @@ -90,6 +201,7 @@ class Whitelist(models.Model): def __str__(self): return str(self.user) + ' ' + str(self.raison) + class InfoForm(ModelForm): force = forms.BooleanField(label="Forcer le déménagement ?", initial=False, required=False) @@ -110,22 +222,34 @@ class InfoForm(ModelForm): class Meta: model = User - fields = ['name','surname','pseudo','email', 'school', 'comment', 'room'] + fields = [ + 'name', + 'surname', + 'pseudo', + 'email', + 'school', + 'comment', + 'room', + ] + class UserForm(InfoForm): class Meta(InfoForm.Meta): fields = '__all__' + class PasswordForm(ModelForm): class Meta: model = User - fields = ['pwd_ssha','pwd_ntlm'] + fields = ['password', 'pwd_ntlm'] + class StateForm(ModelForm): class Meta: model = User fields = ['state'] + class SchoolForm(ModelForm): class Meta: model = School @@ -135,6 +259,7 @@ class SchoolForm(ModelForm): super(SchoolForm, self).__init__(*args, **kwargs) self.fields['name'].label = 'Établissement à ajouter' + class DelSchoolForm(ModelForm): schools = forms.ModelMultipleChoiceField(queryset=School.objects.all(), label="Etablissements actuels", widget=forms.CheckboxSelectMultiple) @@ -142,6 +267,7 @@ class DelSchoolForm(ModelForm): exclude = ['name'] model = School + class RightForm(ModelForm): def __init__(self, *args, **kwargs): super(RightForm, self).__init__(*args, **kwargs) @@ -152,6 +278,7 @@ class RightForm(ModelForm): model = Right fields = ['right'] + class DelRightForm(ModelForm): rights = forms.ModelMultipleChoiceField(queryset=Right.objects.all(), label="Droits actuels", widget=forms.CheckboxSelectMultiple) @@ -159,6 +286,7 @@ class DelRightForm(ModelForm): model = Right exclude = ['user', 'right'] + class BanForm(ModelForm): def __init__(self, *args, **kwargs): super(BanForm, self).__init__(*args, **kwargs) @@ -174,6 +302,7 @@ class BanForm(ModelForm): raise forms.ValidationError("Triple buse, la date de fin ne peut pas être avant maintenant... Re2o ne voyage pas dans le temps") return date_end + class WhitelistForm(ModelForm): def __init__(self, *args, **kwargs): super(WhitelistForm, self).__init__(*args, **kwargs) @@ -189,5 +318,6 @@ class WhitelistForm(ModelForm): raise forms.ValidationError("Triple buse, la date de fin ne peut pas être avant maintenant... Re2o ne voyage pas dans le temps") return date_end + class ProfilForm(Form): - user =forms.CharField(label ='Ok', max_length=100) + user = forms.CharField(label='Ok', max_length=100) diff --git a/users/views.py b/users/views.py index c3e60a9b..0ec309ce 100644 --- a/users/views.py +++ b/users/views.py @@ -1,45 +1,52 @@ # App de gestion des users pour re2o # Goulven Kermarec, Gabriel Détraz # Gplv2 -from django.shortcuts import render, redirect -from django.shortcuts import render_to_response, get_object_or_404 +from django.shortcuts import render_to_response, render, redirect from django.core.context_processors import csrf -from django.template import Context, RequestContext, loader +from django.template import RequestContext from django.contrib import messages from django.db.models import Max, ProtectedError from django.db import IntegrityError from django.utils import timezone -from users.models import User, Right, Ban, DelRightForm, UserForm, InfoForm, PasswordForm, StateForm, RightForm, BanForm, ProfilForm, Whitelist, WhitelistForm, DelSchoolForm, SchoolForm +from users.models import User, Right, Ban, Whitelist +from users.models import DelRightForm, BanForm, WhitelistForm, DelSchoolForm +from users.models import InfoForm, StateForm, RightForm, SchoolForm from cotisations.models import Facture from machines.models import Machine, Interface -from users.forms import PassForm -from search.models import SearchForm +from users.forms import PassForm from cotisations.views import is_adherent, end_adhesion from machines.views import unassign_ips, assign_ips -from re2o.login import makeSecret, hashNT +from re2o.login import hashNT + def archive(user): """ Archive un utilisateur """ unassign_ips(user) return + def unarchive(user): """ Triger actions au desarchivage d'un user """ assign_ips(user) return + def end_ban(user): """ Renvoie la date de fin de ban d'un user, False sinon """ - date_max = Ban.objects.all().filter(user=user).aggregate(Max('date_end'))['date_end__max'] + date_max = Ban.objects.all().filter( + user=user).aggregate(Max('date_end'))['date_end__max'] return date_max + def end_whitelist(user): """ Renvoie la date de fin de ban d'un user, False sinon """ - date_max = Whitelist.objects.all().filter(user=user).aggregate(Max('date_end'))['date_end__max'] + date_max = Whitelist.objects.all().filter( + user=user).aggregate(Max('date_end'))['date_end__max'] return date_max + def is_ban(user): """ Renvoie si un user est banni ou non """ end = end_ban(user) @@ -48,7 +55,8 @@ def is_ban(user): elif end < timezone.now(): return False else: - return True + return True + def is_whitelisted(user): """ Renvoie si un user est whitelisté ou non """ @@ -60,9 +68,12 @@ def is_whitelisted(user): else: return True + def has_access(user): - """ Renvoie si un utilisateur a accès à internet""" - return user.state == User.STATE_ACTIVE and not is_ban(user) and ( is_adherent(user) or is_whitelisted(user)) + """ Renvoie si un utilisateur a accès à internet """ + return user.state == User.STATE_ACTIVE \ + and not is_ban(user) and (is_adherent(user) or is_whitelisted(user)) + def is_active(interface): """ Renvoie si une interface doit avoir accès ou non """ @@ -70,10 +81,16 @@ def is_active(interface): user = machine.user return machine.active and has_access(user) + def form(ctx, template, request): c = ctx c.update(csrf(request)) - return render_to_response(template, c, context_instance=RequestContext(request)) + return render_to_response( + template, + c, + context_instance=RequestContext(request) + ) + def new_user(request): user = InfoForm(request.POST or None) @@ -83,11 +100,12 @@ def new_user(request): return redirect("/users/") return form({'userform': user}, 'users/user.html', request) + def edit_info(request, userid): try: user = User.objects.get(pk=userid) except User.DoesNotExist: - messages.error(request, u"Utilisateur inexistant" ) + messages.error(request, "Utilisateur inexistant") return redirect("/users/") user = InfoForm(request.POST or None, instance=user) if user.is_valid(): @@ -96,11 +114,12 @@ def edit_info(request, userid): return redirect("/users/") return form({'userform': user}, 'users/user.html', request) + def state(request, userid): try: user = User.objects.get(pk=userid) except User.DoesNotExist: - messages.error(request, u"Utilisateur inexistant" ) + messages.error(request, "Utilisateur inexistant") return redirect("/users/") state = StateForm(request.POST or None, instance=user) if state.is_valid(): @@ -114,29 +133,31 @@ def state(request, userid): return redirect("/users/") return form({'userform': state}, 'users/user.html', request) + def password(request, userid): try: user = User.objects.get(pk=userid) except User.DoesNotExist: - messages.error(request, u"Utilisateur inexistant" ) + messages.error(request, "Utilisateur inexistant") return redirect("/users/") - user_form = PassForm(request.POST or None) - if user_form.is_valid(): - if user_form.cleaned_data['passwd1'] != user_form.cleaned_data['passwd2']: - messages.error(request, u"Les 2 mots de passe différent" ) - return form({'userform': user_form}, 'users/user.html', request) - user.pwd_ssha = makeSecret(user_form.cleaned_data['passwd1']) - user.pwd_ntlm = hashNT(user_form.cleaned_data['passwd1']) + u_form = PassForm(request.POST or None) + if u_form.is_valid(): + if u_form.cleaned_data['passwd1'] != u_form.cleaned_data['passwd2']: + messages.error(request, "Les 2 mots de passe différent") + return form({'userform': u_form}, 'users/user.html', request) + user.set_password(u_form.cleaned_data['passwd1']) + user.pwd_ntlm = hashNT(u_form.cleaned_data['passwd1']) user.save() messages.success(request, "Le mot de passe a changé") return redirect("/users/") - return form({'userform': user_form}, 'users/user.html', request) + return form({'userform': u_form}, 'users/user.html', request) + def add_right(request, userid): try: user = User.objects.get(pk=userid) except User.DoesNotExist: - messages.error(request, u"Utilisateur inexistant" ) + messages.error(request, "Utilisateur inexistant") return redirect("/users/") right = RightForm(request.POST or None) if right.is_valid(): @@ -150,6 +171,7 @@ def add_right(request, userid): return redirect("/users/") return form({'userform': right}, 'users/user.html', request) + def del_right(request): right = DelRightForm(request.POST or None) if right.is_valid(): @@ -159,11 +181,12 @@ def del_right(request): return redirect("/users/") return form({'userform': right}, 'users/user.html', request) + def add_ban(request, userid): try: user = User.objects.get(pk=userid) except User.DoesNotExist: - messages.error(request, u"Utilisateur inexistant" ) + messages.error(request, "Utilisateur inexistant") return redirect("/users/") ban_instance = Ban(user=user) ban = BanForm(request.POST or None, instance=ban_instance) @@ -172,14 +195,18 @@ def add_ban(request, userid): messages.success(request, "Bannissement ajouté") return redirect("/users/") if is_ban(user): - messages.error(request, u"Attention, cet utilisateur a deja un bannissement actif" ) + messages.error( + request, + "Attention, cet utilisateur a deja un bannissement actif" + ) return form({'userform': ban}, 'users/user.html', request) + def edit_ban(request, banid): try: ban_instance = Ban.objects.get(pk=banid) except Ban.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) + messages.error(request, "Entrée inexistante") return redirect("/users/") ban = BanForm(request.POST or None, instance=ban_instance) if ban.is_valid(): @@ -188,11 +215,12 @@ def edit_ban(request, banid): return redirect("/users/") return form({'userform': ban}, 'users/user.html', request) + def add_whitelist(request, userid): try: user = User.objects.get(pk=userid) except User.DoesNotExist: - messages.error(request, u"Utilisateur inexistant" ) + messages.error(request, "Utilisateur inexistant") return redirect("/users/") whitelist_instance = Whitelist(user=user) whitelist = WhitelistForm(request.POST or None, instance=whitelist_instance) @@ -201,14 +229,18 @@ def add_whitelist(request, userid): messages.success(request, "Accès à titre gracieux accordé") return redirect("/users/") if is_whitelisted(user): - messages.error(request, u"Attention, cet utilisateur a deja un accès gracieux actif" ) + messages.error( + request, + "Attention, cet utilisateur a deja un accès gracieux actif" + ) return form({'userform': whitelist}, 'users/user.html', request) + def edit_whitelist(request, whitelistid): try: whitelist_instance = Whitelist.objects.get(pk=whitelistid) except Whitelist.DoesNotExist: - messages.error(request, u"Entrée inexistante" ) + messages.error(request, "Entrée inexistante") return redirect("/users/") whitelist = WhitelistForm(request.POST or None, instance=whitelist_instance) if whitelist.is_valid(): @@ -217,13 +249,15 @@ def edit_whitelist(request, whitelistid): return redirect("/users/") return form({'userform': whitelist}, 'users/user.html', request) + def add_school(request): school = SchoolForm(request.POST or None) if school.is_valid(): school.save() messages.success(request, "L'établissement a été ajouté") return redirect("/users/") - return form({'userform': school}, 'users/user.html', request) + return form({'userform': school}, 'users/user.html', request) + def del_school(request): school = DelSchoolForm(request.POST or None) @@ -234,46 +268,73 @@ def del_school(request): school_del.delete() messages.success(request, "L'établissement a été supprimé") except ProtectedError: - messages.error(request, "L'établissement %s est affecté à au moins un user, vous ne pouvez pas le supprimer" % school_del) + messages.error( + request, + "L'établissement %s est affecté à au moins un user, \ + vous ne pouvez pas le supprimer" % school_del + ) return redirect("/users/") return form({'userform': school}, 'users/user.html', request) + def index(request): users_list = User.objects.order_by('pk') connexion = [] for user in users_list: end = end_adhesion(user) access = has_access(user) - if(end!=None): + if(end is not None): connexion.append([user, access, end]) else: connexion.append([user, access, "Non adhérent"]) return render(request, 'users/index.html', {'users_list': connexion}) + def index_ban(request): ban_list = Ban.objects.order_by('date_start') - return render(request, 'users/index_ban.html', {'ban_list':ban_list}) + return render(request, 'users/index_ban.html', {'ban_list': ban_list}) + def index_white(request): white_list = Whitelist.objects.order_by('date_start') - return render(request, 'users/index_whitelist.html', {'white_list':white_list}) + return render( + request, + 'users/index_whitelist.html', + {'white_list': white_list} + ) + def profil(request, userid): try: users = User.objects.get(pk=userid) except User.DoesNotExist: - messages.error(request, u"Utilisateur inexistant" ) + messages.error(request, "Utilisateur inexistant") return redirect("/users/") - machines = Interface.objects.filter(machine=Machine.objects.filter(user__pseudo = users)) - factures = Facture.objects.filter(user__pseudo = users) - bans = Ban.objects.filter(user__pseudo = users) - whitelists = Whitelist.objects.filter(user__pseudo = users) + machines = Interface.objects.filter( + machine=Machine.objects.filter(user__pseudo=users) + ) + factures = Facture.objects.filter(user__pseudo=users) + bans = Ban.objects.filter(user__pseudo=users) + whitelists = Whitelist.objects.filter(user__pseudo=users) end_bans = None end_whitelists = None if(is_ban(users)): - end_bans=end_ban(users) + end_bans = end_ban(users) if(is_whitelisted(users)): - end_whitelists=end_whitelist(users) + end_whitelists = end_whitelist(users) list_droits = Right.objects.filter(user=users) - return render(request, 'users/profil.html', {'user': users, 'machine_list' :machines, 'facture_list':factures, 'ban_list':bans, 'white_list':whitelists,'end_ban':end_bans,'end_whitelist':end_whitelists, 'end_adhesion':end_adhesion(users), 'actif':has_access(users), 'list_droits': list_droits}) - + return render( + request, + 'users/profil.html', + { + 'user': users, + 'machine_list': machines, + 'facture_list': factures, + 'ban_list': bans, + 'white_list': whitelists, + 'end_ban': end_bans, + 'end_whitelist': end_whitelists, + 'end_adhesion': end_adhesion(users), + 'actif': has_access(users), + 'list_droits': list_droits} + )