diff --git a/users/forms.py b/users/forms.py index a8b1a219..fd81b426 100644 --- a/users/forms.py +++ b/users/forms.py @@ -20,8 +20,16 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" +Definition des forms pour l'application users. -# -*- coding: utf-8 -*- +Modification, creation de : + - un user (informations personnelles) + - un bannissement + - le mot de passe d'un user + - une whiteliste + - un user de service +""" from __future__ import unicode_literals @@ -29,17 +37,34 @@ from django import forms from django.forms import ModelForm, Form from django.contrib.auth.forms import ReadOnlyPasswordHashField from django.core.validators import MinLengthValidator -from preferences.models import OptionalUser from django.utils import timezone -from .models import User, ServiceUser, Right, School, ListRight, Whitelist, Ban, Request, remove_user_room -from .models import get_admin_right +from preferences.models import OptionalUser +from .models import User, ServiceUser, Right, School, ListRight, Whitelist +from .models import Ban, remove_user_room + +NOW = timezone.now() + class PassForm(forms.Form): - passwd1 = forms.CharField(label=u'Nouveau mot de passe', max_length=255, validators=[MinLengthValidator(8)], widget=forms.PasswordInput) - passwd2 = forms.CharField(label=u'Saisir à nouveau le mot de passe', max_length=255, validators=[MinLengthValidator(8)], widget=forms.PasswordInput) + """Formulaire de changement de mot de passe. Verifie que les 2 + nouveaux mots de passe renseignés sont identiques et respectent + une norme""" + passwd1 = forms.CharField( + label=u'Nouveau mot de passe', + max_length=255, + validators=[MinLengthValidator(8)], + widget=forms.PasswordInput + ) + passwd2 = forms.CharField( + label=u'Saisir à nouveau le mot de passe', + max_length=255, + validators=[MinLengthValidator(8)], + widget=forms.PasswordInput + ) def clean_passwd2(self): + """Verifie que passwd1 et 2 sont identiques""" # Check that the two password entries match password1 = self.cleaned_data.get("passwd1") password2 = self.cleaned_data.get("passwd2") @@ -47,11 +72,26 @@ class PassForm(forms.Form): raise forms.ValidationError("Passwords don't match") return password2 + 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, validators=[MinLengthValidator(8)], max_length=255) - password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput, validators=[MinLengthValidator(8)], max_length=255) + fields, plus a repeated password. + + Formulaire pour la création d'un user. N'est utilisé que pour + l'admin, lors de la creation d'un user par admin. Inclu tous les + champs obligatoires""" + password1 = forms.CharField( + label='Password', + widget=forms.PasswordInput, + validators=[MinLengthValidator(8)], + max_length=255 + ) + password2 = forms.CharField( + label='Password confirmation', + widget=forms.PasswordInput, + validators=[MinLengthValidator(8)], + max_length=255 + ) is_admin = forms.BooleanField(label='is admin') def __init__(self, *args, **kwargs): @@ -63,6 +103,7 @@ class UserCreationForm(forms.ModelForm): fields = ('pseudo', 'name', 'surname', 'email') def clean_password2(self): + """Verifie que password1 et 2 sont identiques""" # Check that the two password entries match password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") @@ -78,21 +119,40 @@ class UserCreationForm(forms.ModelForm): user.is_admin = self.cleaned_data.get("is_admin") return user + class ServiceUserCreationForm(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, min_length=8, max_length=255) - password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput, min_length=8, max_length=255) + fields, plus a repeated password. + + Formulaire pour la creation de nouveaux serviceusers. + Requiert seulement un mot de passe; et un pseudo""" + password1 = forms.CharField( + label='Password', + widget=forms.PasswordInput, + min_length=8, + max_length=255 + ) + password2 = forms.CharField( + label='Password confirmation', + widget=forms.PasswordInput, + min_length=8, + max_length=255 + ) def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(ServiceUserCreationForm, self).__init__(*args, prefix=prefix, **kwargs) + super(ServiceUserCreationForm, self).__init__( + *args, + prefix=prefix, + **kwargs + ) class Meta: model = ServiceUser fields = ('pseudo',) def clean_password2(self): + """Verifie que password1 et 2 sont indentiques""" # Check that the two password entries match password1 = self.cleaned_data.get("password1") password2 = self.cleaned_data.get("password2") @@ -107,10 +167,13 @@ class ServiceUserCreationForm(forms.ModelForm): user.save() 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. + + Formulaire pour la modification d'un user coté admin """ password = ReadOnlyPasswordHashField() is_admin = forms.BooleanField(label='is admin', required=False) @@ -126,6 +189,7 @@ class UserChangeForm(forms.ModelForm): self.initial['is_admin'] = kwargs['instance'].is_admin def clean_password(self): + """Dummy fun""" # 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 @@ -139,42 +203,59 @@ class UserChangeForm(forms.ModelForm): user.save() return user + class ServiceUserChangeForm(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. + + Formulaire pour l'edition des service users coté admin """ password = ReadOnlyPasswordHashField() def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) - super(ServiceUserChangeForm, self).__init__(*args, prefix=prefix, **kwargs) + super(ServiceUserChangeForm, self).__init__( + *args, + prefix=prefix, + **kwargs + ) class Meta: model = ServiceUser fields = ('pseudo',) 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 + """Dummy fun""" return self.initial["password"] + class ResetPasswordForm(forms.Form): + """Formulaire de demande de reinitialisation de mot de passe, + mdp oublié""" pseudo = forms.CharField(label=u'Pseudo', max_length=255) email = forms.EmailField(max_length=255) + class MassArchiveForm(forms.Form): + """Formulaire d'archivage des users inactif. Prend en argument + du formulaire la date de depart avant laquelle archiver les + users""" date = forms.DateTimeField(help_text='%d/%m/%y') def clean(self): - cleaned_data=super(MassArchiveForm, self).clean() + cleaned_data = super(MassArchiveForm, self).clean() date = cleaned_data.get("date") if date: - if date>timezone.now(): - raise forms.ValidationError("Impossible d'archiver des utilisateurs dont la fin d'accès se situe dans le futur !") + if date > NOW: + raise forms.ValidationError("Impossible d'archiver des\ + utilisateurs dont la fin d'accès se situe dans le futur !") + class BaseInfoForm(ModelForm): + """Formulaire de base d'edition d'un user. Formulaire de base, utilisé + pour l'edition de self par self ou un cableur. On formate les champs + avec des label plus jolis""" def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(BaseInfoForm, self).__init__(*args, prefix=prefix, **kwargs) @@ -200,13 +281,21 @@ class BaseInfoForm(ModelForm): ] def clean_telephone(self): + """Verifie que le tel est présent si 'option est validée + dans preferences""" telephone = self.cleaned_data['telephone'] - preferences, created = OptionalUser.objects.get_or_create() + preferences, _created = OptionalUser.objects.get_or_create() if not telephone and preferences.is_tel_mandatory: - raise forms.ValidationError("Un numéro de téléphone valide est requis") + raise forms.ValidationError( + "Un numéro de téléphone valide est requis" + ) return telephone + class EditInfoForm(BaseInfoForm): + """Edition complète d'un user. Utilisé par admin, + permet d'editer normalement la chambre, ou le shell + Herite de la base""" class Meta(BaseInfoForm.Meta): fields = [ 'name', @@ -220,22 +309,33 @@ class EditInfoForm(BaseInfoForm): 'telephone', ] + class InfoForm(EditInfoForm): - """ Utile pour forcer un déménagement quand il y a déjà un user en place""" - force = forms.BooleanField(label="Forcer le déménagement ?", initial=False, required=False) + """ Utile pour forcer un déménagement quand il y a déjà un user en place + Formuaire utilisé pour la creation initiale""" + force = forms.BooleanField( + label="Forcer le déménagement ?", + initial=False, + required=False + ) def clean_force(self): + """On supprime l'ancien user de la chambre si et seulement si la + case est cochée""" if self.cleaned_data.get('force', False): remove_user_room(self.cleaned_data.get('room')) return + class UserForm(InfoForm): """ Model form general""" class Meta(InfoForm.Meta): fields = '__all__' + class PasswordForm(ModelForm): - """ Formulaire de changement brut de mot de passe. Ne pas utiliser sans traitement""" + """ Formulaire de changement brut de mot de passe. + Ne pas utiliser sans traitement""" class Meta: model = User fields = ['password', 'pwd_ntlm'] @@ -244,21 +344,32 @@ class PasswordForm(ModelForm): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(PasswordForm, self).__init__(*args, prefix=prefix, **kwargs) + class ServiceUserForm(ModelForm): """ Modification d'un service user""" - password = forms.CharField(label=u'Nouveau mot de passe', max_length=255, validators=[MinLengthValidator(8)], widget=forms.PasswordInput, required=False) + password = forms.CharField( + label=u'Nouveau mot de passe', + max_length=255, + validators=[MinLengthValidator(8)], + widget=forms.PasswordInput, + required=False + ) class Meta: model = ServiceUser - fields = ('pseudo','access_group') + fields = ('pseudo', 'access_group') def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(ServiceUserForm, self).__init__(*args, prefix=prefix, **kwargs) + class EditServiceUserForm(ServiceUserForm): + """Formulaire d'edition de base d'un service user. Ne permet + d'editer que son group d'acl et son commentaire""" class Meta(ServiceUserForm.Meta): - fields = ['access_group','comment'] + fields = ['access_group', 'comment'] + class StateForm(ModelForm): """ Changement de l'état d'un user""" @@ -272,6 +383,7 @@ class StateForm(ModelForm): class SchoolForm(ModelForm): + """Edition, creation d'un école""" class Meta: model = School fields = ['name'] @@ -281,7 +393,10 @@ class SchoolForm(ModelForm): super(SchoolForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['name'].label = 'Établissement' + class ListRightForm(ModelForm): + """Edition, d'un groupe , équivalent à un droit + Ne peremet pas d'editer le gid, car il sert de primary key""" class Meta: model = ListRight fields = ['listright', 'details'] @@ -291,21 +406,38 @@ class ListRightForm(ModelForm): super(ListRightForm, self).__init__(*args, prefix=prefix, **kwargs) self.fields['listright'].label = 'Nom du droit/groupe' + class NewListRightForm(ListRightForm): + """Ajout d'un groupe/list de droit """ class Meta(ListRightForm.Meta): fields = '__all__' def __init__(self, *args, **kwargs): super(NewListRightForm, self).__init__(*args, **kwargs) - self.fields['gid'].label = 'Gid, attention, cet attribut ne doit pas être modifié après création' + self.fields['gid'].label = 'Gid, attention, cet attribut ne doit\ + pas être modifié après création' + class DelListRightForm(Form): - listrights = forms.ModelMultipleChoiceField(queryset=ListRight.objects.all(), label="Droits actuels", widget=forms.CheckboxSelectMultiple) + """Suppression d'un ou plusieurs groupes""" + listrights = forms.ModelMultipleChoiceField( + queryset=ListRight.objects.all(), + label="Droits actuels", + widget=forms.CheckboxSelectMultiple + ) + class DelSchoolForm(Form): - schools = forms.ModelMultipleChoiceField(queryset=School.objects.all(), label="Etablissements actuels", widget=forms.CheckboxSelectMultiple) + """Suppression d'une ou plusieurs écoles""" + schools = forms.ModelMultipleChoiceField( + queryset=School.objects.all(), + label="Etablissements actuels", + widget=forms.CheckboxSelectMultiple + ) + class RightForm(ModelForm): + """Assignation d'un droit à un user""" def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(RightForm, self).__init__(*args, prefix=prefix, **kwargs) @@ -318,13 +450,19 @@ class RightForm(ModelForm): class DelRightForm(Form): - rights = forms.ModelMultipleChoiceField(queryset=Right.objects.all(), widget=forms.CheckboxSelectMultiple) + """Suppression d'un droit d'un user""" + rights = forms.ModelMultipleChoiceField( + queryset=Right.objects.all(), + widget=forms.CheckboxSelectMultiple + ) def __init__(self, right, *args, **kwargs): super(DelRightForm, self).__init__(*args, **kwargs) self.fields['rights'].queryset = Right.objects.filter(right=right) + class BanForm(ModelForm): + """Creation, edition d'un objet bannissement""" def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(BanForm, self).__init__(*args, prefix=prefix, **kwargs) @@ -335,13 +473,16 @@ class BanForm(ModelForm): exclude = ['user'] def clean_date_end(self): + """Verification que date_end est après now""" date_end = self.cleaned_data['date_end'] - if date_end < timezone.now(): - raise forms.ValidationError("Triple buse, la date de fin ne peut pas être avant maintenant... Re2o ne voyage pas dans le temps") + if date_end < NOW: + 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): + """Creation, edition d'un objet whitelist""" def __init__(self, *args, **kwargs): prefix = kwargs.pop('prefix', self.Meta.model.__name__) super(WhitelistForm, self).__init__(*args, prefix=prefix, **kwargs) @@ -352,7 +493,9 @@ class WhitelistForm(ModelForm): exclude = ['user'] def clean_date_end(self): + """Verification que la date_end est posterieur à now""" date_end = self.cleaned_data['date_end'] - if date_end < timezone.now(): - raise forms.ValidationError("Triple buse, la date de fin ne peut pas être avant maintenant... Re2o ne voyage pas dans le temps") + if date_end < NOW: + 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 diff --git a/users/models.py b/users/models.py index 78b76156..a0ef29f8 100644 --- a/users/models.py +++ b/users/models.py @@ -1,7 +1,7 @@ # -*- mode: python; coding: utf-8 -*- -# Re2o est un logiciel d'administration développé initiallement au rezometz. Il -# se veut agnostique au réseau considéré, de manière à être installable en -# quelques clics. +# Re2o est un logiciel d'administration développé initiallement au rezometz. +# Il se veut agnostique au réseau considéré, de manière à être installable +# en quelques clics. # # Copyright © 2017 Gabriel Détraz # Copyright © 2017 Goulven Kermarec @@ -20,44 +20,66 @@ # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +""" +Models de l'application users. + +On défini ici des models django classiques: +- users, qui hérite de l'abstract base user de django. Permet de définit +un utilisateur du site (login, passwd, chambre, adresse, etc) +- les whiteslist +- les bannissements +- les établissements d'enseignement (school) +- les droits (right et listright) +- les utilisateurs de service (pour connexion automatique) + +On défini aussi des models qui héritent de django-ldapdb : +- ldapuser +- ldapgroup +- ldapserviceuser + +Ces utilisateurs ldap sont synchronisés à partir des objets +models sql classiques. Seuls certains champs essentiels sont +dupliqués. +""" + from __future__ import unicode_literals +import re +import uuid +import datetime + from django.db import models from django.db.models import Q from django import forms from django.db.models.signals import post_save, post_delete from django.dispatch import receiver from django.utils.functional import cached_property -from django.template import Context, RequestContext, loader +from django.template import Context, loader from django.core.mail import send_mail from django.core.urlresolvers import reverse +from django.db import transaction +from django.utils import timezone +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager +from django.core.validators import RegexValidator from reversion import revisions as reversion -from django.db import transaction import ldapdb.models import ldapdb.models.fields -from re2o.settings import RIGHTS_LINK, LDAP, GID_RANGES,UID_RANGES -import re, uuid -import datetime +from re2o.settings import RIGHTS_LINK, LDAP, GID_RANGES, UID_RANGES from re2o.login import hashNT -from django.utils import timezone -from django.contrib.auth.models import AbstractBaseUser, BaseUserManager - -from django.core.validators import MinLengthValidator -from django.core.validators import RegexValidator -from topologie.models import Room from cotisations.models import Cotisation, Facture, Paiement, Vente -from machines.models import Domain, Interface, MachineType, Machine, Nas, MachineType, Extension, regen -from preferences.models import GeneralOption, AssoOption, OptionalUser, OptionalMachine, MailMessageOption +from machines.models import Domain, Interface, Machine, regen +from preferences.models import GeneralOption, AssoOption, OptionalUser +from preferences.models import OptionalMachine, MailMessageOption -now = timezone.now() +DT_NOW = timezone.now() -#### Utilitaires généraux +# Utilitaires généraux def remove_user_room(room): """ Déménage de force l'ancien locataire de la chambre """ @@ -76,33 +98,42 @@ def linux_user_check(login): def linux_user_validator(login): - """ Retourne une erreur de validation si le login ne respecte + """ Retourne une erreur de validation si le login ne respecte pas les contraintes unix (maj, min, chiffres ou tiret)""" if not linux_user_check(login): raise forms.ValidationError( - ", ce pseudo ('%(label)s') contient des carractères interdits", - params={'label': login}, + ", ce pseudo ('%(label)s') contient des carractères interdits", + params={'label': login}, ) + def get_fresh_user_uid(): """ Renvoie le plus petit uid non pris. Fonction très paresseuse """ - uids = list(range(int(min(UID_RANGES['users'])),int(max(UID_RANGES['users'])))) + uids = list(range( + int(min(UID_RANGES['users'])), + int(max(UID_RANGES['users'])) + )) try: used_uids = list(User.objects.values_list('uid_number', flat=True)) except: used_uids = [] - free_uids = [ id for id in uids if id not in used_uids] + free_uids = [id for id in uids if id not in used_uids] return min(free_uids) + def get_fresh_gid(): """ Renvoie le plus petit gid libre """ - gids = list(range(int(min(GID_RANGES['posix'])),int(max(GID_RANGES['posix'])))) + gids = list(range( + int(min(GID_RANGES['posix'])), + int(max(GID_RANGES['posix'])) + )) used_gids = list(ListRight.objects.values_list('gid', flat=True)) - free_gids = [ id for id in gids if id not in used_gids] + free_gids = [id for id in gids if id not in used_gids] return min(free_gids) + def get_admin_right(): - """ Renvoie l'instance droit admin. La crée si elle n'existe pas + """ Renvoie l'instance droit admin. La crée si elle n'existe pas Lui attribue un gid libre""" try: admin_right = ListRight.objects.get(listright="admin") @@ -112,25 +143,74 @@ def get_admin_right(): admin_right.save() return admin_right -def all_adherent(search_time=now): - """ Fonction renvoyant tous les users adherents. Optimisee pour n'est qu'une seule requete sql - Inspecte les factures de l'user et ses cotisation, regarde si elles sont posterieur à now (end_time)""" - return User.objects.filter(facture__in=Facture.objects.filter(vente__in=Vente.objects.filter(cotisation__in=Cotisation.objects.filter(vente__in=Vente.objects.filter(facture__in=Facture.objects.all().exclude(valid=False))).filter(date_end__gt=search_time)))).distinct() -def all_baned(search_time=now): +def all_adherent(search_time=DT_NOW): + """ Fonction renvoyant tous les users adherents. Optimisee pour n'est + qu'une seule requete sql + Inspecte les factures de l'user et ses cotisation, regarde si elles + sont posterieur à now (end_time)""" + return User.objects.filter( + facture__in=Facture.objects.filter( + vente__in=Vente.objects.filter( + cotisation__in=Cotisation.objects.filter( + vente__in=Vente.objects.filter( + facture__in=Facture.objects.all().exclude(valid=False) + ) + ).filter(date_end__gt=search_time) + ) + ) + ).distinct() + + +def all_baned(search_time=DT_NOW): """ Fonction renvoyant tous les users bannis """ - return User.objects.filter(ban__in=Ban.objects.filter(date_end__gt=search_time)).distinct() + return User.objects.filter( + ban__in=Ban.objects.filter( + date_end__gt=search_time + ) + ).distinct() -def all_whitelisted(search_time=now): + +def all_whitelisted(search_time=DT_NOW): """ Fonction renvoyant tous les users whitelistes """ - return User.objects.filter(whitelist__in=Whitelist.objects.filter(date_end__gt=search_time)).distinct() + return User.objects.filter( + whitelist__in=Whitelist.objects.filter( + date_end__gt=search_time + ) + ).distinct() + + +def all_has_access(search_time=DT_NOW): + """ Renvoie tous les users beneficiant d'une connexion + : user adherent ou whiteliste et non banni """ + return User.objects.filter( + Q(state=User.STATE_ACTIVE) & + ~Q(ban__in=Ban.objects.filter(date_end__gt=search_time)) & + (Q(whitelist__in=Whitelist.objects.filter(date_end__gt=search_time)) | + Q(facture__in=Facture.objects.filter( + vente__in=Vente.objects.filter( + cotisation__in=Cotisation.objects.filter( + vente__in=Vente.objects.filter( + facture__in=Facture.objects.all() + .exclude(valid=False) + ) + ).filter(date_end__gt=search_time) + ) + ))) + ).distinct() -def all_has_access(search_time=now): - """ Renvoie tous les users beneficiant d'une connexion : user adherent ou whiteliste et non banni """ - return User.objects.filter(Q(state=User.STATE_ACTIVE) & ~Q(ban__in=Ban.objects.filter(date_end__gt=timezone.now())) & (Q(whitelist__in=Whitelist.objects.filter(date_end__gt=timezone.now())) | Q(facture__in=Facture.objects.filter(vente__in=Vente.objects.filter(cotisation__in=Cotisation.objects.filter(vente__in=Vente.objects.filter(facture__in=Facture.objects.all().exclude(valid=False))).filter(date_end__gt=search_time)))))).distinct() class UserManager(BaseUserManager): - def _create_user(self, pseudo, name, surname, email, password=None, su=False): + """User manager basique de django""" + def _create_user( + self, + pseudo, + name, + surname, + email, + password=None, + su=False + ): if not pseudo: raise ValueError('Users must have an username') @@ -174,28 +254,53 @@ class User(AbstractBaseUser): STATE_DISABLED = 1 STATE_ARCHIVE = 2 STATES = ( - (0, 'STATE_ACTIVE'), - (1, 'STATE_DISABLED'), - (2, 'STATE_ARCHIVE'), - ) + (0, 'STATE_ACTIVE'), + (1, 'STATE_DISABLED'), + (2, 'STATE_ARCHIVE'), + ) def auto_uid(): + """Renvoie un uid libre""" return get_fresh_user_uid() name = models.CharField(max_length=255) surname = models.CharField(max_length=255) - pseudo = models.CharField(max_length=32, unique=True, help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", validators=[linux_user_validator]) + pseudo = models.CharField( + max_length=32, + unique=True, + help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", + validators=[linux_user_validator] + ) email = models.EmailField() - school = models.ForeignKey('School', on_delete=models.PROTECT, null=True, blank=True) - shell = models.ForeignKey('ListShell', on_delete=models.PROTECT, null=True, blank=True) - 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) + school = models.ForeignKey( + 'School', + on_delete=models.PROTECT, + null=True, + blank=True + ) + shell = models.ForeignKey( + 'ListShell', + on_delete=models.PROTECT, + null=True, + blank=True + ) + 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_ntlm = models.CharField(max_length=255) state = models.IntegerField(choices=STATES, default=STATE_ACTIVE) registered = models.DateTimeField(auto_now_add=True) telephone = models.CharField(max_length=15, blank=True, null=True) uid_number = models.IntegerField(default=auto_uid, unique=True) - rezo_rez_uid = models.IntegerField(unique=True, blank=True, null=True) + rezo_rez_uid = models.IntegerField(unique=True, blank=True, null=True) USERNAME_FIELD = 'pseudo' REQUIRED_FIELDS = ['name', 'surname', 'email'] @@ -223,7 +328,8 @@ class User(AbstractBaseUser): @is_admin.setter def is_admin(self, value): - """ Change la valeur de admin à true ou false suivant la valeur de value""" + """ Change la valeur de admin à true ou false suivant la valeur de + value""" if value and not self.is_admin: self.make_admin() elif not value and self.is_admin: @@ -247,7 +353,7 @@ class User(AbstractBaseUser): for right in RIGHTS_LINK[perm]: query = query | Q(right__listright=right) if Right.objects.filter(Q(user=self) & query): - return True + return True try: Right.objects.get(user=self, right__listright=perm) except Right.DoesNotExist: @@ -255,17 +361,20 @@ class User(AbstractBaseUser): return True def has_perm(self, perm, obj=None): + """Ne sert à rien""" return True - def has_right(self, right): - """ Renvoie si un user a un right donné. Crée le right si il n'existe pas""" + """ Renvoie si un user a un right donné. Crée le right si il n'existe + pas""" try: list_right = ListRight.objects.get(listright=right) except: list_right = ListRight(listright=right, gid=get_fresh_gid()) list_right.save() - return Right.objects.filter(user=self).filter(right=list_right).exists() + return Right.objects.filter(user=self).filter( + right=list_right + ).exists() @cached_property def is_bureau(self): @@ -279,9 +388,10 @@ class User(AbstractBaseUser): @cached_property def is_cableur(self): - """ True si l'user a les droits cableur + """ True si l'user a les droits cableur (également true si bureau, infra ou bofh)""" - return self.has_right('cableur') or self.has_right('bureau') or self.has_right('infra') or self.has_right('bofh') + return self.has_right('cableur') or self.has_right('bureau') or\ + self.has_right('infra') or self.has_right('bofh') @cached_property def is_trez(self): @@ -296,15 +406,22 @@ class User(AbstractBaseUser): def end_adhesion(self): """ Renvoie la date de fin d'adhésion d'un user. Examine les objets cotisation""" - date_max = Cotisation.objects.filter(vente__in=Vente.objects.filter(facture__in=Facture.objects.filter(user=self).exclude(valid=False))).aggregate(models.Max('date_end'))['date_end__max'] + date_max = Cotisation.objects.filter( + vente__in=Vente.objects.filter( + facture__in=Facture.objects.filter( + user=self + ).exclude(valid=False) + ) + ).aggregate(models.Max('date_end'))['date_end__max'] return date_max def is_adherent(self): - """ Renvoie True si l'user est adhérent : si self.end_adhesion()>now""" + """ Renvoie True si l'user est adhérent : si + self.end_adhesion()>now""" end = self.end_adhesion() if not end: return False - elif end < timezone.now(): + elif end < DT_NOW: return False else: return True @@ -312,13 +429,17 @@ class User(AbstractBaseUser): @cached_property def end_ban(self): """ Renvoie la date de fin de ban d'un user, False sinon """ - date_max = Ban.objects.filter(user=self).aggregate(models.Max('date_end'))['date_end__max'] + date_max = Ban.objects.filter( + user=self + ).aggregate(models.Max('date_end'))['date_end__max'] return date_max @cached_property def end_whitelist(self): """ Renvoie la date de fin de whitelist d'un user, False sinon """ - date_max = Whitelist.objects.filter(user=self).aggregate(models.Max('date_end'))['date_end__max'] + date_max = Whitelist.objects.filter( + user=self + ).aggregate(models.Max('date_end'))['date_end__max'] return date_max @cached_property @@ -327,7 +448,7 @@ class User(AbstractBaseUser): end = self.end_ban if not end: return False - elif end < timezone.now(): + elif end < DT_NOW: return False else: return True @@ -338,14 +459,14 @@ class User(AbstractBaseUser): end = self.end_whitelist if not end: return False - elif end < timezone.now(): + elif end < DT_NOW: return False else: return True def has_access(self): """ Renvoie si un utilisateur a accès à internet """ - return self.state == User.STATE_ACTIVE \ + return self.state == User.STATE_ACTIVE\ and not self.is_ban and (self.is_adherent() or self.is_whitelisted) def end_access(self): @@ -358,27 +479,50 @@ class User(AbstractBaseUser): else: if not self.end_whitelist: return self.end_adhesion() - else: + else: return max(self.end_adhesion(), self.end_whitelist) @cached_property def solde(self): - """ Renvoie le solde d'un user. Vérifie que l'option solde est activé, retourne 0 sinon. + """ Renvoie le solde d'un user. Vérifie que l'option solde est + activé, retourne 0 sinon. Somme les crédits de solde et retire les débit payés par solde""" - options, created = OptionalUser.objects.get_or_create() + options, _created = OptionalUser.objects.get_or_create() user_solde = options.user_solde if user_solde: - solde_object, created=Paiement.objects.get_or_create(moyen='Solde') - somme_debit = Vente.objects.filter(facture__in=Facture.objects.filter(user=self, paiement=solde_object)).aggregate(total=models.Sum(models.F('prix')*models.F('number'), output_field=models.FloatField()))['total'] or 0 - somme_credit =Vente.objects.filter(facture__in=Facture.objects.filter(user=self), name="solde").aggregate(total=models.Sum(models.F('prix')*models.F('number'), output_field=models.FloatField()))['total'] or 0 + solde_object, _created = Paiement.objects.get_or_create( + moyen='Solde' + ) + somme_debit = Vente.objects.filter( + facture__in=Facture.objects.filter( + user=self, + paiement=solde_object + ) + ).aggregate( + total=models.Sum( + models.F('prix')*models.F('number'), + output_field=models.FloatField() + ) + )['total'] or 0 + somme_credit = Vente.objects.filter( + facture__in=Facture.objects.filter(user=self), + name="solde" + ).aggregate( + total=models.Sum( + models.F('prix')*models.F('number'), + output_field=models.FloatField() + ) + )['total'] or 0 return somme_credit - somme_debit else: return 0 def user_interfaces(self, active=True): - """ Renvoie toutes les interfaces dont les machines appartiennent à self - Par defaut ne prend que les interfaces actives""" - return Interface.objects.filter(machine__in=Machine.objects.filter(user=self, active=active)).select_related('domain__extension') + """ Renvoie toutes les interfaces dont les machines appartiennent à + self. Par defaut ne prend que les interfaces actives""" + return Interface.objects.filter( + machine__in=Machine.objects.filter(user=self, active=active) + ).select_related('domain__extension') def assign_ips(self): """ Assign une ipv4 aux machines d'un user """ @@ -400,17 +544,19 @@ class User(AbstractBaseUser): interface.save() def archive(self): - """ Archive l'user : appelle unassign_ips() puis passe state à ARCHIVE""" + """ Archive l'user : appelle unassign_ips() puis passe state à + ARCHIVE""" self.unassign_ips() - self.state = User.STATE_ARCHIVE + self.state = User.STATE_ARCHIVE def unarchive(self): - """ Désarchive l'user : réassigne ses ip et le passe en state ACTIVE""" + """ Désarchive l'user : réassigne ses ip et le passe en state + ACTIVE""" self.assign_ips() self.state = User.STATE_ACTIVE def has_module_perms(self, app_label): - # Simplest version again + """True, a toutes les permissions de module""" return True def make_admin(self): @@ -419,16 +565,20 @@ class User(AbstractBaseUser): user_admin_right.save() def un_admin(self): + """Supprime les droits admin d'un user""" try: - user_right = Right.objects.get(user=self,right=get_admin_right()) + user_right = Right.objects.get(user=self, right=get_admin_right()) except Right.DoesNotExist: return user_right.delete() def ldap_sync(self, base=True, access_refresh=True, mac_refresh=True): - """ Synchronisation du ldap. Synchronise dans le ldap les attributs de self - Options : base : synchronise tous les attributs de base - nom, prenom, mail, password, shell, home - access_refresh : synchronise le dialup_access notant si l'user a accès aux services + """ Synchronisation du ldap. Synchronise dans le ldap les attributs de + self + Options : base : synchronise tous les attributs de base - nom, prenom, + mail, password, shell, home + access_refresh : synchronise le dialup_access notant si l'user a accès + aux services mac_refresh : synchronise les machines de l'user""" self.refresh_from_db() try: @@ -441,7 +591,8 @@ class User(AbstractBaseUser): user_ldap.dialupAccess = str(self.has_access()) user_ldap.home_directory = '/home/' + self.pseudo user_ldap.mail = self.email - user_ldap.given_name = self.surname.lower() + '_' + self.name.lower()[:3] + user_ldap.given_name = self.surname.lower() + '_'\ + + self.name.lower()[:3] user_ldap.gid = LDAP['user_gid'] user_ldap.user_password = self.password[:6] + self.password[7:] user_ldap.sambat_nt_password = self.pwd_ntlm.upper() @@ -454,7 +605,10 @@ class User(AbstractBaseUser): if access_refresh: user_ldap.dialupAccess = str(self.has_access()) if mac_refresh: - user_ldap.macs = [inter.mac_bare() for inter in Interface.objects.filter(machine__in=Machine.objects.filter(user=self))] + user_ldap.macs = [inter.mac_bare() for inter in + Interface.objects.filter( + machine__in=Machine.objects.filter(user=self) + )] user_ldap.save() def ldap_del(self): @@ -467,53 +621,69 @@ class User(AbstractBaseUser): def notif_inscription(self): """ Prend en argument un objet user, envoie un mail de bienvenue """ - t = loader.get_template('users/email_welcome') - assooptions, created = AssoOption.objects.get_or_create() - mailmessageoptions, created = MailMessageOption.objects.get_or_create() - general_options, created = GeneralOption.objects.get_or_create() - c = Context({ + template = loader.get_template('users/email_welcome') + assooptions, _created = AssoOption.objects.get_or_create() + mailmessageoptions, _created = MailMessageOption\ + .objects.get_or_create() + general_options, _created = GeneralOption.objects.get_or_create() + context = Context({ 'nom': str(self.name) + ' ' + str(self.surname), 'asso_name': assooptions.name, 'asso_email': assooptions.contact, - 'welcome_mail_fr' : mailmessageoptions.welcome_mail_fr, - 'welcome_mail_en' : mailmessageoptions.welcome_mail_en, - 'pseudo':self.pseudo, + 'welcome_mail_fr': mailmessageoptions.welcome_mail_fr, + 'welcome_mail_en': mailmessageoptions.welcome_mail_en, + 'pseudo': self.pseudo, }) - send_mail('Bienvenue au %(name)s / Welcome to %(name)s' % {'name': assooptions.name }, '', - general_options.email_from, [self.email], html_message=t.render(c)) + send_mail( + 'Bienvenue au %(name)s / Welcome to %(name)s' % { + 'name': assooptions.name + }, + '', + general_options.email_from, + [self.email], + html_message=template.render(context) + ) return def reset_passwd_mail(self, request): - """ Prend en argument un request, envoie un mail de réinitialisation de mot de pass """ + """ Prend en argument un request, envoie un mail de + réinitialisation de mot de pass """ req = Request() req.type = Request.PASSWD req.user = self req.save() - t = loader.get_template('users/email_passwd_request') - options, created = AssoOption.objects.get_or_create() - general_options, created = GeneralOption.objects.get_or_create() - c = { + template = loader.get_template('users/email_passwd_request') + options, _created = AssoOption.objects.get_or_create() + general_options, _created = GeneralOption.objects.get_or_create() + context = { 'name': str(req.user.name) + ' ' + str(req.user.surname), 'asso': options.name, 'asso_mail': options.contact, 'site_name': general_options.site_name, 'url': request.build_absolute_uri( - reverse('users:process', kwargs={'token': req.token})), + reverse('users:process', kwargs={'token': req.token})), 'expire_in': str(general_options.req_expire_hrs) + ' heures', } - send_mail('Changement de mot de passe du %(name)s / Password renewal for %(name)s' % {'name': options.name }, t.render(c), - general_options.email_from, [req.user.email], fail_silently=False) + send_mail( + 'Changement de mot de passe du %(name)s / Password\ + renewal for %(name)s' % {'name': options.name}, + template.render(context), + general_options.email_from, + [req.user.email], + fail_silently=False + ) return def autoregister_machine(self, mac_address, nas_type): - """ Fonction appellée par freeradius. Enregistre la mac pour une machine inconnue - sur le compte de l'user""" + """ Fonction appellée par freeradius. Enregistre la mac pour + une machine inconnue sur le compte de l'user""" all_interfaces = self.user_interfaces(active=False) - options, created = OptionalMachine.objects.get_or_create() + options, _created = OptionalMachine.objects.get_or_create() if all_interfaces.count() > options.max_lambdauser_interfaces: return False, "Maximum de machines enregistrees atteinte" if not nas_type: - return False, "Re2o ne sait pas à quel machinetype affecter cette machine" + return False, "Re2o ne sait pas à quel machinetype affecter cette\ + machine" machine_type_cible = nas_type.machine_type try: machine_parent = Machine() @@ -533,12 +703,12 @@ class User(AbstractBaseUser): domain.interface_parent = interface_cible domain.clean() domain.save() - except Exception as e: - return False, e + except Exception as error: + return False, error return True, "Ok" def set_user_password(self, password): - """ A utiliser de préférence, set le password en hash courrant et + """ A utiliser de préférence, set le password en hash courrant et dans la version ntlm""" self.set_password(password) self.pwd_ntlm = hashNT(password) @@ -547,23 +717,28 @@ class User(AbstractBaseUser): def get_next_domain_name(self): """Look for an available name for a new interface for this user by trying "pseudo0", "pseudo1", "pseudo2", ... + + Recherche un nom disponible, pour une machine. Doit-être + unique, concatène le nom, le pseudo et le numero de machine """ def simple_pseudo(): + """Renvoie le pseudo sans underscore (compat dns)""" return self.pseudo.replace('_', '-').lower() - def composed_pseudo( n ): - return simple_pseudo() + str(n) + def composed_pseudo(name): + """Renvoie le resultat de simplepseudo et rajoute le nom""" + return simple_pseudo() + str(name) num = 0 - while Domain.objects.filter(name=composed_pseudo(num)) : + while Domain.objects.filter(name=composed_pseudo(num)): num += 1 return composed_pseudo(num) - def __str__(self): return self.pseudo + @receiver(post_save, sender=User) def user_post_save(sender, **kwargs): """ Synchronisation post_save : envoie le mail de bienvenue si creation @@ -575,29 +750,44 @@ def user_post_save(sender, **kwargs): user.ldap_sync(base=True, access_refresh=True, mac_refresh=False) regen('mailing') + @receiver(post_delete, sender=User) def user_post_delete(sender, **kwargs): + """Post delete d'un user, on supprime son instance ldap""" user = kwargs['instance'] user.ldap_del() regen('mailing') + class ServiceUser(AbstractBaseUser): """ Classe des users daemons, règle leurs accès au ldap""" readonly = 'readonly' ACCESS = ( - ('auth', 'auth'), - ('readonly', 'readonly'), - ('usermgmt', 'usermgmt'), - ) + ('auth', 'auth'), + ('readonly', 'readonly'), + ('usermgmt', 'usermgmt'), + ) PRETTY_NAME = "Utilisateurs de service" - pseudo = models.CharField(max_length=32, unique=True, help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", validators=[linux_user_validator]) - access_group = models.CharField(choices=ACCESS, default=readonly, max_length=32) - comment = models.CharField(help_text="Commentaire", max_length=255, blank=True) + pseudo = models.CharField( + max_length=32, + unique=True, + help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", + validators=[linux_user_validator] + ) + access_group = models.CharField( + choices=ACCESS, + default=readonly, + max_length=32 + ) + comment = models.CharField( + help_text="Commentaire", + max_length=255, + blank=True + ) USERNAME_FIELD = 'pseudo' - objects = UserManager() def ldap_sync(self): @@ -611,6 +801,7 @@ class ServiceUser(AbstractBaseUser): self.serviceuser_group_sync() def ldap_del(self): + """Suppression de l'instance ldap d'un service user""" try: user_ldap = LdapServiceUser.objects.get(name=self.pseudo) user_ldap.delete() @@ -619,30 +810,38 @@ class ServiceUser(AbstractBaseUser): self.serviceuser_group_sync() def serviceuser_group_sync(self): + """Synchronise le groupe et les droits de groupe dans le ldap""" try: group = LdapServiceUserGroup.objects.get(name=self.access_group) except: group = LdapServiceUserGroup(name=self.access_group) - group.members = list(LdapServiceUser.objects.filter(name__in=[user.pseudo for user in ServiceUser.objects.filter(access_group=self.access_group)]).values_list('dn', flat=True)) + group.members = list(LdapServiceUser.objects.filter( + name__in=[user.pseudo for user in ServiceUser.objects.filter( + access_group=self.access_group + )]).values_list('dn', flat=True)) group.save() def __str__(self): return self.pseudo + @receiver(post_save, sender=ServiceUser) def service_user_post_save(sender, **kwargs): """ Synchronise un service user ldap après modification django""" service_user = kwargs['instance'] service_user.ldap_sync() + @receiver(post_delete, sender=ServiceUser) def service_user_post_delete(sender, **kwargs): """ Supprime un service user ldap après suppression django""" service_user = kwargs['instance'] service_user.ldap_del() + class Right(models.Model): - """ Couple droit/user. Peut-être aurait-on mieux fait ici d'utiliser un manytomany + """ Couple droit/user. Peut-être aurait-on mieux fait ici d'utiliser un + manytomany Ceci dit le résultat aurait été le même avec une table intermediaire""" PRETTY_NAME = "Droits affectés à des users" @@ -655,18 +854,21 @@ class Right(models.Model): def __str__(self): return str(self.user) + @receiver(post_save, sender=Right) def right_post_save(sender, **kwargs): """ Synchronise les users ldap groups avec les groupes de droits""" right = kwargs['instance'].right right.ldap_sync() + @receiver(post_delete, sender=Right) def right_post_delete(sender, **kwargs): """ Supprime l'user du groupe""" right = kwargs['instance'].right right.ldap_sync() + class School(models.Model): """ Etablissement d'enseignement""" PRETTY_NAME = "Etablissements enregistrés" @@ -678,46 +880,69 @@ class School(models.Model): class ListRight(models.Model): - """ Ensemble des droits existants. Chaque droit crée un groupe ldap synchronisé, avec gid. + """ Ensemble des droits existants. Chaque droit crée un groupe + ldap synchronisé, avec gid. Permet de gérer facilement les accès serveurs et autres - La clef de recherche est le gid, pour cette raison là il n'est plus modifiable après creation""" + La clef de recherche est le gid, pour cette raison là + il n'est plus modifiable après creation""" PRETTY_NAME = "Liste des droits existants" - listright = models.CharField(max_length=255, unique=True, validators=[RegexValidator('^[a-z]+$', message="Les groupes unix ne peuvent contenir que des lettres minuscules")]) + listright = models.CharField( + max_length=255, + unique=True, + validators=[RegexValidator( + '^[a-z]+$', + message="Les groupes unix ne peuvent contenir\ + que des lettres minuscules" + )] + ) gid = models.IntegerField(unique=True, null=True) - details = models.CharField(help_text="Description", max_length=255, blank=True) + details = models.CharField( + help_text="Description", + max_length=255, + blank=True + ) def __str__(self): return self.listright def ldap_sync(self): + """Sychronise les groups ldap avec le model listright coté django""" try: group_ldap = LdapUserGroup.objects.get(gid=self.gid) except LdapUserGroup.DoesNotExist: group_ldap = LdapUserGroup(gid=self.gid) group_ldap.name = self.listright - group_ldap.members = [right.user.pseudo for right in Right.objects.filter(right=self)] + group_ldap.members = [right.user.pseudo for right + in Right.objects.filter(right=self)] group_ldap.save() def ldap_del(self): + """Supprime un groupe ldap""" try: group_ldap = LdapUserGroup.objects.get(gid=self.gid) group_ldap.delete() except LdapUserGroup.DoesNotExist: pass + @receiver(post_save, sender=ListRight) def listright_post_save(sender, **kwargs): """ Synchronise le droit ldap quand il est modifié""" right = kwargs['instance'] right.ldap_sync() + @receiver(post_delete, sender=ListRight) def listright_post_delete(sender, **kwargs): + """Suppression d'un groupe ldap après suppression coté django""" right = kwargs['instance'] right.ldap_del() + class ListShell(models.Model): + """Un shell possible. Pas de check si ce shell existe, les + admin sont des grands""" PRETTY_NAME = "Liste des shells disponibles" shell = models.CharField(max_length=255, unique=True) @@ -725,6 +950,7 @@ class ListShell(models.Model): def __str__(self): return self.shell + class Ban(models.Model): """ Bannissement. Actuellement a un effet tout ou rien. Gagnerait à être granulaire""" @@ -734,38 +960,45 @@ class Ban(models.Model): STATE_SOFT = 1 STATE_BRIDAGE = 2 STATES = ( - (0, 'HARD (aucun accès)'), - (1, 'SOFT (accès local seulement)'), - (2, 'BRIDAGE (bridage du débit)'), - ) + (0, 'HARD (aucun accès)'), + (1, 'SOFT (accès local seulement)'), + (2, 'BRIDAGE (bridage du débit)'), + ) 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') - state = models.IntegerField(choices=STATES, default=STATE_HARD) + state = models.IntegerField(choices=STATES, default=STATE_HARD) def notif_ban(self): """ Prend en argument un objet ban, envoie un mail de notification """ - general_options, created = GeneralOption.objects.get_or_create() - t = loader.get_template('users/email_ban_notif') - options, created = AssoOption.objects.get_or_create() - c = Context({ + general_options, _created = GeneralOption.objects.get_or_create() + template = loader.get_template('users/email_ban_notif') + options, _created = AssoOption.objects.get_or_create() + context = Context({ 'name': str(self.user.name) + ' ' + str(self.user.surname), 'raison': self.raison, 'date_end': self.date_end, - 'asso_name' : options.name, + 'asso_name': options.name, }) - send_mail('Deconnexion disciplinaire', t.render(c), - general_options.email_from, [self.user.email], fail_silently=False) + send_mail( + 'Deconnexion disciplinaire', + template.render(context), + general_options.email_from, + [self.user.email], + fail_silently=False + ) return def is_active(self): - return self.date_end > now + """Ce ban est-il actif?""" + return self.date_end > DT_NOW def __str__(self): return str(self.user) + ' ' + str(self.raison) + @receiver(post_save, sender=Ban) def ban_post_save(sender, **kwargs): """ Regeneration de tous les services après modification d'un ban""" @@ -782,6 +1015,7 @@ def ban_post_save(sender, **kwargs): regen('dhcp') regen('mac_ip_list') + @receiver(post_delete, sender=Ban) def ban_post_delete(sender, **kwargs): """ Regen de tous les services après suppression d'un ban""" @@ -791,7 +1025,11 @@ def ban_post_delete(sender, **kwargs): regen('dhcp') regen('mac_ip_list') + class Whitelist(models.Model): + """Accès à titre gracieux. L'utilisateur ne paye pas; se voit + accorder un accès internet pour une durée défini. Moins + fort qu'un ban quel qu'il soit""" PRETTY_NAME = "Liste des accès gracieux" user = models.ForeignKey('User', on_delete=models.PROTECT) @@ -800,13 +1038,16 @@ class Whitelist(models.Model): date_end = models.DateTimeField(help_text='%d/%m/%y %H:%M:%S') def is_active(self): - return self.date_end > now + return self.date_end > DT_NOW def __str__(self): return str(self.user) + ' ' + str(self.raison) + @receiver(post_save, sender=Whitelist) def whitelist_post_save(sender, **kwargs): + """Après modification d'une whitelist, on synchronise les services + et on lui permet d'avoir internet""" whitelist = kwargs['instance'] user = whitelist.user user.ldap_sync(base=False, access_refresh=True, mac_refresh=False) @@ -819,17 +1060,21 @@ def whitelist_post_save(sender, **kwargs): regen('dhcp') regen('mac_ip_list') + @receiver(post_delete, sender=Whitelist) def whitelist_post_delete(sender, **kwargs): + """Après suppression d'une whitelist, on supprime l'accès internet + en forçant la régénration""" user = kwargs['instance'].user user.ldap_sync(base=False, access_refresh=True, mac_refresh=False) regen('mailing') regen('dhcp') regen('mac_ip_list') + class Request(models.Model): """ Objet request, générant une url unique de validation. - Utilisé par exemple pour la generation du mot de passe et + Utilisé par exemple pour la generation du mot de passe et sa réinitialisation""" PASSWD = 'PW' EMAIL = 'EM' @@ -845,38 +1090,86 @@ class Request(models.Model): def save(self): if not self.expires_at: - options, created = GeneralOption.objects.get_or_create() - self.expires_at = timezone.now() \ + options, _created = GeneralOption.objects.get_or_create() + self.expires_at = DT_NOW \ + datetime.timedelta(hours=options.req_expire_hrs) if not self.token: self.token = str(uuid.uuid4()).replace('-', '') # remove hyphens super(Request, self).save() + class LdapUser(ldapdb.models.Model): """ Class for representing an LDAP user entry. """ # LDAP meta-data base_dn = LDAP['base_user_dn'] - object_classes = ['inetOrgPerson','top','posixAccount','sambaSamAccount','radiusprofile', 'shadowAccount'] + object_classes = ['inetOrgPerson', 'top', 'posixAccount', + 'sambaSamAccount', 'radiusprofile', + 'shadowAccount'] # attributes gid = ldapdb.models.fields.IntegerField(db_column='gidNumber') - name = ldapdb.models.fields.CharField(db_column='cn', max_length=200, primary_key=True) + name = ldapdb.models.fields.CharField( + db_column='cn', + max_length=200, + primary_key=True + ) uid = ldapdb.models.fields.CharField(db_column='uid', max_length=200) - uidNumber = ldapdb.models.fields.IntegerField(db_column='uidNumber', unique=True) + uidNumber = ldapdb.models.fields.IntegerField( + db_column='uidNumber', + unique=True + ) sn = ldapdb.models.fields.CharField(db_column='sn', max_length=200) - login_shell = ldapdb.models.fields.CharField(db_column='loginShell', max_length=200, blank=True, null=True) - mail = ldapdb.models.fields.CharField(db_column='mail', max_length=200) - given_name = ldapdb.models.fields.CharField(db_column='givenName', max_length=200) - home_directory = ldapdb.models.fields.CharField(db_column='homeDirectory', max_length=200) - display_name = ldapdb.models.fields.CharField(db_column='displayName', max_length=200, blank=True, null=True) + login_shell = ldapdb.models.fields.CharField( + db_column='loginShell', + max_length=200, + blank=True, + null=True + ) + mail = ldapdb.models.fields.CharField(db_column='mail', max_length=200) + given_name = ldapdb.models.fields.CharField( + db_column='givenName', + max_length=200 + ) + home_directory = ldapdb.models.fields.CharField( + db_column='homeDirectory', + max_length=200 + ) + display_name = ldapdb.models.fields.CharField( + db_column='displayName', + max_length=200, + blank=True, + null=True + ) dialupAccess = ldapdb.models.fields.CharField(db_column='dialupAccess') - sambaSID = ldapdb.models.fields.IntegerField(db_column='sambaSID', unique=True) - user_password = ldapdb.models.fields.CharField(db_column='userPassword', max_length=200, blank=True, null=True) - sambat_nt_password = ldapdb.models.fields.CharField(db_column='sambaNTPassword', max_length=200, blank=True, null=True) - macs = ldapdb.models.fields.ListField(db_column='radiusCallingStationId', max_length=200, blank=True, null=True) - shadowexpire = ldapdb.models.fields.CharField(db_column='shadowExpire', blank=True, null=True) + sambaSID = ldapdb.models.fields.IntegerField( + db_column='sambaSID', + unique=True + ) + user_password = ldapdb.models.fields.CharField( + db_column='userPassword', + max_length=200, + blank=True, + null=True + ) + sambat_nt_password = ldapdb.models.fields.CharField( + db_column='sambaNTPassword', + max_length=200, + blank=True, + null=True + ) + macs = ldapdb.models.fields.ListField( + db_column='radiusCallingStationId', + max_length=200, + blank=True, + null=True + ) + shadowexpire = ldapdb.models.fields.CharField( + db_column='shadowExpire', + blank=True, + null=True + ) def __str__(self): return self.name @@ -890,9 +1183,12 @@ class LdapUser(ldapdb.models.Model): self.sambaSID = self.uidNumber super(LdapUser, self).save(*args, **kwargs) + class LdapUserGroup(ldapdb.models.Model): """ - Class for representing an LDAP user entry. + Class for representing an LDAP group entry. + + Un groupe ldap """ # LDAP meta-data base_dn = LDAP['base_usergroup_dn'] @@ -901,38 +1197,64 @@ class LdapUserGroup(ldapdb.models.Model): # attributes gid = ldapdb.models.fields.IntegerField(db_column='gidNumber') members = ldapdb.models.fields.ListField(db_column='memberUid', blank=True) - name = ldapdb.models.fields.CharField(db_column='cn', max_length=200, primary_key=True) + name = ldapdb.models.fields.CharField( + db_column='cn', + max_length=200, + primary_key=True + ) def __str__(self): return self.name + class LdapServiceUser(ldapdb.models.Model): """ Class for representing an LDAP userservice entry. + + Un user de service coté ldap """ # LDAP meta-data base_dn = LDAP['base_userservice_dn'] - object_classes = ['applicationProcess','simpleSecurityObject'] + object_classes = ['applicationProcess', 'simpleSecurityObject'] # attributes - name = ldapdb.models.fields.CharField(db_column='cn', max_length=200, primary_key=True) - user_password = ldapdb.models.fields.CharField(db_column='userPassword', max_length=200, blank=True, null=True) + name = ldapdb.models.fields.CharField( + db_column='cn', + max_length=200, + primary_key=True + ) + user_password = ldapdb.models.fields.CharField( + db_column='userPassword', + max_length=200, + blank=True, + null=True + ) def __str__(self): return self.name + class LdapServiceUserGroup(ldapdb.models.Model): """ Class for representing an LDAP userservice entry. + + Un group user de service coté ldap. Dans userservicegroupdn + (voir dans settings_local.py) """ # LDAP meta-data base_dn = LDAP['base_userservicegroup_dn'] object_classes = ['groupOfNames'] # attributes - name = ldapdb.models.fields.CharField(db_column='cn', max_length=200, primary_key=True) - members = ldapdb.models.fields.ListField(db_column='member', blank=True) + name = ldapdb.models.fields.CharField( + db_column='cn', + max_length=200, + primary_key=True + ) + members = ldapdb.models.fields.ListField( + db_column='member', + blank=True + ) def __str__(self): return self.name -