8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-04 17:06:27 +00:00
re2o/users/models.py

1274 lines
40 KiB
Python
Raw Normal View History

# -*- mode: python; coding: utf-8 -*-
2017-10-14 18:18:12 +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.
2017-01-15 23:01:18 +00:00
#
# 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.
2017-10-14 18:18:12 +00:00
"""
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.
"""
2017-01-15 23:01:18 +00:00
from __future__ import unicode_literals
2017-10-14 18:18:12 +00:00
import re
import uuid
import datetime
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
2017-10-14 18:18:12 +00:00
from django.template import Context, loader
from django.core.mail import send_mail
from django.core.urlresolvers import reverse
2017-10-14 18:18:12 +00:00
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
import ldapdb.models
import ldapdb.models.fields
2017-10-14 18:18:12 +00:00
from re2o.settings import RIGHTS_LINK, LDAP, GID_RANGES, UID_RANGES
from re2o.login import hashNT
2016-06-30 01:39:07 +00:00
2017-06-26 17:23:01 +00:00
from cotisations.models import Cotisation, Facture, Paiement, Vente
2017-10-14 18:18:12 +00:00
from machines.models import Domain, Interface, Machine, regen
from preferences.models import GeneralOption, AssoOption, OptionalUser
from preferences.models import OptionalMachine, MailMessageOption
2017-10-14 18:18:12 +00:00
DT_NOW = timezone.now()
2017-10-04 15:53:30 +00:00
2017-10-14 18:18:12 +00:00
# Utilitaires généraux
2017-10-04 15:53:30 +00:00
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-14 18:18:12 +00:00
""" Retourne une erreur de validation si le login ne respecte
2017-10-04 15:53:30 +00:00
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(
2017-10-14 18:18:12 +00:00
", ce pseudo ('%(label)s') contient des carractères interdits",
params={'label': login},
2016-07-06 00:56:30 +00:00
)
2017-10-14 18:18:12 +00:00
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 """
2017-10-14 18:18:12 +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 = []
2017-10-14 18:18:12 +00:00
free_uids = [id for id in uids if id not in used_uids]
2016-10-12 10:24:01 +00:00
return min(free_uids)
2017-10-14 18:18:12 +00:00
2016-10-12 10:24:01 +00:00
def get_fresh_gid():
2017-10-04 15:53:30 +00:00
""" Renvoie le plus petit gid libre """
2017-10-14 18:18:12 +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))
2017-10-14 18:18:12 +00:00
free_gids = [id for id in gids if id not in used_gids]
2016-10-12 10:24:01 +00:00
return min(free_gids)
2017-10-14 18:18:12 +00:00
def get_admin_right():
2017-10-14 18:18:12 +00:00
""" Renvoie l'instance droit admin. La crée si elle n'existe pas
2017-10-04 15:53:30 +00:00
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
class UserManager(BaseUserManager):
2017-10-14 18:18:12 +00:00
"""User manager basique de django"""
def _create_user(
self,
pseudo,
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,
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, surname, email, password=None):
"""
Creates and saves a User with the given pseudo, name, surname, email,
and password.
"""
return self._create_user(pseudo, surname, email, password, False)
def create_superuser(self, pseudo, surname, email, password):
"""
Creates and saves a superuser with the given pseudo, name, surname,
email, and password.
"""
return self._create_user(pseudo, 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 = (
2017-10-14 18:18:12 +00:00
(0, 'STATE_ACTIVE'),
(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():
2017-10-14 18:18:12 +00:00
"""Renvoie un uid libre"""
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
surname = models.CharField(max_length=255)
2017-10-14 18:18:12 +00:00
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()
2017-10-14 18:18:12 +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
)
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)
uid_number = models.PositiveIntegerField(default=auto_uid, unique=True)
rezo_rez_uid = models.PositiveIntegerField(unique=True, blank=True, null=True)
2016-06-30 01:39:07 +00:00
USERNAME_FIELD = 'pseudo'
REQUIRED_FIELDS = ['surname', 'email']
objects = UserManager()
@cached_property
def name(self):
"""Si il s'agit d'un adhérent, on renvoie le prénom"""
if self.is_class_adherent:
return self.adherent.name
else:
return ''
@cached_property
def room(self):
"""Alias vers room """
if self.is_class_adherent:
return self.adherent.room
elif self.is_class_club:
return self.club.room
else:
raise NotImplementedError("Type inconnu")
@cached_property
def class_name(self):
"""Renvoie si il s'agit d'un adhérent ou d'un club"""
if hasattr(self, 'adherent'):
return "Adhérent"
elif hasattr(self, 'club'):
return "Club"
else:
raise NotImplementedError("Type inconnu")
@cached_property
def is_class_club(self):
return hasattr(self, 'club')
@cached_property
def is_class_adherent(self):
return hasattr(self, 'adherent')
@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-14 18:18:12 +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"""
name = self.name
if name:
return '%s %s' % (name, self.surname)
else:
return self.surname
def get_short_name(self):
2017-10-04 15:53:30 +00:00
""" Renvoie seulement le nom"""
return self.surname
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):
2017-10-14 18:18:12 +00:00
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):
2017-10-14 18:18:12 +00:00
"""Ne sert à rien"""
return True
2017-05-27 13:12:21 +00:00
def has_right(self, right):
2017-10-14 18:18:12 +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()
2017-10-14 18:18:12 +00:00
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-14 18:18:12 +00:00
""" True si l'user a les droits cableur
2017-10-04 15:53:30 +00:00
(également true si bureau, infra ou bofh)"""
2017-10-14 18:18:12 +00:00
return self.has_right('cableur') or self.has_right('bureau') or\
self.has_right('infra') or self.has_right('bofh')
2017-05-27 13:12:21 +00:00
@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"""
2017-10-14 18:18:12 +00:00
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-14 18:18:12 +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
2017-10-14 18:18:12 +00:00
elif end < DT_NOW:
return False
else:
return True
@cached_property
def end_ban(self):
""" Renvoie la date de fin de ban d'un user, False sinon """
2017-10-14 18:18:12 +00:00
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 """
2017-10-14 18:18:12 +00:00
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
2017-10-14 18:18:12 +00:00
elif end < DT_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
2017-10-14 18:18:12 +00:00
elif end < DT_NOW:
return False
else:
return True
def has_access(self):
""" Renvoie si un utilisateur a accès à internet """
2017-10-14 18:18:12 +00:00
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()
2017-10-14 18:18:12 +00:00
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-14 18:18:12 +00:00
""" Renvoie le solde d'un user. Vérifie que l'option solde est
activé, retourne 0 sinon.
2017-10-04 15:53:30 +00:00
Somme les crédits de solde et retire les débit payés par solde"""
2017-10-14 18:18:12 +00:00
options, _created = OptionalUser.objects.get_or_create()
2017-06-26 17:23:01 +00:00
user_solde = options.user_solde
if user_solde:
2017-10-14 18:18:12 +00:00
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
2017-06-26 17:23:01 +00:00
return somme_credit - somme_debit
else:
return 0
2017-10-04 15:53:30 +00:00
def user_interfaces(self, active=True):
2017-10-14 18:18:12 +00:00
""" 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-14 18:18:12 +00:00
""" Archive l'user : appelle unassign_ips() puis passe state à
ARCHIVE"""
self.unassign_ips()
2017-10-14 18:18:12 +00:00
self.state = User.STATE_ARCHIVE
def unarchive(self):
2017-10-14 18:18:12 +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):
2017-10-14 18:18:12 +00:00
"""True, a toutes les permissions de module"""
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):
2017-10-14 18:18:12 +00:00
"""Supprime les droits admin d'un user"""
try:
2017-10-14 18:18:12 +00:00
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-14 18:18:12 +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
2017-10-04 15:53:30 +00:00
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-10-14 18:18:12 +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 = [str(mac) for mac in Interface.objects.filter(
machine__user=self
).values_list('mac_address', flat=True).distinct()]
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 """
2017-10-14 18:18:12 +00:00
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': self.get_full_name(),
'asso_name': assooptions.name,
'asso_email': assooptions.contact,
2017-10-14 18:18:12 +00:00
'welcome_mail_fr': mailmessageoptions.welcome_mail_fr,
'welcome_mail_en': mailmessageoptions.welcome_mail_en,
'pseudo': self.pseudo,
})
2017-10-14 18:18:12 +00:00
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):
2017-10-14 18:18:12 +00:00
""" 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()
2017-10-14 18:18:12 +00:00
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': req.user.get_full_name(),
'asso': options.name,
'asso_mail': options.contact,
'site_name': general_options.site_name,
'url': request.build_absolute_uri(
2017-10-14 18:18:12 +00:00
reverse('users:process', kwargs={'token': req.token})),
'expire_in': str(general_options.req_expire_hrs) + ' heures',
}
2017-10-14 18:18:12 +00:00
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
2017-09-14 16:15:14 +00:00
def autoregister_machine(self, mac_address, nas_type):
2017-10-14 18:18:12 +00:00
""" Fonction appellée par freeradius. Enregistre la mac pour
une machine inconnue sur le compte de l'user"""
2017-10-04 15:53:30 +00:00
all_interfaces = self.user_interfaces(active=False)
2017-10-14 18:18:12 +00:00
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:
2017-10-14 18:18:12 +00:00
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()
2017-10-25 23:59:05 +00:00
self.notif_auto_newmachine(interface_cible)
2017-10-14 18:18:12 +00:00
except Exception as error:
return False, error
return True, "Ok"
2017-10-25 23:59:05 +00:00
def notif_auto_newmachine(self, interface):
"""Notification mail lorsque une machine est automatiquement
ajoutée par le radius"""
template = loader.get_template('users/email_auto_newmachine')
assooptions, _created = AssoOption.objects.get_or_create()
general_options, _created = GeneralOption.objects.get_or_create()
context = Context({
'nom': self.get_full_name(),
'mac_address' : interface.mac_address,
'asso_name': assooptions.name,
'interface_name' : interface.domain,
'asso_email': assooptions.contact,
'pseudo': self.pseudo,
})
send_mail(
"Ajout automatique d'une machine / New machine autoregistered",
'',
general_options.email_from,
[self.email],
html_message=template.render(context)
)
return
def set_user_password(self, password):
2017-10-14 18:18:12 +00:00
""" A utiliser de préférence, set le password en hash courrant et
2017-10-04 15:53:30 +00:00
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", ...
2017-10-14 18:18:12 +00:00
Recherche un nom disponible, pour une machine. Doit-être
unique, concatène le nom, le pseudo et le numero de machine
"""
def simple_pseudo():
2017-10-14 18:18:12 +00:00
"""Renvoie le pseudo sans underscore (compat dns)"""
return self.pseudo.replace('_', '-').lower()
2017-10-14 18:18:12 +00:00
def composed_pseudo(name):
"""Renvoie le resultat de simplepseudo et rajoute le nom"""
return simple_pseudo() + str(name)
num = 0
2017-10-14 18:18:12 +00:00
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
2017-10-14 18:18:12 +00:00
class Adherent(User):
name = models.CharField(max_length=255)
room = models.OneToOneField(
'topologie.Room',
on_delete=models.PROTECT,
blank=True,
null=True
)
pass
class Club(User):
room = models.ForeignKey(
'topologie.Room',
on_delete=models.PROTECT,
blank=True,
null=True
)
pass
2017-10-26 22:37:16 +00:00
@receiver(post_save, sender=Adherent)
@receiver(post_save, sender=Club)
2016-11-20 15:53:59 +00:00
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')
2017-10-14 18:18:12 +00:00
2017-10-26 22:37:16 +00:00
@receiver(post_delete, sender=Adherent)
@receiver(post_delete, sender=Club)
def user_post_delete(sender, **kwargs):
2017-10-14 18:18:12 +00:00
"""Post delete d'un user, on supprime son instance ldap"""
user = kwargs['instance']
2016-11-20 15:53:59 +00:00
user.ldap_del()
2017-09-14 18:03:28 +00:00
regen('mailing')
2017-10-14 18:18:12 +00:00
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 = (
2017-10-14 18:18:12 +00:00
('auth', 'auth'),
('readonly', 'readonly'),
('usermgmt', 'usermgmt'),
)
2017-06-18 12:59:53 +00:00
PRETTY_NAME = "Utilisateurs de service"
2016-07-31 01:36:54 +00:00
2017-10-14 18:18:12 +00:00
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
)
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):
2017-10-14 18:18:12 +00:00
"""Suppression de l'instance ldap d'un service user"""
2016-07-31 01:36:54 +00:00
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):
2017-10-14 18:18:12 +00:00
"""Synchronise le groupe et les droits de groupe dans le ldap"""
2017-06-18 12:59:53 +00:00
try:
group = LdapServiceUserGroup.objects.get(name=self.access_group)
except:
group = LdapServiceUserGroup(name=self.access_group)
2017-10-14 18:18:12 +00:00
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
2017-10-14 18:18:12 +00:00
2016-07-31 01:36:54 +00:00
@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
2017-10-14 18:18:12 +00:00
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
2017-10-14 18:18:12 +00:00
class Right(models.Model):
2017-10-14 18:18:12 +00:00
""" Couple droit/user. Peut-être aurait-on mieux fait ici d'utiliser un
manytomany
2017-10-04 15:53:30 +00:00
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)
2017-10-14 18:18:12 +00:00
@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()
2017-10-14 18:18:12 +00:00
@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()
2017-10-14 18:18:12 +00:00
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-14 18:18:12 +00:00
""" Ensemble des droits existants. Chaque droit crée un groupe
ldap synchronisé, avec gid.
2017-10-04 15:53:30 +00:00
Permet de gérer facilement les accès serveurs et autres
2017-10-14 18:18:12 +00:00
La clef de recherche est le gid, pour cette raison
il n'est plus modifiable après creation"""
PRETTY_NAME = "Liste des droits existants"
2017-10-14 18:18:12 +00:00
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.PositiveIntegerField(unique=True, null=True)
2017-10-14 18:18:12 +00:00
details = models.CharField(
help_text="Description",
max_length=255,
blank=True
)
def __str__(self):
return self.listright
def ldap_sync(self):
2017-10-14 18:18:12 +00:00
"""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
2017-10-14 18:18:12 +00:00
group_ldap.members = [right.user.pseudo for right
in Right.objects.filter(right=self)]
group_ldap.save()
def ldap_del(self):
2017-10-14 18:18:12 +00:00
"""Supprime un groupe ldap"""
try:
group_ldap = LdapUserGroup.objects.get(gid=self.gid)
group_ldap.delete()
except LdapUserGroup.DoesNotExist:
pass
2017-10-14 18:18:12 +00:00
@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()
2017-10-14 18:18:12 +00:00
@receiver(post_delete, sender=ListRight)
def listright_post_delete(sender, **kwargs):
2017-10-14 18:18:12 +00:00
"""Suppression d'un groupe ldap après suppression coté django"""
right = kwargs['instance']
2016-11-20 15:53:59 +00:00
right.ldap_del()
2017-10-14 18:18:12 +00:00
class ListShell(models.Model):
2017-10-14 18:18:12 +00:00
"""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)
def __str__(self):
return self.shell
2017-10-14 18:18:12 +00:00
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 = (
2017-10-14 18:18:12 +00:00
(0, 'HARD (aucun accès)'),
(1, 'SOFT (accès local seulement)'),
(2, 'BRIDAGE (bridage du débit)'),
)
2017-03-06 01:28:16 +00:00
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-10-14 18:18:12 +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 """
2017-10-14 18:18:12 +00:00
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': self.user.get_full_name(),
'raison': self.raison,
'date_end': self.date_end,
2017-10-14 18:18:12 +00:00
'asso_name': options.name,
})
2017-10-14 18:18:12 +00:00
send_mail(
'Deconnexion disciplinaire',
template.render(context),
general_options.email_from,
[self.user.email],
fail_silently=False
)
return
2017-09-19 02:45:33 +00:00
def is_active(self):
2017-10-14 18:18:12 +00:00
"""Ce ban est-il actif?"""
return self.date_end > DT_NOW
2017-09-19 02:45:33 +00:00
2016-07-02 19:57:31 +00:00
def __str__(self):
return str(self.user) + ' ' + str(self.raison)
2017-10-14 18:18:12 +00:00
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
2017-10-14 18:18:12 +00:00
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')
2017-10-14 18:18:12 +00:00
2016-07-04 18:04:11 +00:00
class Whitelist(models.Model):
2017-10-14 18:18:12 +00:00
"""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"
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):
2017-10-14 18:18:12 +00:00
return self.date_end > DT_NOW
2017-09-19 02:45:33 +00:00
2016-07-04 18:04:11 +00:00
def __str__(self):
return str(self.user) + ' ' + str(self.raison)
2017-10-14 18:18:12 +00:00
2016-11-20 15:53:59 +00:00
@receiver(post_save, sender=Whitelist)
def whitelist_post_save(sender, **kwargs):
2017-10-14 18:18:12 +00:00
"""Après modification d'une whitelist, on synchronise les services
et on lui permet d'avoir internet"""
2016-11-20 15:53:59 +00:00
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
2017-10-14 18:18:12 +00:00
2016-11-20 15:53:59 +00:00
@receiver(post_delete, sender=Whitelist)
def whitelist_post_delete(sender, **kwargs):
2017-10-14 18:18:12 +00:00
"""Après suppression d'une whitelist, on supprime l'accès internet
en forçant la régénration"""
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-11-20 15:53:59 +00:00
2017-10-14 18:18:12 +00:00
class Request(models.Model):
2017-10-04 15:53:30 +00:00
""" Objet request, générant une url unique de validation.
2017-10-14 18:18:12 +00:00
Utilisé par exemple pour la generation du mot de passe et
2017-10-04 15:53:30 +00:00
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:
2017-10-14 18:18:12 +00:00
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()
2017-10-14 18:18:12 +00:00
class LdapUser(ldapdb.models.Model):
"""
Class for representing an LDAP user entry.
"""
# LDAP meta-data
base_dn = LDAP['base_user_dn']
2017-10-14 18:18:12 +00:00
object_classes = ['inetOrgPerson', 'top', 'posixAccount',
'sambaSamAccount', 'radiusprofile',
'shadowAccount']
# attributes
gid = ldapdb.models.fields.IntegerField(db_column='gidNumber')
2017-10-14 18:18:12 +00:00
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)
2017-10-14 18:18:12 +00:00
uidNumber = ldapdb.models.fields.IntegerField(
db_column='uidNumber',
unique=True
)
sn = ldapdb.models.fields.CharField(db_column='sn', max_length=200)
2017-10-14 18:18:12 +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')
2017-10-14 18:18:12 +00:00
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
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)
2017-10-14 18:18:12 +00:00
class LdapUserGroup(ldapdb.models.Model):
"""
2017-10-14 18:18:12 +00:00
Class for representing an LDAP group entry.
Un groupe ldap
"""
# 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)
2017-10-14 18:18:12 +00:00
name = ldapdb.models.fields.CharField(
db_column='cn',
max_length=200,
primary_key=True
)
def __str__(self):
return self.name
2017-10-14 18:18:12 +00:00
2016-07-31 01:36:54 +00:00
class LdapServiceUser(ldapdb.models.Model):
"""
Class for representing an LDAP userservice entry.
2017-10-14 18:18:12 +00:00
Un user de service coté ldap
2016-07-31 01:36:54 +00:00
"""
# LDAP meta-data
base_dn = LDAP['base_userservice_dn']
2017-10-14 18:18:12 +00:00
object_classes = ['applicationProcess', 'simpleSecurityObject']
2016-07-31 01:36:54 +00:00
# attributes
2017-10-14 18:18:12 +00:00
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
)
2016-07-31 01:36:54 +00:00
2017-06-18 12:59:53 +00:00
def __str__(self):
return self.name
2017-10-14 18:18:12 +00:00
2017-06-18 12:59:53 +00:00
class LdapServiceUserGroup(ldapdb.models.Model):
"""
Class for representing an LDAP userservice entry.
2017-10-14 18:18:12 +00:00
Un group user de service coté ldap. Dans userservicegroupdn
(voir dans settings_local.py)
2017-06-18 12:59:53 +00:00
"""
# LDAP meta-data
base_dn = LDAP['base_userservicegroup_dn']
object_classes = ['groupOfNames']
# attributes
2017-10-14 18:18:12 +00:00
name = ldapdb.models.fields.CharField(
db_column='cn',
max_length=200,
primary_key=True
)
members = ldapdb.models.fields.ListField(
db_column='member',
blank=True
)
2017-06-18 12:59:53 +00:00
def __str__(self):
return self.name