8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-30 08:32:26 +00:00
re2o/users/models.py

939 lines
36 KiB
Python
Raw Normal View History

# -*- mode: python; coding: utf-8 -*-
2017-01-15 23:01:18 +00:00
# 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
# Copyright © 2017 Augustin Lemesle
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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.
from __future__ import unicode_literals
2016-06-30 01:39:07 +00:00
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.core.mail import send_mail
from django.core.urlresolvers import reverse
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.login import hashNT
2016-06-30 01:39:07 +00:00
from django.utils import timezone
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
2017-06-18 12:59:53 +00:00
from django.core.validators import MinLengthValidator
from django.core.validators import RegexValidator
from topologie.models import Room
2017-06-26 17:23:01 +00:00
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
now = timezone.now()
2017-10-04 15:53:30 +00:00
#### Utilitaires généraux
2016-07-03 13:26:44 +00:00
def remove_user_room(room):
""" Déménage de force l'ancien locataire de la chambre """
try:
user = User.objects.get(room=room)
except User.DoesNotExist:
return
user.room = None
user.save()
def linux_user_check(login):
2016-07-06 00:56:30 +00:00
""" Validation du pseudo pour respecter les contraintes unix"""
UNIX_LOGIN_PATTERN = re.compile("^[a-zA-Z0-9-]*[$]?$")
return UNIX_LOGIN_PATTERN.match(login)
def linux_user_validator(login):
2017-10-04 15:53:30 +00:00
""" 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):
2016-07-06 00:56:30 +00:00
raise forms.ValidationError(
", ce pseudo ('%(label)s') contient des carractères interdits",
params={'label': login},
)
2016-10-12 10:24:01 +00:00
def get_fresh_user_uid():
2017-10-04 15:53:30 +00:00
""" Renvoie le plus petit uid non pris. Fonction très paresseuse """
2016-10-12 10:24:01 +00:00
uids = list(range(int(min(UID_RANGES['users'])),int(max(UID_RANGES['users']))))
2016-12-18 09:52:20 +00:00
try:
used_uids = list(User.objects.values_list('uid_number', flat=True))
2016-12-18 09:52:20 +00:00
except:
used_uids = []
2016-10-12 10:24:01 +00:00
free_uids = [ id for id in uids if id not in used_uids]
return min(free_uids)
def get_fresh_gid():
2017-10-04 15:53:30 +00:00
""" Renvoie le plus petit gid libre """
2016-10-12 10:24:01 +00:00
gids = list(range(int(min(GID_RANGES['posix'])),int(max(GID_RANGES['posix']))))
used_gids = list(ListRight.objects.values_list('gid', flat=True))
2016-10-12 10:24:01 +00:00
free_gids = [ id for id in gids if id not in used_gids]
return min(free_gids)
def get_admin_right():
2017-10-04 15:53:30 +00:00
""" 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")
except ListRight.DoesNotExist:
admin_right = ListRight(listright="admin")
2016-10-12 10:24:01 +00:00
admin_right.gid = get_fresh_gid()
admin_right.save()
return admin_right
def all_adherent(search_time=now):
2017-10-04 15:53:30 +00:00
""" 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):
2017-10-04 15:53:30 +00:00
""" Fonction renvoyant tous les users bannis """
return User.objects.filter(ban__in=Ban.objects.filter(date_end__gt=search_time)).distinct()
def all_whitelisted(search_time=now):
2017-10-04 15:53:30 +00:00
""" Fonction renvoyant tous les users whitelistes """
return User.objects.filter(whitelist__in=Whitelist.objects.filter(date_end__gt=search_time)).distinct()
def all_has_access(search_time=now):
2017-10-04 15:53:30 +00:00
""" 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):
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):
2017-10-04 15:53:30 +00:00
""" Definition de l'utilisateur de base.
Champs principaux : name, surnname, pseudo, email, room, password
Herite du django BaseUser et du système d'auth django"""
PRETTY_NAME = "Utilisateurs"
2016-06-30 01:39:07 +00:00
STATE_ACTIVE = 0
2016-10-18 15:11:45 +00:00
STATE_DISABLED = 1
STATE_ARCHIVE = 2
2016-06-30 01:39:07 +00:00
STATES = (
2016-06-30 18:18:06 +00:00
(0, 'STATE_ACTIVE'),
2016-10-18 15:11:45 +00:00
(1, 'STATE_DISABLED'),
(2, 'STATE_ARCHIVE'),
2016-06-30 01:39:07 +00:00
)
2016-07-31 03:03:07 +00:00
def auto_uid():
2016-10-12 10:24:01 +00:00
return get_fresh_user_uid()
2016-07-31 03:03:07 +00:00
2016-06-30 01:39:07 +00:00
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])
2016-06-30 01:39:07 +00:00
email = models.EmailField()
2016-10-11 15:11:46 +00:00
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)
2016-06-30 01:39:07 +00:00
pwd_ntlm = models.CharField(max_length=255)
state = models.IntegerField(choices=STATES, default=STATE_ACTIVE)
registered = models.DateTimeField(auto_now_add=True)
2017-06-25 02:12:21 +00:00
telephone = models.CharField(max_length=15, blank=True, null=True)
2016-07-31 03:03:07 +00:00
uid_number = models.IntegerField(default=auto_uid, unique=True)
2016-10-07 19:30:05 +00:00
rezo_rez_uid = models.IntegerField(unique=True, blank=True, null=True)
2016-06-30 01:39:07 +00:00
USERNAME_FIELD = 'pseudo'
REQUIRED_FIELDS = ['name', 'surname', 'email']
objects = UserManager()
@property
def is_active(self):
2017-10-04 15:53:30 +00:00
""" Renvoie si l'user est à l'état actif"""
return self.state == self.STATE_ACTIVE
@property
def is_staff(self):
2017-10-04 15:53:30 +00:00
""" Fonction de base django, renvoie si l'user est admin"""
return self.is_admin
@property
def is_admin(self):
2017-10-04 15:53:30 +00:00
""" Renvoie si l'user est admin"""
try:
Right.objects.get(user=self, right__listright='admin')
except Right.DoesNotExist:
return False
return True
@is_admin.setter
def is_admin(self, value):
2017-10-04 15:53:30 +00:00
""" 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:
self.un_admin()
def get_full_name(self):
2017-10-04 15:53:30 +00:00
""" Renvoie le nom complet de l'user formaté nom/prénom"""
return '%s %s' % (self.name, self.surname)
def get_short_name(self):
2017-10-04 15:53:30 +00:00
""" Renvoie seulement le nom"""
return self.name
2016-07-09 02:12:09 +00:00
def has_perms(self, perms, obj=None):
2017-10-04 15:53:30 +00:00
""" Renvoie true si l'user dispose de la permission.
Prend en argument une liste de permissions.
TODO : Arranger cette fonction"""
2016-07-09 02:12:09 +00:00
for perm in perms:
if perm in RIGHTS_LINK:
query = Q()
for right in RIGHTS_LINK[perm]:
query = query | Q(right__listright=right)
if Right.objects.filter(Q(user=self) & query):
return True
2016-07-09 02:12:09 +00:00
try:
Right.objects.get(user=self, right__listright=perm)
except Right.DoesNotExist:
return False
return True
def has_perm(self, perm, obj=None):
return True
2017-05-27 13:12:21 +00:00
def has_right(self, right):
2017-10-04 15:53:30 +00:00
""" Renvoie si un user a un right donné. Crée le right si il n'existe pas"""
2017-07-02 21:52:43 +00:00
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()
2017-05-27 13:12:21 +00:00
@cached_property
def is_bureau(self):
2017-10-04 15:53:30 +00:00
""" True si user a les droits bureau """
2017-07-02 21:52:43 +00:00
return self.has_right('bureau')
2017-05-27 13:12:21 +00:00
@cached_property
def is_bofh(self):
2017-10-04 15:53:30 +00:00
""" True si l'user a les droits bofh"""
2017-07-02 21:52:43 +00:00
return self.has_right('bofh')
2017-05-27 13:12:21 +00:00
@cached_property
def is_cableur(self):
2017-10-04 15:53:30 +00:00
""" True si l'user a les droits cableur
(également true si bureau, infra ou bofh)"""
2017-05-27 13:12:21 +00:00
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):
2017-10-04 15:53:30 +00:00
""" Renvoie true si droits trésorier pour l'user"""
return self.has_right('tresorier')
2017-05-27 13:12:21 +00:00
@cached_property
def is_infra(self):
2017-10-04 15:53:30 +00:00
""" True si a les droits infra"""
2017-07-02 21:52:43 +00:00
return self.has_right('infra')
2017-05-27 13:12:21 +00:00
def end_adhesion(self):
2017-10-04 15:53:30 +00:00
""" 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']
return date_max
def is_adherent(self):
2017-10-04 15:53:30 +00:00
""" Renvoie True si l'user est adhérent : si self.end_adhesion()>now"""
2017-07-18 01:49:36 +00:00
end = self.end_adhesion()
if not end:
return False
elif end < timezone.now():
return False
else:
return True
@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']
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']
return date_max
@cached_property
def is_ban(self):
""" Renvoie si un user est banni ou non """
end = self.end_ban
if not end:
return False
elif end < timezone.now():
return False
else:
return True
@cached_property
def is_whitelisted(self):
""" Renvoie si un user est whitelisté ou non """
end = self.end_whitelist
if not end:
return False
elif end < timezone.now():
return False
else:
return True
def has_access(self):
""" Renvoie si un utilisateur a accès à internet """
return self.state == User.STATE_ACTIVE \
2017-07-18 01:49:36 +00:00
and not self.is_ban and (self.is_adherent() or self.is_whitelisted)
def end_access(self):
""" Renvoie la date de fin normale d'accès (adhésion ou whiteliste)"""
2017-07-18 01:49:36 +00:00
if not self.end_adhesion():
if not self.end_whitelist:
return None
else:
return self.end_whitelist
else:
if not self.end_whitelist:
2017-07-18 01:49:36 +00:00
return self.end_adhesion()
else:
2017-07-18 01:49:36 +00:00
return max(self.end_adhesion(), self.end_whitelist)
2017-06-26 17:23:01 +00:00
@cached_property
def solde(self):
2017-10-04 15:53:30 +00:00
""" 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"""
2017-06-26 17:23:01 +00:00
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
return somme_credit - somme_debit
else:
return 0
2017-10-04 15:53:30 +00:00
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')
def assign_ips(self):
""" Assign une ipv4 aux machines d'un user """
interfaces = self.user_interfaces()
for interface in interfaces:
if not interface.ipv4:
with transaction.atomic(), reversion.create_revision():
interface.assign_ipv4()
reversion.set_comment("Assignation ipv4")
interface.save()
def unassign_ips(self):
2017-10-04 15:53:30 +00:00
""" Désassigne les ipv4 aux machines de l'user"""
interfaces = self.user_interfaces()
for interface in interfaces:
with transaction.atomic(), reversion.create_revision():
interface.unassign_ipv4()
reversion.set_comment("Désassignation ipv4")
interface.save()
def archive(self):
2017-10-04 15:53:30 +00:00
""" Archive l'user : appelle unassign_ips() puis passe state à ARCHIVE"""
self.unassign_ips()
self.state = User.STATE_ARCHIVE
def unarchive(self):
2017-10-04 15:53:30 +00:00
""" 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
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 ldap_sync(self, base=True, access_refresh=True, mac_refresh=True):
2017-10-04 15:53:30 +00:00
""" 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"""
2016-07-31 01:36:54 +00:00
self.refresh_from_db()
try:
2016-10-11 15:11:46 +00:00
user_ldap = LdapUser.objects.get(uidNumber=self.uid_number)
except LdapUser.DoesNotExist:
2016-10-11 15:11:46 +00:00
user_ldap = LdapUser(uidNumber=self.uid_number)
if base:
2016-07-31 03:03:07 +00:00
user_ldap.name = self.pseudo
user_ldap.sn = self.pseudo
2017-07-18 01:49:36 +00:00
user_ldap.dialupAccess = str(self.has_access())
user_ldap.home_directory = '/home/' + self.pseudo
user_ldap.mail = self.email
2017-09-27 21:41:31 +00:00
user_ldap.given_name = self.surname.lower() + '_' + self.name.lower()[:3]
user_ldap.gid = LDAP['user_gid']
2016-11-20 15:53:59 +00:00
user_ldap.user_password = self.password[:6] + self.password[7:]
2016-11-21 17:32:53 +00:00
user_ldap.sambat_nt_password = self.pwd_ntlm.upper()
2016-07-31 01:36:54 +00:00
if self.shell:
2017-06-26 20:23:58 +00:00
user_ldap.login_shell = self.shell.shell
if self.state == self.STATE_DISABLED:
user_ldap.shadowexpire = str(0)
else:
user_ldap.shadowexpire = None
if access_refresh:
2017-07-18 01:49:36 +00:00
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.save()
def ldap_del(self):
2017-10-04 15:53:30 +00:00
""" Supprime la version ldap de l'user"""
try:
user_ldap = LdapUser.objects.get(name=self.pseudo)
user_ldap.delete()
except LdapUser.DoesNotExist:
pass
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({
'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,
})
send_mail('Bienvenue au %(name)s / Welcome to %(name)s' % {'name': assooptions.name }, '',
general_options.email_from, [self.email], html_message=t.render(c))
return
def reset_passwd_mail(self, request):
""" 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 = {
'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})),
'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)
return
2017-09-14 16:15:14 +00:00
def autoregister_machine(self, mac_address, nas_type):
2017-10-04 15:53:30 +00:00
""" 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()
2017-10-04 15:53:30 +00:00
if all_interfaces.count() > options.max_lambdauser_interfaces:
return False, "Maximum de machines enregistrees atteinte"
2017-09-14 16:15:14 +00:00
if not nas_type:
return False, "Re2o ne sait pas à quel machinetype affecter cette machine"
2017-09-14 16:15:14 +00:00
machine_type_cible = nas_type.machine_type
try:
machine_parent = Machine()
machine_parent.user = self
interface_cible = Interface()
interface_cible.mac_address = mac_address
interface_cible.type = machine_type_cible
interface_cible.clean()
machine_parent.clean()
domain = Domain()
domain.name = self.get_next_domain_name()
domain.interface_parent = interface_cible
domain.clean()
2017-09-11 01:37:01 +00:00
machine_parent.save()
interface_cible.machine = machine_parent
interface_cible.save()
domain.interface_parent = interface_cible
domain.clean()
domain.save()
except Exception as e:
return False, e
return True, "Ok"
def set_user_password(self, password):
2017-10-04 15:53:30 +00:00
""" 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)
return
def get_next_domain_name(self):
"""Look for an available name for a new interface for
this user by trying "pseudo0", "pseudo1", "pseudo2", ...
"""
def simple_pseudo():
return self.pseudo.replace('_', '-').lower()
def composed_pseudo( n ):
return simple_pseudo() + str(n)
num = 0
while Domain.objects.filter(name=composed_pseudo(num)) :
num += 1
return composed_pseudo(num)
def __str__(self):
return self.pseudo
2016-06-30 01:39:07 +00:00
2016-11-20 15:53:59 +00:00
@receiver(post_save, sender=User)
def user_post_save(sender, **kwargs):
2017-10-04 15:53:30 +00:00
""" Synchronisation post_save : envoie le mail de bienvenue si creation
Synchronise le ldap"""
is_created = kwargs['created']
2016-11-20 15:53:59 +00:00
user = kwargs['instance']
if is_created:
user.notif_inscription()
2016-11-20 15:53:59 +00:00
user.ldap_sync(base=True, access_refresh=True, mac_refresh=False)
2017-09-14 18:03:28 +00:00
regen('mailing')
@receiver(post_delete, sender=User)
def user_post_delete(sender, **kwargs):
user = kwargs['instance']
2016-11-20 15:53:59 +00:00
user.ldap_del()
2017-09-14 18:03:28 +00:00
regen('mailing')
2016-07-31 01:36:54 +00:00
class ServiceUser(AbstractBaseUser):
2017-10-04 15:53:30 +00:00
""" Classe des users daemons, règle leurs accès au ldap"""
2017-06-18 12:59:53 +00:00
readonly = 'readonly'
ACCESS = (
('auth', 'auth'),
('readonly', 'readonly'),
('usermgmt', 'usermgmt'),
)
PRETTY_NAME = "Utilisateurs de service"
2016-07-31 01:36:54 +00:00
pseudo = models.CharField(max_length=32, unique=True, help_text="Doit contenir uniquement des lettres, chiffres, ou tirets", validators=[linux_user_validator])
2017-06-18 12:59:53 +00:00
access_group = models.CharField(choices=ACCESS, default=readonly, max_length=32)
comment = models.CharField(help_text="Commentaire", max_length=255, blank=True)
2016-07-31 01:36:54 +00:00
USERNAME_FIELD = 'pseudo'
objects = UserManager()
def ldap_sync(self):
2017-10-04 15:53:30 +00:00
""" Synchronisation du ServiceUser dans sa version ldap"""
2016-07-31 01:36:54 +00:00
try:
user_ldap = LdapServiceUser.objects.get(name=self.pseudo)
except LdapServiceUser.DoesNotExist:
user_ldap = LdapServiceUser(name=self.pseudo)
2017-06-18 12:59:53 +00:00
user_ldap.user_password = self.password[:6] + self.password[7:]
2016-07-31 01:36:54 +00:00
user_ldap.save()
2017-06-18 12:59:53 +00:00
self.serviceuser_group_sync()
2016-07-31 01:36:54 +00:00
def ldap_del(self):
try:
user_ldap = LdapServiceUser.objects.get(name=self.pseudo)
user_ldap.delete()
except LdapUser.DoesNotExist:
pass
2017-06-18 12:59:53 +00:00
self.serviceuser_group_sync()
def serviceuser_group_sync(self):
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))
2017-06-18 12:59:53 +00:00
group.save()
2016-07-31 01:36:54 +00:00
def __str__(self):
return self.pseudo
@receiver(post_save, sender=ServiceUser)
def service_user_post_save(sender, **kwargs):
2017-10-04 15:53:30 +00:00
""" Synchronise un service user ldap après modification django"""
2016-07-31 01:36:54 +00:00
service_user = kwargs['instance']
2017-06-18 12:59:53 +00:00
service_user.ldap_sync()
2016-07-31 01:36:54 +00:00
@receiver(post_delete, sender=ServiceUser)
def service_user_post_delete(sender, **kwargs):
2017-10-04 15:53:30 +00:00
""" Supprime un service user ldap après suppression django"""
2016-07-31 01:36:54 +00:00
service_user = kwargs['instance']
2017-06-18 12:59:53 +00:00
service_user.ldap_del()
2016-07-31 01:36:54 +00:00
class Right(models.Model):
2017-10-04 15:53:30 +00:00
""" 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"
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)
@receiver(post_save, sender=Right)
def right_post_save(sender, **kwargs):
2017-10-04 15:53:30 +00:00
""" Synchronise les users ldap groups avec les groupes de droits"""
right = kwargs['instance'].right
2016-11-20 15:53:59 +00:00
right.ldap_sync()
@receiver(post_delete, sender=Right)
def right_post_delete(sender, **kwargs):
2017-10-04 15:53:30 +00:00
""" Supprime l'user du groupe"""
right = kwargs['instance'].right
2016-11-20 15:53:59 +00:00
right.ldap_sync()
2016-06-30 01:39:07 +00:00
class School(models.Model):
2017-10-04 15:53:30 +00:00
""" Etablissement d'enseignement"""
PRETTY_NAME = "Etablissements enregistrés"
2016-06-30 01:39:07 +00:00
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class ListRight(models.Model):
2017-10-04 15:53:30 +00:00
""" 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 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")])
gid = models.IntegerField(unique=True, null=True)
2016-11-21 00:49:45 +00:00
details = models.CharField(help_text="Description", max_length=255, blank=True)
def __str__(self):
return self.listright
def ldap_sync(self):
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.save()
def ldap_del(self):
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):
2017-10-04 15:53:30 +00:00
""" Synchronise le droit ldap quand il est modifié"""
right = kwargs['instance']
2016-11-20 15:53:59 +00:00
right.ldap_sync()
@receiver(post_delete, sender=ListRight)
def listright_post_delete(sender, **kwargs):
right = kwargs['instance']
2016-11-20 15:53:59 +00:00
right.ldap_del()
class ListShell(models.Model):
PRETTY_NAME = "Liste des shells disponibles"
shell = models.CharField(max_length=255, unique=True)
def __str__(self):
return self.shell
2016-07-02 19:57:31 +00:00
class Ban(models.Model):
2017-10-04 15:53:30 +00:00
""" Bannissement. Actuellement a un effet tout ou rien.
Gagnerait à être granulaire"""
PRETTY_NAME = "Liste des bannissements"
2017-03-06 01:28:16 +00:00
STATE_HARD = 0
STATE_SOFT = 1
STATE_BRIDAGE = 2
STATES = (
(0, 'HARD (aucun accès)'),
(1, 'SOFT (accès local seulement)'),
(2, 'BRIDAGE (bridage du débit)'),
)
2016-07-02 19:57:31 +00:00
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')
2017-03-06 01:28:16 +00:00
state = models.IntegerField(choices=STATES, default=STATE_HARD)
2016-07-02 19:57:31 +00:00
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({
'name': str(self.user.name) + ' ' + str(self.user.surname),
'raison': self.raison,
'date_end': self.date_end,
'asso_name' : options.name,
})
send_mail('Deconnexion disciplinaire', t.render(c),
general_options.email_from, [self.user.email], fail_silently=False)
return
2017-09-19 02:45:33 +00:00
def is_active(self):
return self.date_end > now
2016-07-02 19:57:31 +00:00
def __str__(self):
return str(self.user) + ' ' + str(self.raison)
2016-11-20 15:53:59 +00:00
@receiver(post_save, sender=Ban)
def ban_post_save(sender, **kwargs):
2017-10-04 15:53:30 +00:00
""" Regeneration de tous les services après modification d'un ban"""
2016-11-20 15:53:59 +00:00
ban = kwargs['instance']
is_created = kwargs['created']
2016-11-20 15:53:59 +00:00
user = ban.user
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
2017-09-14 18:03:28 +00:00
regen('mailing')
if is_created:
ban.notif_ban()
regen('dhcp')
regen('mac_ip_list')
if user.has_access():
regen('dhcp')
regen('mac_ip_list')
2016-11-20 15:53:59 +00:00
@receiver(post_delete, sender=Ban)
def ban_post_delete(sender, **kwargs):
2017-10-04 15:53:30 +00:00
""" Regen de tous les services après suppression d'un ban"""
2016-11-20 15:53:59 +00:00
user = kwargs['instance'].user
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
2017-09-14 18:03:28 +00:00
regen('mailing')
regen('dhcp')
regen('mac_ip_list')
2016-07-04 18:04:11 +00:00
class Whitelist(models.Model):
PRETTY_NAME = "Liste des accès gracieux"
2016-07-04 18:04:11 +00:00
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')
2016-07-04 18:04:11 +00:00
2017-09-19 02:45:33 +00:00
def is_active(self):
return self.date_end > now
2016-07-04 18:04:11 +00:00
def __str__(self):
return str(self.user) + ' ' + str(self.raison)
2016-11-20 15:53:59 +00:00
@receiver(post_save, sender=Whitelist)
def whitelist_post_save(sender, **kwargs):
whitelist = kwargs['instance']
user = whitelist.user
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
is_created = kwargs['created']
2017-09-14 18:03:28 +00:00
regen('mailing')
if is_created:
regen('dhcp')
regen('mac_ip_list')
if user.has_access():
regen('dhcp')
regen('mac_ip_list')
2016-11-20 15:53:59 +00:00
@receiver(post_delete, sender=Whitelist)
def whitelist_post_delete(sender, **kwargs):
user = kwargs['instance'].user
user.ldap_sync(base=False, access_refresh=True, mac_refresh=False)
2017-09-14 18:03:28 +00:00
regen('mailing')
regen('dhcp')
regen('mac_ip_list')
2016-11-20 15:53:59 +00:00
class Request(models.Model):
2017-10-04 15:53:30 +00:00
""" Objet request, générant une url unique de validation.
Utilisé par exemple pour la generation du mot de passe et
sa réinitialisation"""
PASSWD = 'PW'
EMAIL = 'EM'
TYPE_CHOICES = (
(PASSWD, 'Mot de passe'),
(EMAIL, 'Email'),
)
type = models.CharField(max_length=2, choices=TYPE_CHOICES)
token = models.CharField(max_length=32)
user = models.ForeignKey('User', on_delete=models.PROTECT)
created_at = models.DateTimeField(auto_now_add=True, editable=False)
expires_at = models.DateTimeField()
def save(self):
if not self.expires_at:
options, created = GeneralOption.objects.get_or_create()
self.expires_at = timezone.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']
2017-06-26 20:23:58 +00:00
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)
uid = ldapdb.models.fields.CharField(db_column='uid', max_length=200)
uidNumber = ldapdb.models.fields.IntegerField(db_column='uidNumber', unique=True)
sn = ldapdb.models.fields.CharField(db_column='sn', max_length=200)
2016-07-31 03:03:07 +00:00
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)
2017-06-26 20:23:58 +00:00
shadowexpire = ldapdb.models.fields.CharField(db_column='shadowExpire', blank=True, null=True)
def __str__(self):
return self.name
def __unicode__(self):
return self.name
def save(self, *args, **kwargs):
self.sn = self.name
self.uid = self.name
self.sambaSID = self.uidNumber
super(LdapUser, self).save(*args, **kwargs)
class LdapUserGroup(ldapdb.models.Model):
"""
Class for representing an LDAP user entry.
"""
# LDAP meta-data
base_dn = LDAP['base_usergroup_dn']
object_classes = ['posixGroup']
# 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)
def __str__(self):
return self.name
2016-07-31 01:36:54 +00:00
class LdapServiceUser(ldapdb.models.Model):
"""
Class for representing an LDAP userservice entry.
"""
# LDAP meta-data
base_dn = LDAP['base_userservice_dn']
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)
2017-06-18 12:59:53 +00:00
def __str__(self):
return self.name
class LdapServiceUserGroup(ldapdb.models.Model):
"""
Class for representing an LDAP userservice entry.
"""
# 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)
def __str__(self):
return self.name