
* Déjà parce que quand on va lire depuis la base ldap l'objet, on va régénérer des frais à chaque fois. * Ensuite parce qu'on ne doit pouvoir faire ce calcul qu'une et une seule fois, une bonne fois pour toute. Idéalement, un paiement via paypal doit être bloqué (par recuPaiement) dès qu'il est validé. À terme, supprimer la notion de frais semble être une bonne idée.
1732 lines
52 KiB
Python
1732 lines
52 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
|
|
""" Définition des classes permettant d'instancier les attributs LDAP. """
|
|
|
|
#
|
|
# Copyright (C) 2010-2013 Cr@ns <roots@crans.org>
|
|
# Authors: Antoine Durand-Gasselin <adg@crans.org>
|
|
# Nicolas Dandrimont <olasd@crans.org>
|
|
# Valentin Samir <samir@crans.org>
|
|
# Vincent Le Gallic <legallic@crans.org>
|
|
# Pierre-Elliott Bécue <becue@crans.org>
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
# * Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
# * Neither the name of the Cr@ns nor the names of its contributors may
|
|
# be used to endorse or promote products derived from this software
|
|
# without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT
|
|
# HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import re
|
|
import sys
|
|
import ssl
|
|
import netaddr
|
|
import time
|
|
import base64
|
|
import datetime
|
|
import functools
|
|
import smtplib
|
|
import random
|
|
import string
|
|
from unicodedata import normalize
|
|
from crans_utils import format_tel, format_mac, mailexist, validate_name, ip4_of_rid, ip6_of_mac, fetch_cert_info
|
|
import itertools
|
|
|
|
sys.path.append("/usr/scripts")
|
|
import cranslib.deprecated
|
|
from gestion import config
|
|
from gestion import annuaires_pg
|
|
|
|
#: Serveur SMTP
|
|
smtpserv = "smtp.crans.org"
|
|
|
|
### Les droits
|
|
# en cas de typo, l'appel d'une variable plante, on préfèrera donc les utiliser en lieu et place
|
|
# des chaînes de caractères
|
|
#: Chaine de caractère des droits nounou
|
|
nounou = u"Nounou"
|
|
#: Droit cableur
|
|
cableur = u"Cableur"
|
|
#: Droit apprenti
|
|
apprenti = u"Apprenti"
|
|
#: Droit trésorier
|
|
tresorier = u"Tresorier"
|
|
#: Droit bureau
|
|
bureau = u"Bureau"
|
|
#: Droit imprimeur
|
|
imprimeur = u"Imprimeur"
|
|
#: Droit modérateur
|
|
moderateur = u"Moderateur"
|
|
#: Droit multimachine
|
|
multimachines = u"Multimachines"
|
|
#: Droit Webmaster
|
|
webmaster = u"Webmaster"
|
|
#: Droit Webradio
|
|
webradio = u"Webradio"
|
|
#: On peut faire subir des choses à un objet si on est son parent
|
|
parent = u"parent"
|
|
#: On peut faire subir des choses à un objet si on est cet objet
|
|
soi = u"soi"
|
|
#: Le responsable d'un club peut lui faire subir des choses
|
|
respo = u"responsable"
|
|
|
|
#: Liste de tous les droits
|
|
TOUS_DROITS = [nounou, apprenti, bureau, tresorier, imprimeur, moderateur, multimachines, cableur, webmaster]
|
|
#: Liste des droits forts
|
|
DROITS_ELEVES = [nounou, bureau, tresorier]
|
|
#: Liste des droits intérmédiaires
|
|
DROITS_MOYEN = [apprenti, moderateur]
|
|
#: Liste des droits moins sensibles
|
|
DROITS_FAIBLES = [cableur, imprimeur, multimachines]
|
|
|
|
#: Qui a le droit de modifier quels droits
|
|
DROITS_SUPERVISEUR = { nounou : TOUS_DROITS,
|
|
bureau : DROITS_FAIBLES + [bureau, tresorier],
|
|
}
|
|
|
|
class SingleValueError(ValueError):
|
|
"""Erreur levée si on essaye de multivaluer un champ monovalué."""
|
|
pass
|
|
|
|
class UniquenessError(EnvironmentError):
|
|
"""Erreur levée si on essaye de créer un objet dont le ``dn`` existe déjà."""
|
|
pass
|
|
|
|
class OptionalError(EnvironmentError):
|
|
"""Erreur levée si on essaye de créer un objet sans fournir un attribut obligatoire."""
|
|
pass
|
|
|
|
|
|
def attrify(val, attr, conn, Parent=None):
|
|
"""Transforme un n'importe quoi en :py:class:`Attr`.
|
|
|
|
Doit effectuer les normalisations et sanity check si un str ou un
|
|
unicode est passé en argument.
|
|
Devrait insulter en cas de potentiel problème d'encodage.
|
|
"""
|
|
if isinstance(val, Attr):
|
|
return val
|
|
else:
|
|
attr_classe = AttributeFactory.get(attr, fallback=Attr)
|
|
if not isinstance(val, unicode) and not (attr_classe.python_type and isinstance(val, attr_classe.python_type)):
|
|
cranslib.deprecated.usage("attrify ne devrait être appelé qu'avec des unicode (%r)" % val, level=3)
|
|
val = val.decode('utf-8')
|
|
return attr_classe(val, conn, Parent)
|
|
|
|
class AttrsList(list):
|
|
def __init__(self, Parent, attr, attr_list):
|
|
super(AttrsList, self).__init__(attr_list)
|
|
self._parent = Parent
|
|
self._attr = attr
|
|
|
|
def _start(self):
|
|
if self._parent[self._attr] != self:
|
|
raise ValueError("La variable que vous utilisez n'est pas à jour (modifications conccurentes ?)")
|
|
|
|
def _commit(self):
|
|
try:
|
|
self._parent[self._attr] = self
|
|
finally:
|
|
super(AttrsList, self).__init__(self._parent[self._attr])
|
|
|
|
def __delitem__(self, index):
|
|
self._start()
|
|
super(AttrsList, self).__delitem__(index)
|
|
self._commit()
|
|
|
|
def __setitem__(self, index, value):
|
|
self._start()
|
|
super(AttrsList, self).__setitem__(index, value)
|
|
self._commit()
|
|
|
|
def append(self, val):
|
|
self._start()
|
|
super(AttrsList, self).append(val)
|
|
self._commit()
|
|
|
|
def extend(self, vals):
|
|
self._start()
|
|
super(AttrsList, self).extend(vals)
|
|
self._commit()
|
|
|
|
def insert(self, pos, val):
|
|
self._start()
|
|
super(AttrsList, self).insert(pos, val)
|
|
self._commit()
|
|
|
|
def remove(self, val):
|
|
self._start()
|
|
super(AttrsList, self).remove(val)
|
|
self._commit()
|
|
|
|
def sort(self):
|
|
self._start()
|
|
super(AttrsList, self).sort()
|
|
self._commit()
|
|
|
|
def pop(self, index=None):
|
|
self._start()
|
|
ret = super(AttrsList, self).pop(index) if index else super(AttrsList, self).pop()
|
|
self._commit()
|
|
return ret
|
|
|
|
def reverse(self):
|
|
self._start()
|
|
super(AttrsList, self).reverse()
|
|
self._commit()
|
|
|
|
class AttrsDict(dict):
|
|
def __init__(self, conn, uldif={}, Parent=None):
|
|
super(AttrsDict, self).__init__(uldif)
|
|
self._conn = conn
|
|
self._parent = Parent
|
|
self._iterator = None
|
|
|
|
def __getitem__(self, attr):
|
|
values = super(AttrsDict, self).__getitem__(attr)
|
|
if not isinstance(values, list):
|
|
values = [values]
|
|
output = []
|
|
for val in values:
|
|
output.append(attrify(val, attr, self._conn, self._parent))
|
|
self[attr] = output
|
|
return output
|
|
|
|
def __setitem__(self, attr, values):
|
|
# ne devrait par arriver
|
|
if not isinstance(values, list):
|
|
values = [ values ]
|
|
if self._parent.mode in ['w', 'rw']:
|
|
if AttributeFactory.get(attr, fallback=Attr).singlevalue and len(values) > 1:
|
|
raise SingleValueError("L'attribut %s doit être monovalué." % (attr,))
|
|
super(AttrsDict, self).__setitem__(attr, values)
|
|
else:
|
|
super(AttrsDict, self).__setitem__(attr, values)
|
|
|
|
def get(self, value, default_value):
|
|
try:
|
|
return self[value]
|
|
except KeyError:
|
|
return default_value
|
|
|
|
def items(self):
|
|
return [(key, self[key]) for key in self]
|
|
|
|
def to_ldif(self):
|
|
"""
|
|
Transforme le dico en ldif valide au sens openldap.
|
|
Ce ldif est celui qui sera transmis à la base.
|
|
"""
|
|
ldif = {}
|
|
for attr, vals in self.items():
|
|
ldif[attr] = [ str(val) for val in vals ]
|
|
return ldif
|
|
|
|
class Attr(object):
|
|
"""Objet représentant un attribut.
|
|
|
|
**Paramètres :**
|
|
|
|
* ``val`` : valeur de l'attribut
|
|
* ``ldif`` : objet contenant l'attribut (permet de faire les validations sur l'environnement)
|
|
"""
|
|
legend = "Human-readable description of attribute"
|
|
singlevalue = False
|
|
optional = True
|
|
conn = None
|
|
unique = False
|
|
unique_exclue = []
|
|
#: Le nom de l'attribut dans le schéma LDAP
|
|
ldap_name = None
|
|
python_type = None
|
|
binary = False
|
|
default = None
|
|
|
|
"""La liste des droits qui suffisent à avoir le droit de modifier la valeur"""
|
|
can_modify = [nounou]
|
|
|
|
"""Qui peut voir l'attribut. Par défaut, les Nounous et les Apprentis
|
|
peuvent tout voir. Par transparence, et par utilité, on autorise par
|
|
défaut l'adhérent à voir les données le concernant."""
|
|
can_view = [nounou, apprenti, soi, parent, respo]
|
|
|
|
"""Catégorie de l'attribut (pour affichage futur)"""
|
|
category = 'other'
|
|
|
|
def __init__(self, val, conn, Parent):
|
|
"""Crée un nouvel objet représentant un attribut."""
|
|
self.value = None
|
|
self.conn = conn
|
|
assert isinstance(val, unicode) or (self.python_type and isinstance(val, self.python_type))
|
|
self.parent = Parent
|
|
self.parse_value(val)
|
|
|
|
def __hash__(self):
|
|
if not self.parent or self.parent.mode in ['w', 'rw']:
|
|
raise TypeError("Mutable structure are not hashable, please use mode = 'ro' to do so")
|
|
if hasattr(self.value, "__hash__") and self.value.__hash__ is not None:
|
|
return self.value.__hash__()
|
|
else:
|
|
return str(self).__hash__()
|
|
|
|
def __getattr__(self, name):
|
|
return getattr(self.value, name)
|
|
|
|
def __ne__(self, obj):
|
|
return not self == obj
|
|
|
|
def __eq__(self, item):
|
|
if isinstance(item, self.__class__):
|
|
return str(self) == str(item)
|
|
elif isinstance(item, Attr) and item.python_type == self.python_type:
|
|
return item.value == self.value
|
|
elif self.python_type and isinstance(item, self.python_type):
|
|
return self.value == item
|
|
elif isinstance(item, str):
|
|
return str(self) == item
|
|
elif isinstance(item, unicode):
|
|
return unicode(self) == item
|
|
else:
|
|
return False
|
|
|
|
def __nonzero__(self):
|
|
if self.value:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def parse_value(self, val):
|
|
"""Transforme l'attribut pour travailler avec notre validateur
|
|
Le ldif est en dépendance car à certains endroits, il peut servir
|
|
(par exemple, pour l'ipv6, ou l'ipv4…"""
|
|
self.value = val
|
|
|
|
def __str__(self):
|
|
return self.__unicode__().encode('utf-8')
|
|
|
|
def __repr__(self):
|
|
return str(self.__class__) + " : " + repr(self.value)
|
|
|
|
def __unicode__(self):
|
|
if isinstance(self.value, unicode):
|
|
return self.value
|
|
else:
|
|
return unicode(self.value)
|
|
|
|
def check_uniqueness(self, liste_exclue):
|
|
"""Vérifie l'unicité dans la base de la valeur (``mailAlias``, ``chbre``,
|
|
etc...)"""
|
|
attr = self.__class__.__name__
|
|
if unicode(self) in liste_exclue + self.unique_exclue:
|
|
return
|
|
if self.unique:
|
|
res = self.conn.search(u'%s=%s' % (attr, str(self)))
|
|
if res:
|
|
raise UniquenessError("%s déjà existant" % attr, [r.dn for r in res])
|
|
|
|
def is_modifiable(self, liste_droits):
|
|
"""
|
|
L'attribut est-il modifiable par un des droits dans liste_droits ?
|
|
"""
|
|
return not set(liste_droits).isdisjoint(self.can_modify)
|
|
|
|
|
|
class AttributeFactory(object):
|
|
"""Utilisée pour enregistrer toutes les classes servant à instancier un attribut LDAP.
|
|
Elle sert à les récupérer à partir de leur nom LDAP.
|
|
|
|
Cette classe n'est jamais instanciée.
|
|
|
|
"""
|
|
_classes = {}
|
|
|
|
@classmethod
|
|
def register(cls, name, classe):
|
|
"""Enregistre l'association ``name`` -> ``classe``"""
|
|
cls._classes[name] = classe
|
|
|
|
@classmethod
|
|
def get(cls, name, fallback=Attr):
|
|
"""Retourne la classe qui a ``name`` pour ``ldap_name``.
|
|
|
|
Si elle n'existe pas, renvoie :py:class:`Attr` (peut être override en précisant ``fallback``)
|
|
|
|
"""
|
|
return cls._classes.get(name, fallback)
|
|
|
|
def crans_attribute(classe):
|
|
"""Pour décorer les classes permettant d'instancier des attributs LDAP,
|
|
afin de les enregistrer dans :py:class:`AttributeFactory`.
|
|
|
|
"""
|
|
AttributeFactory.register(classe.ldap_name, classe)
|
|
return classe
|
|
|
|
@crans_attribute
|
|
class objectClass(Attr):
|
|
singlevalue = False
|
|
optional = False
|
|
legend = "entité"
|
|
ldap_name = "objectClass"
|
|
|
|
""" Personne ne doit modifier de classe """
|
|
can_modify = []
|
|
|
|
""" Internal purpose (et à fin pédagogique) """
|
|
can_view = [nounou, apprenti]
|
|
|
|
def parse_value(self, val):
|
|
if val not in [ 'top', 'organizationalUnit', 'posixAccount', 'shadowAccount',
|
|
'proprio', 'adherent', 'club', 'machine', 'machineCrans',
|
|
'borneWifi', 'machineWifi', 'machineFixe', 'x509Cert', 'TLSACert',
|
|
'baseCert', 'cransAccount', 'service', 'facture', 'freeMid', 'privateKey' ]:
|
|
raise ValueError("Pourquoi insérer un objectClass=%r ?" % val)
|
|
else:
|
|
self.value = unicode(val)
|
|
|
|
|
|
class intAttr(Attr):
|
|
|
|
python_type = int
|
|
|
|
def parse_value(self, val):
|
|
if self.python_type(val) < 0:
|
|
raise ValueError("Valeur entière invalide : %r" % val)
|
|
self.value = self.python_type(val)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value)
|
|
|
|
class boolAttr(Attr):
|
|
|
|
python_type = bool
|
|
|
|
def parse_value(self, val):
|
|
if isinstance(val, self.python_type):
|
|
self.value = val
|
|
elif val.lower() in [u'true', u'ok']:
|
|
self.value = True
|
|
elif val.lower() == u'false':
|
|
self.value = False
|
|
else:
|
|
raise ValueError("%r doit être un booléen !" % val)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value).upper()
|
|
|
|
@crans_attribute
|
|
class aid(intAttr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = u"Identifiant de l'adhérent"
|
|
category = 'id'
|
|
unique = True
|
|
ldap_name = "aid"
|
|
|
|
""" Personne ne devrait modifier un attribut d'identification """
|
|
can_modify = []
|
|
|
|
can_view = [nounou, apprenti, cableur]
|
|
|
|
def parse_value(self, aid):
|
|
self.value = int(aid)
|
|
|
|
@crans_attribute
|
|
class uid(Attr):
|
|
singlevalue = True
|
|
option = False
|
|
legend = u"L'identifiant canonique de l'adhérent"
|
|
category = 'perso'
|
|
unique = True
|
|
ldap_name = "uid"
|
|
|
|
@crans_attribute
|
|
class preferedLanguage(Attr):
|
|
singlevalue = True
|
|
option = True
|
|
legend = u"La langue préférée de l'adhérent"
|
|
category = 'perso'
|
|
unique = False
|
|
ldap_name = "preferedLanguage"
|
|
|
|
@crans_attribute
|
|
class nom(Attr):
|
|
singlevalue = True
|
|
optional = False
|
|
legend = "Nom"
|
|
category = 'perso'
|
|
can_modify = [nounou, cableur]
|
|
ldap_name = "nom"
|
|
|
|
def parse_value(self, nom):
|
|
if self.parent != None:
|
|
if u'club' in [o.value for o in self.parent['objectClass']]:
|
|
self.value = validate_name(nom,"0123456789\[\]")
|
|
else:
|
|
self.value = validate_name(nom)
|
|
else:
|
|
self.value = validate_name(nom)
|
|
|
|
@crans_attribute
|
|
class prenom(Attr):
|
|
singlevalue = True
|
|
optional = False
|
|
legend = u"Prénom"
|
|
category = 'perso'
|
|
can_modify = [nounou, cableur]
|
|
ldap_name = "prenom"
|
|
|
|
def parse_value(self, prenom):
|
|
self.value = validate_name(prenom)
|
|
|
|
@crans_attribute
|
|
class compteWiki(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
legend = u"Compte WiKi"
|
|
category = 'perso'
|
|
can_modify = [nounou, cableur, soi]
|
|
ldap_name = "compteWiki"
|
|
|
|
def parse_value(self, compte):
|
|
self.value = validate_name(compte)
|
|
# TODO: validate with mdp for user definition here ?
|
|
|
|
@crans_attribute
|
|
class tel(Attr):
|
|
singlevalue = True
|
|
optional = False
|
|
legend = u"Téléphone"
|
|
category = 'perso'
|
|
can_modify = [soi, nounou, cableur]
|
|
ldap_name = "tel"
|
|
|
|
def parse_value(self, tel):
|
|
self.value = format_tel(tel)
|
|
if len(self.value) == 0:
|
|
raise ValueError("Numéro de téléphone invalide (%r)" % tel)
|
|
|
|
class yearAttr(intAttr):
|
|
singlevalue = False
|
|
optional = True
|
|
|
|
def parse_value(self, annee):
|
|
annee = self.python_type(annee)
|
|
if annee < 1998:
|
|
raise ValueError("Année invalide (%r)" % annee)
|
|
self.value = annee
|
|
|
|
@crans_attribute
|
|
class paiement(yearAttr):
|
|
legend = u"Paiement"
|
|
can_modify = [cableur, nounou, tresorier]
|
|
category = 'perso'
|
|
ldap_name = "paiement"
|
|
|
|
@crans_attribute
|
|
class carteEtudiant(Attr):
|
|
legend = u"Carte d'étudiant"
|
|
category = 'perso'
|
|
can_modify = [cableur, nounou, tresorier]
|
|
ldap_name = "carteEtudiant"
|
|
|
|
def parse_value(self, data):
|
|
self.value = data
|
|
|
|
@crans_attribute
|
|
class derniereConnexion(intAttr):
|
|
legend = u"Dernière connexion"
|
|
can_modify = [nounou, cableur, soi] # il faut bien pouvoir le modifier pour le mettre à jour
|
|
ldap_name = "derniereConnexion"
|
|
|
|
@crans_attribute
|
|
class mail(Attr):
|
|
singlevalue = False
|
|
optional = False
|
|
unique = True
|
|
legend = "Adresse mail de l'adhérent"
|
|
can_modify = [soi, nounou, cableur]
|
|
category = 'mail'
|
|
ldap_name = "mail"
|
|
|
|
def is_modifiable(self, liste_droits):
|
|
"""
|
|
Une adresse mail n'est modifiable que si on a au moins autant de droits
|
|
que la personne à qui est l'adresse mail
|
|
"""
|
|
modifiables = set()
|
|
for i in liste_droits:
|
|
if i in DROITS_SUPERVISEUR:
|
|
modifiables = modifiables.union(DROITS_SUPERVISEUR[i])
|
|
modifiables = list(modifiables)
|
|
|
|
for droit in self.parent.get('droits', []):
|
|
if droit not in modifiables and droit in TOUS_DROITS:
|
|
return False
|
|
return super(mail, self).is_modifiable(liste_droits)
|
|
|
|
|
|
def check_uniqueness(self, liste_exclue):
|
|
attr = self.__class__.__name__
|
|
if str(self) in liste_exclue:
|
|
return
|
|
if attr in ["mailAlias", "canonicalAlias", 'mail']:
|
|
mail, end = str(self).split('@', 1)
|
|
if end.startswith('crans'):
|
|
try:
|
|
smtp = smtplib.SMTP(smtpserv)
|
|
smtp.putcmd("vrfy", mail)
|
|
res = smtp.getreply()[0] in [250, 252]
|
|
smtp.close()
|
|
except:
|
|
raise ValueError('Serveur de mail injoignable')
|
|
|
|
if res:
|
|
raise ValueError("Le mail %s est déjà pris." % (str(self)))
|
|
else:
|
|
check = self.conn.search(u'mail=%s' % mail)
|
|
if len(check) >= 1:
|
|
raise ValueError("Le mail %s est déjà pris." % (str(self)))
|
|
|
|
def parse_value(self, mail):
|
|
if not re.match(u'^[-_.0-9A-Za-z]+@([A-Za-z0-9]{1}[A-Za-z0-9-_]+[.])+[a-z]{2,6}$', mail):
|
|
raise ValueError("%s invalide %r" % (self.legend, mail))
|
|
self.value = mail
|
|
|
|
|
|
@crans_attribute
|
|
class canonicalAlias(mail):
|
|
singlevalue = True
|
|
optional = True
|
|
unique = True
|
|
legend = u"Alias mail canonique"
|
|
category = 'mail'
|
|
ldap_name = "canonicalAlias"
|
|
|
|
def parse_value(self, mail):
|
|
mail = u".".join([ a.capitalize() for a in mail.split(u'.', 1) ])
|
|
super(canonicalAlias, self).parse_value(mail)
|
|
|
|
@crans_attribute
|
|
class mailAlias(mail):
|
|
singlevalue = False
|
|
optional = True
|
|
unique = True
|
|
legend = u"Alias mail"
|
|
can_modify = [soi, cableur, nounou]
|
|
category = 'mail'
|
|
ldap_name = "mailAlias"
|
|
|
|
def parse_value(self, mail):
|
|
mail = mail.lower()
|
|
super(mailAlias, self).parse_value(mail)
|
|
|
|
@crans_attribute
|
|
class mailExt(mail):
|
|
singlevalue = False
|
|
optional = True
|
|
unique = True
|
|
legend = u"Mail de secours"
|
|
can_modify = [soi, cableur, nounou]
|
|
category = 'mail'
|
|
ldap_name = "mailExt"
|
|
|
|
def parse_value(self, mail):
|
|
mail = mail.lower()
|
|
# comme on utilise mailExt comme mail de secours si l'utilisateur
|
|
# à perdu ses id crans, ça ne sert à rien de mettre ne adresse crans
|
|
if mail.endswith("@crans.org"):
|
|
raise ValueError("%s ne devrait pas être une adresse crans." % str(self.legend))
|
|
super(mailExt, self).parse_value(mail)
|
|
|
|
@crans_attribute
|
|
class mailInvalide(boolAttr):
|
|
optional = True
|
|
legend = u"Mail invalide"
|
|
can_modify = [bureau, nounou]
|
|
ldap_name = "mailInvalide"
|
|
|
|
@crans_attribute
|
|
class contourneGreylist(boolAttr):
|
|
optionnal = True
|
|
legend = u"Contourner la greylist"
|
|
category = 'mail'
|
|
can_modify = [soi]
|
|
ldap_name = "contourneGreylist"
|
|
|
|
def __unicode__(self):
|
|
return u"OK"
|
|
|
|
@crans_attribute
|
|
class etudes(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
legend = u"Études"
|
|
can_modify = [soi, cableur, nounou]
|
|
category = 'perso'
|
|
ldap_name = "etudes"
|
|
|
|
@crans_attribute
|
|
class chbre(Attr):
|
|
singlevalue = True
|
|
optional = False
|
|
unique = True
|
|
unique_exclue = [u'????', u'EXT']
|
|
legend = u"Chambre sur le campus"
|
|
can_modify = [soi, cableur, nounou]
|
|
category = 'perso'
|
|
ldap_name = "chbre"
|
|
|
|
def parse_value(self, chambre):
|
|
if self.parent != None and u'club' in [str(o) for o in self.parent['objectClass']]:
|
|
if chambre in annuaires_pg.locaux_clubs():
|
|
self.value = chambre
|
|
else:
|
|
raise ValueError("Club devrait etre en XclN, pas en %r" % chambre)
|
|
elif chambre in (u"EXT", u"????"):
|
|
self.value = chambre
|
|
else:
|
|
try:
|
|
annuaires_pg.chbre_prises(chambre[0], chambre[1:])
|
|
except NameError:
|
|
import annuaires_pg_test
|
|
annuaires_pg_test.chbre_prises(chambre[0], chambre[1:])
|
|
self.value = chambre
|
|
|
|
@crans_attribute
|
|
class droits(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
legend = u"Droits sur les serveurs"
|
|
can_modify = [nounou, bureau]
|
|
category = 'perso'
|
|
ldap_name = "droits"
|
|
|
|
def parse_value(self, droits):
|
|
# if val.lower() not in [i.lower() for i in TOUS_DROITS]:
|
|
# raise ValueError("Ces droits n'existent pas (%r)" % val)
|
|
self.value = droits.capitalize()
|
|
|
|
def is_modifiable(self, liste_droits):
|
|
"""
|
|
Le droit est-il modifiable par un des droits dans liste_droits ?
|
|
L'idée étant que pour modifier un droit, il faut avoir un droit plus fort
|
|
"""
|
|
modifiables = set()
|
|
for i in liste_droits:
|
|
if i in DROITS_SUPERVISEUR:
|
|
modifiables = modifiables.union(DROITS_SUPERVISEUR[i])
|
|
modifiables = list(modifiables)
|
|
|
|
return self.value in modifiables and super(droits, self).is_modifiable(liste_droits)
|
|
|
|
@crans_attribute
|
|
class solde(Attr):
|
|
python_type = float
|
|
singlevalue = True
|
|
optional = True
|
|
legend = u"Solde d'impression"
|
|
can_modify = [imprimeur, nounou, tresorier]
|
|
ldap_name = "solde"
|
|
|
|
def parse_value(self, solde):
|
|
# on évite les dépassements, sauf si on nous dit de ne pas vérifier
|
|
#if not (float(solde) >= config.impression.decouvert and float(solde) <= 1024.):
|
|
# raise ValueError("Solde invalide: %r" % solde)
|
|
self.value = self.python_type(solde)
|
|
|
|
def __unicode__(self):
|
|
return u"%.2f" % self.value
|
|
|
|
class dnsAttr(Attr):
|
|
category = 'dns'
|
|
ldap_name = "dnsAttr"
|
|
python_type = unicode
|
|
|
|
def parse_value(self, val):
|
|
val = val.lower()
|
|
names = val.split('.')
|
|
for name in names:
|
|
if not re.match('^[a-z0-9](-*[a-z0-9]+)*$', name):
|
|
raise ValueError("Nom d'hote invalide %r" % val)
|
|
self.value = val
|
|
|
|
@crans_attribute
|
|
class host(dnsAttr):
|
|
singlevalue = True
|
|
unique = True
|
|
optional = False
|
|
hname = legend = u"Nom d'hôte"
|
|
can_modify = [parent, nounou, cableur]
|
|
category = 'base_tech'
|
|
ldap_name = "host"
|
|
|
|
def check_uniqueness(self, liste_exclue):
|
|
attr = self.__class__.__name__
|
|
if str(self) in liste_exclue:
|
|
return
|
|
if attr in ["host", "hostAlias"]:
|
|
res = self.conn.search(u'(|(host=%s)(hostAlias=%s))' % ((str(self),)*2))
|
|
if res:
|
|
raise ValueError("Hôte déjà existant", [r.dn for r in res])
|
|
|
|
@crans_attribute
|
|
class hostAlias(host):
|
|
singlevalue = False
|
|
unique = True
|
|
optional = True
|
|
legend = u'Alias de nom de machine'
|
|
can_modify = [nounou, cableur]
|
|
ldap_name = "hostAlias"
|
|
|
|
@crans_attribute
|
|
class macAddress(Attr):
|
|
singlevalue = True
|
|
optional = False
|
|
legend = u"Adresse physique de la carte réseau"
|
|
hname = "Adresse MAC"
|
|
category = 'base_tech'
|
|
can_modify = [parent, nounou, cableur]
|
|
ldap_name = "macAddress"
|
|
default = u'<automatique>'
|
|
|
|
def parse_value(self, mac):
|
|
mac = mac.lower()
|
|
if mac == macAddress.default:
|
|
self.value = mac
|
|
else:
|
|
self.value = format_mac(mac)
|
|
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value).lower()
|
|
|
|
@crans_attribute
|
|
class ipHostNumber(Attr):
|
|
singlevalue = True
|
|
optional = True
|
|
unique = True
|
|
legend = u"Adresse IPv4 de la machine"
|
|
hname = "IPv4"
|
|
category = 'base_tech'
|
|
can_modify = [nounou, cableur]
|
|
ldap_name = "ipHostNumber"
|
|
python_type = netaddr.IPAddress
|
|
default = u'<automatique>'
|
|
|
|
def parse_value(self, ip):
|
|
if ip == '<automatique>':
|
|
ip = ip4_of_rid(str(self.parent['rid'][0]))
|
|
self.value = self.python_type(ip)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value)
|
|
|
|
@crans_attribute
|
|
class ip6HostNumber(Attr):
|
|
singlevalue = True
|
|
optional = True
|
|
unique = True
|
|
legend = u"Adresse IPv6 de la machine"
|
|
hname = "IPv6"
|
|
category = 'base_tech'
|
|
# beware: parent est nécessaire à la modification des adresses macs par
|
|
# les adhérents lambda (see validate_changes)
|
|
can_modify = [nounou, cableur, parent]
|
|
ldap_name = "ip6HostNumber"
|
|
python_type = netaddr.IPAddress
|
|
default = u'<automatique>'
|
|
|
|
def parse_value(self, ip6):
|
|
if ip6 == '<automatique>':
|
|
ip6 = ip6_of_mac(str(self.parent['macAddress'][0]), int(str(self.parent['rid'][0])))
|
|
self.value = self.python_type(ip6)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value)
|
|
|
|
@crans_attribute
|
|
class mid(intAttr):
|
|
singlevalue = True
|
|
optional = False
|
|
unique = True
|
|
legend = u"Identifiant de machine"
|
|
category = 'id'
|
|
ldap_name = "mid"
|
|
|
|
@crans_attribute
|
|
class rid(intAttr):
|
|
singlevalue = True
|
|
optional = True
|
|
unique = True
|
|
legend = u"Identifiant réseau de machine"
|
|
category = 'id'
|
|
can_modify = [nounou]
|
|
ldap_name = "rid"
|
|
|
|
def parse_value(self, rid):
|
|
rid = self.python_type(rid)
|
|
|
|
# On veut éviter les rid qui recoupent les ipv4 finissant par
|
|
# .0 ou .255
|
|
plages = [itertools.chain(*[xrange(plage[0], plage[1]+1) for plage in value]) for (key, value) in config.rid_primaires.iteritems() if ('v6' not in key) and ('special' not in key)]
|
|
|
|
for plage in plages:
|
|
if rid in plage:
|
|
if rid % 256 == 0 or rid % 256 == 255:
|
|
rid = -1
|
|
break
|
|
else:
|
|
continue
|
|
else:
|
|
continue
|
|
self.value = rid
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value)
|
|
|
|
@crans_attribute
|
|
class ipsec(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
legend = u'Clef wifi'
|
|
category = 'wifi'
|
|
ldap_name = "ipsec"
|
|
can_modify = [nounou, parent]
|
|
default = u'auto'
|
|
|
|
def parse_value(self, val):
|
|
if len(val) in [10, 22]:
|
|
self.value = val
|
|
else:
|
|
val = u'auto'
|
|
if val == u"auto":
|
|
self.value = u''.join( random.choice(filter(lambda x: x != 'l' and x != 'o', string.lowercase) + filter(lambda x: x != '1' and x != '0', string.digits)) for i in range(10))
|
|
|
|
@crans_attribute
|
|
class puissance(Attr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = u"puissance d'émission pour les bornes wifi"
|
|
category = 'wifi'
|
|
can_modify = [nounou]
|
|
ldap_name = "puissance"
|
|
|
|
@crans_attribute
|
|
class canal(intAttr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = u'Canal d\'émission de la borne'
|
|
category = 'wifi'
|
|
can_modify = [nounou]
|
|
ldap_name = "canal"
|
|
|
|
@crans_attribute
|
|
class hotspot(boolAttr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = u'Hotspot'
|
|
category = 'wifi'
|
|
can_modify = [nounou]
|
|
ldap_name = "hotspot"
|
|
|
|
@crans_attribute
|
|
class positionBorne(Attr):
|
|
legend = u"Position de la borne"
|
|
category = "wifi"
|
|
can_modify = [nounou]
|
|
singlevalue = True
|
|
optional = True
|
|
ldap_name = "positionBorne"
|
|
|
|
@crans_attribute
|
|
class nvram(Attr):
|
|
legend = u"Configuration speciale"
|
|
optional = True
|
|
can_modify = [nounou]
|
|
ldap_name = "nvram"
|
|
|
|
class portAttr(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
legend = u'Ouverture de port'
|
|
category = 'firewall'
|
|
can_modify = [nounou]
|
|
|
|
def parse_value(self, port):
|
|
if ":" in port:
|
|
a,b = port.split(":", 1)
|
|
if a:
|
|
if int(a) <0 or int(a)> 65535:
|
|
raise ValueError("Port invalide: %r" % port)
|
|
else:
|
|
a = 0
|
|
if b:
|
|
if int(a) <0 or int(a)> 65535:
|
|
raise ValueError("Port invalide: %r" % port)
|
|
else:
|
|
b = 65535
|
|
self.value = [int(a), int(b)]
|
|
else:
|
|
if int(port) <0 or int(port)> 65535:
|
|
raise ValueError("Port invalide: %r" % port)
|
|
self.value = [int(port)]
|
|
|
|
def __unicode__(self):
|
|
if len(self.value) > 1:
|
|
a, b = self.value
|
|
a = '' if a == 0 else str(a)
|
|
b = '' if b == 65535 else str(b)
|
|
return unicode('%s:%s' % (a, b))
|
|
else:
|
|
return unicode(self.value[0])
|
|
|
|
@crans_attribute
|
|
class portTCPout(portAttr):
|
|
legend = u"Port TCP ouvert vers l'extérieur"
|
|
ldap_name = "portTCPout"
|
|
|
|
@crans_attribute
|
|
class portTCPin(portAttr):
|
|
legend = u"Port TCP ouvert depuis l'extérieur"
|
|
ldap_name = "portTCPin"
|
|
|
|
@crans_attribute
|
|
class portUDPout(portAttr):
|
|
legend = u"Port UDP ouvert vers l'extérieur"
|
|
ldap_name = "portUDPout"
|
|
|
|
@crans_attribute
|
|
class portUDPin(portAttr):
|
|
legend = u"Port UDP ouvert depuis l'extérieur"
|
|
ldap_name = "portUDPin"
|
|
|
|
@crans_attribute
|
|
class exempt(Attr):
|
|
legend = u"Exemption vers une IP"
|
|
ldap_name = "exempt"
|
|
python_type = netaddr.IPNetwork
|
|
|
|
def parse_value(self, net):
|
|
self.value = self.python_type(net)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value)
|
|
|
|
|
|
@crans_attribute
|
|
class nombrePrises(intAttr):
|
|
legend = u"Nombre de prises ethernet de la machine"
|
|
singlevalue = True
|
|
optional = True
|
|
categoriy = 'base_tech'
|
|
can_modify = [nounou]
|
|
ldap_name = "nombrePrises"
|
|
|
|
@crans_attribute
|
|
class prise(Attr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = u"Prise sur laquelle est branchée la machine"
|
|
category = 'base_tech'
|
|
can_modify = [nounou]
|
|
ldap_name = "prise"
|
|
|
|
@crans_attribute
|
|
class cid(intAttr):
|
|
singlevalue = True
|
|
optional = True
|
|
unique = True
|
|
legend = u"Identifiant du club"
|
|
category = 'id'
|
|
ldap_name = "cid"
|
|
|
|
def parse_value(self, val):
|
|
self.value = int(val)
|
|
|
|
@crans_attribute
|
|
class responsable(Attr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = u"Responsable du club"
|
|
category = 'perso'
|
|
can_modify = [cableur, nounou]
|
|
ldap_name = "responsable"
|
|
python_type = int # en vrai, c'est l'aid du responsable
|
|
|
|
def nonefunction(self, resp):
|
|
"""
|
|
Just... do... nothing.
|
|
L'idée est qu'on initialise self.value à None
|
|
dans Attr. Simplement ici, self.value est une property,
|
|
donc il faut une fonction pour l'attribution.
|
|
"""
|
|
pass
|
|
|
|
def get_respo(self):
|
|
"""Méthode spéciale, pour aller avec
|
|
property. On génère le respo quand
|
|
c'est nécessaire, pas avant."""
|
|
if hasattr(self, "_value"):
|
|
return self._value
|
|
else:
|
|
try:
|
|
res = self.conn.search(u'aid=%s' % self.__value)[0]
|
|
except IndexError:
|
|
raise ValueError("get_respo: L'adherent %s n'existe pas ou plus" % (self.__value))
|
|
self._value = res
|
|
return res
|
|
|
|
def parse_value(self, resp):
|
|
self.__value = resp
|
|
|
|
value = property(get_respo, nonefunction)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.__value)
|
|
|
|
@crans_attribute
|
|
class imprimeurClub(Attr):
|
|
optional = True
|
|
legend = u"Imprimeur du club"
|
|
category = "perso"
|
|
can_modify = [cableur, nounou]
|
|
ldap_name = "imprimeurClub"
|
|
|
|
def nonefunction(self, imprimeur):
|
|
"""
|
|
Just... do... nothing.
|
|
L'idée est qu'on initialise self.value à None
|
|
dans Attr. Simplement ici, self.value est une property,
|
|
donc il faut une fonction pour l'attribution.
|
|
"""
|
|
pass
|
|
|
|
def get_imprimeur(self):
|
|
if hasattr(self, "_value"):
|
|
return self._value
|
|
else:
|
|
try:
|
|
res = self.conn.search(u'aid=%s' % int(self.__value))[0]
|
|
except IndexError:
|
|
raise ValueError("get_imprimeur: L'adhérent aid=%r n'existe pas ou plus" % self.__value)
|
|
self._value = res
|
|
return res
|
|
|
|
def parse_value(self, imprimeur):
|
|
self.__value = imprimeur
|
|
|
|
value = property(get_imprimeur, nonefunction)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value['aid'][0])
|
|
|
|
@crans_attribute
|
|
class blacklist(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
legend = u"Blackliste"
|
|
category = 'info'
|
|
can_modify = [nounou]
|
|
ldap_name = "blacklist"
|
|
python_type = dict
|
|
|
|
def parse_value(self, blacklist):
|
|
if isinstance(blacklist, self.python_type):
|
|
if set(blacklist.keys()).issuperset(['debut', 'fin', 'type', 'comm']):
|
|
bl_debut, bl_fin, bl_type, bl_comm = (blacklist['debut'], blacklist['fin'], blacklist['type'], blacklist['comm'])
|
|
else:
|
|
raise ValueError("A blacklist should contain the following keys : 'debut', 'fin', 'type', 'comm'")
|
|
else:
|
|
try:
|
|
bl_debut, bl_fin, bl_type, bl_comm = blacklist.split('$')
|
|
except:
|
|
print "valeur = %s" % blacklist
|
|
raise
|
|
|
|
now = time.time()
|
|
self.value = { 'debut' : bl_debut if bl_debut == '-' else int (bl_debut),
|
|
'fin' : bl_fin if bl_fin == '-' else int(bl_fin),
|
|
'type' : bl_type,
|
|
'comm' : bl_comm,
|
|
'actif' : (bl_debut == '-' or int(bl_debut) < now) and (bl_fin == '-' or int(bl_fin) > now) }
|
|
|
|
def __getitem__(self, attr):
|
|
return self.value.__getitem__(attr)
|
|
|
|
def __setitem__(self, attr, values):
|
|
if attr in ['debut', 'fin', 'type', 'comm']:
|
|
ret=self.value.__setitem__(attr, values)
|
|
self.parse_value(unicode(self))
|
|
return ret
|
|
else:
|
|
raise ValueError("blacklist has no %r" % attr)
|
|
|
|
def is_actif(self):
|
|
return self.value['actif']
|
|
|
|
def terminer(self):
|
|
self.value['fin'] = int(max(self.value['debut'], time.time() - 60))
|
|
self.value['actif'] = False
|
|
|
|
def __unicode__(self):
|
|
return u'%(debut)s$%(fin)s$%(type)s$%(comm)s' % self.value
|
|
|
|
@crans_attribute
|
|
class historique(Attr):
|
|
"""Un historique est usuellement de la forme JJ/MM/AAAA HH:mm:ss, action comm"""
|
|
|
|
singlevalue = False
|
|
optional = True
|
|
legend = u"Historique de l'objet"
|
|
category = 'info'
|
|
ldap_name = "historique"
|
|
# Il faudrait faire quelque chose pour que soi, parent, cableur ne puissent que ajouter de nouvelles lignes
|
|
can_modify = [nounou, soi, parent, cableur]
|
|
|
|
# Thanks to 20-100, on va devoir gérer deux cas
|
|
FORMAT = "%d/%m/%Y %H:%M:%S"
|
|
FORMAT_OLD = "%d/%m/%Y %H:%M" # ancien binding
|
|
|
|
def get_datetime(self):
|
|
"""Renvoie un objet datetime de la ligne correspondante"""
|
|
datepart = self.value.split(',',1)[0]
|
|
try:
|
|
return datetime.datetime.strptime(datepart, self.FORMAT)
|
|
except ValueError:
|
|
return datetime.datetime.strptime(datepart, self.FORMAT_OLD)
|
|
|
|
@crans_attribute
|
|
class info(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
legend = u"Quelques informations"
|
|
category = 'info'
|
|
can_modify = [nounou, cableur, bureau]
|
|
ldap_name = "info"
|
|
|
|
@crans_attribute
|
|
class homepageAlias(Attr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = u'Un alias pour la page personnelle'
|
|
can_modify = [nounou, cableur]
|
|
category = 'webpage'
|
|
ldap_name = "homepageAlias"
|
|
|
|
@crans_attribute
|
|
class charteMA(boolAttr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend= "Signale si l'adhérent a signé la charte de membres actifs"
|
|
can_modify = [nounou, bureau]
|
|
category = 'perso'
|
|
ldap_name = "charteMA"
|
|
|
|
@crans_attribute
|
|
class homeDirectory(Attr):
|
|
singlevalue=True
|
|
optional = True
|
|
unique = True
|
|
can_modify = [nounou, bureau, cableur]
|
|
legend="Le chemin du home de l'adhérent"
|
|
ldap_name = "homeDirectory"
|
|
|
|
def is_modifiable(self, liste_droits):
|
|
"""
|
|
Une adresse mail n'est modifiable que si on a au moins autant de droits
|
|
que la personne à qui est l'adresse mail
|
|
"""
|
|
modifiables = set()
|
|
for i in liste_droits:
|
|
if i in DROITS_SUPERVISEUR:
|
|
modifiables = modifiables.union(DROITS_SUPERVISEUR[i])
|
|
modifiables = list(modifiables)
|
|
|
|
for droit in self.parent.get('droits', []):
|
|
if droit not in modifiables:
|
|
return False
|
|
return super(homeDirectory, self).is_modifiable(liste_droits)
|
|
|
|
def parse_value(self, home):
|
|
uid = unicode(self.parent['uid'][0])
|
|
if uid.startswith(u'club-'):
|
|
uid = uid.split('-', 1)[1]
|
|
if home != u'/home/%s' % uid and home != u'/home/club/%s' % uid:
|
|
raise ValueError("Le répertoire personnel n'est pas bon: %r (devrait être %r ou %r)" % (home, '/home/%s' % self.parent['uid'][0], '/home/club/%s' % self.parent['uid'][0]))
|
|
self.value = home
|
|
|
|
@crans_attribute
|
|
class loginShell(Attr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = "Le shell de l'adherent"
|
|
can_modify = [soi, nounou, cableur]
|
|
ldap_name = "loginShell"
|
|
|
|
def parse_value(self, shell):
|
|
#with open('/etc/shells') as f:
|
|
# shells = [ l.strip() for l in f.readlines() if not l.startswith('#') ]
|
|
shells = config.shells_possibles
|
|
if shell not in shells:
|
|
raise ValueError("Shell %r invalide.\nLes shells valident sont : %s" % (shell, ', '.join(shells)))
|
|
self.value = shell
|
|
|
|
@crans_attribute
|
|
class uidNumber(intAttr):
|
|
singlevalue = True
|
|
optional = True
|
|
unique = True
|
|
legend = "L'uid du compte de l'adherent"
|
|
category = 'id'
|
|
ldap_name = "uidNumber"
|
|
|
|
@crans_attribute
|
|
class gidNumber(intAttr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = "Le gid du compte de l'adhérent"
|
|
category = 'id'
|
|
ldap_name = "gidNumber"
|
|
|
|
@crans_attribute
|
|
class gecos(Attr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = "Le gecos"
|
|
category = 'id'
|
|
ldap_name = "gecos"
|
|
|
|
@crans_attribute
|
|
class userPassword(Attr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = "Le mot de passe"
|
|
category = ''
|
|
ldap_name = "userPassword"
|
|
can_modify = [nounou, bureau, cableur]
|
|
|
|
def is_modifiable(self, liste_droits):
|
|
"""
|
|
Le mot de passe n'est modifiable que si on a au moins autant de droits
|
|
que la personne à qui est le mot de passe
|
|
"""
|
|
modifiables = set()
|
|
for i in liste_droits:
|
|
if i in DROITS_SUPERVISEUR:
|
|
modifiables = modifiables.union(DROITS_SUPERVISEUR[i])
|
|
modifiables = list(modifiables)
|
|
|
|
for droit in self.parent.get('droits', []):
|
|
if droit not in modifiables and droit in TOUS_DROITS:
|
|
return False
|
|
return super(userPassword, self).is_modifiable(liste_droits)
|
|
|
|
@crans_attribute
|
|
class sshFingerprint(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
legend = "Clef ssh de la machine"
|
|
can_modify = [parent, nounou]
|
|
ldap_name = "sshFingerprint"
|
|
python_type = dict
|
|
|
|
def parse_value(self, key):
|
|
if isinstance(key, self.python_type):
|
|
if set(key.keys()).issuperset(['type', 'key']):
|
|
details = [ key['type'], key['key'] ]
|
|
if 'comm' in key:
|
|
details.append(key['comm'])
|
|
else:
|
|
raise ValueError("sshFingerprint should have a type dans a key field")
|
|
else:
|
|
details = key.strip().split()
|
|
if len(details)<2:
|
|
raise ValueError("Une clef ssh devrait être de la forme : 'format clef commentaire' comme 'ssh-dss AAAAB3NzaC… root@mon-pc' par exemple")
|
|
self.value = { 'type' : details[0],
|
|
'key' : details[1],
|
|
'comm' : ' '.join(details[2:]),
|
|
}
|
|
if not self.value['type'] in config.sshfs_ralgo.keys():
|
|
raise ValueError("Seul les clefs ssh de type %s sont supportées" % ', '.join(config.sshfs_ralgo.keys()))
|
|
try:
|
|
base64.b64decode(self.value['key'])
|
|
except:
|
|
raise ValueError("La partie centrale de la clef ssh devrait être du base64 valide")
|
|
|
|
def __getitem__(self, attr):
|
|
return self.value.__getitem__(attr)
|
|
|
|
def __setitem__(self, attr, values):
|
|
if attr in ['comm']:
|
|
ret=self.value.__setitem__(attr, values)
|
|
self.parse_value(unicode(self))
|
|
return ret
|
|
else:
|
|
raise ValueError("sshFingerprint has no %r editable" % attr)
|
|
|
|
def __unicode__(self):
|
|
return (u'%(type)s %(key)s %(comm)s' % self.value).strip()
|
|
|
|
@crans_attribute
|
|
class gpgFingerprint(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
unique = True
|
|
legend = "Clef gpg d'un adhérent"
|
|
can_modify = [soi, nounou]
|
|
ldap_name = "gpgFingerprint"
|
|
|
|
@crans_attribute
|
|
class gpgMail(mail):
|
|
"""Attribut servant à stocker un mail allant de paire avec
|
|
l'un des uid dans la clef gpg pointée par gpgFingerprint"""
|
|
singlevalue = False
|
|
optional = True
|
|
unique = True
|
|
legend = "Mail associé à une clef gpg"
|
|
can_modify = [soi, nounou]
|
|
ldap_name = "gpgMail"
|
|
def check_uniqueness(self, liste_exclue):
|
|
super(mail, self).check_uniqueness(liste_exclue)
|
|
|
|
def parse_value(self, mail):
|
|
mail = mail.lower()
|
|
super(gpgMail, self).parse_value(mail)
|
|
|
|
@crans_attribute
|
|
class cn(Attr):
|
|
singlevalue = True
|
|
optional = False
|
|
category = 'id'
|
|
ldap_name = "cn"
|
|
|
|
@crans_attribute
|
|
class dn(Attr):
|
|
singlevalue = True
|
|
optional = False
|
|
unique = True
|
|
category = 'id'
|
|
ldap_name = "dn"
|
|
|
|
@crans_attribute
|
|
class postalAddress(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
can_modify = [soi, cableur, nounou, bureau]
|
|
legend = u"Adresse"
|
|
category = 'perso'
|
|
ldap_name = "postalAddress"
|
|
|
|
@crans_attribute
|
|
class controle(Attr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = u"Contrôle"
|
|
can_modify = [tresorier, nounou]
|
|
category = 'perso'
|
|
ldap_name = "controle"
|
|
|
|
def parse_value(self, ctrl):
|
|
if ctrl not in [u"", u"c", u"p", u"cp", u"pc"]:
|
|
raise ValueError("Contrôle peut prendre les valeurs [c][p]")
|
|
self.value = ctrl
|
|
|
|
@crans_attribute
|
|
class fid(intAttr):
|
|
legend = u"Id de facture"
|
|
category = 'factures'
|
|
optional = False
|
|
singlevalue = True
|
|
ldap_name = "fid"
|
|
|
|
@crans_attribute
|
|
class modePaiement(Attr):
|
|
legend = u"Mode de paiement"
|
|
category = 'factures'
|
|
optional = False
|
|
singlevalue = True
|
|
ldap_name = "modePaiement"
|
|
|
|
def parse_value(self, mode):
|
|
if not mode in config.modePaiement:
|
|
raise ValueError("%s n'est pas un moyen de paiement accepté. Les moyens accepté sont %s" % ", ".join(config.modePaiement))
|
|
self.value = mode
|
|
|
|
@crans_attribute
|
|
class recuPaiement(Attr):
|
|
ldap_name = "recuPaiement"
|
|
|
|
@crans_attribute
|
|
class article(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
legend = u"Articles"
|
|
category = 'facture'
|
|
can_modify = [cableur, nounou, tresorier]
|
|
ldap_name = "article"
|
|
python_type = dict
|
|
|
|
python_type_keys = ['code', 'designation', 'nombre', 'pu']
|
|
|
|
def parse_value(self, article):
|
|
if isinstance(article, self.python_type):
|
|
for key in self.python_type_keys:
|
|
if key not in article:
|
|
raise ValueError("Un article devrait avoir '%s'" % key)
|
|
art_code = article['code']
|
|
art_designation = article['designation']
|
|
art_nombre = article['nombre']
|
|
art_pu = article['pu']
|
|
else:
|
|
art_code, art_designation, art_nombre, art_pu = article.split('~~')
|
|
self.value = { 'code' : art_code, # code de l'article (SOLDE, FRAIS, ...)
|
|
'designation' : art_designation,
|
|
'nombre' : art_nombre,# nombre d'article
|
|
'pu' : art_pu, # prix à l'unité
|
|
}
|
|
|
|
def __unicode__(self):
|
|
return u'%(code)s~~%(designation)s~~%(nombre)s~~%(pu)s' % self.value
|
|
|
|
def __getitem__(self, attr):
|
|
return self.value.__getitem__(attr)
|
|
|
|
def __setitem__(self, attr, values):
|
|
if attr in self.python_type_keys:
|
|
ret = self.value.__setitem__(attr, values)
|
|
self.parse_value(unicode(self))
|
|
return ret
|
|
else:
|
|
raise ValueError("article has no %r" % attr)
|
|
|
|
@crans_attribute
|
|
class dnsIpv6(boolAttr):
|
|
ldap_name = "dnsIpv6"
|
|
legend = "Détermine si l'ipv6 apparait dans le dns"
|
|
can_modify = [nounou, parent, cableur]
|
|
|
|
@crans_attribute
|
|
class rewriteMailHeaders(boolAttr):
|
|
ldap_name = "rewriteMailHeaders"
|
|
|
|
@crans_attribute
|
|
class machineAlias(boolAttr):
|
|
ldap_name = "machineAlias"
|
|
|
|
@crans_attribute
|
|
class issuerCN(Attr):
|
|
ldap_name = "issuerCN"
|
|
can_modify = [nounou]
|
|
legend = "Common Name de l'éméteur du certificat"
|
|
|
|
@crans_attribute
|
|
class serialNumber(Attr):
|
|
ldap_name = "serialNumber"
|
|
python_type = int
|
|
can_modify = [nounou]
|
|
legend = "Numéro de série du certificat"
|
|
|
|
@crans_attribute
|
|
class start(intAttr):
|
|
ldap_name = "start"
|
|
can_modify = [nounou]
|
|
legend = "Date de début"
|
|
|
|
@crans_attribute
|
|
class end(intAttr):
|
|
ldap_name = "end"
|
|
can_modify = [nounou]
|
|
legend = "Date de fin"
|
|
|
|
@crans_attribute
|
|
class crlUrl(Attr):
|
|
ldap_name = "crlUrl"
|
|
optional = True
|
|
can_modify = [parent, nounou]
|
|
legend = "Adresse de la liste de révocation pour le certificat"
|
|
|
|
@crans_attribute
|
|
class revocked(boolAttr):
|
|
ldap_name = "revocked"
|
|
singlevalue = True
|
|
optional = True
|
|
can_modify = [nounou]
|
|
legend = "Détermine si le certificat est révoqué"
|
|
|
|
@crans_attribute
|
|
class encrypted(boolAttr):
|
|
ldap_name = "encrypted"
|
|
singlevalue = True
|
|
optional = True
|
|
can_modify = [nounou, parent]
|
|
legend = "Détermine si une clef privée est chiffrée"
|
|
|
|
@crans_attribute
|
|
class privatekey(Attr):
|
|
ldap_name = "privatekey"
|
|
python_type = str
|
|
can_modify = [parent, nounou]
|
|
legend = "Clef privée"
|
|
|
|
@crans_attribute
|
|
class csr(Attr):
|
|
ldap_name = "csr"
|
|
python_type = str
|
|
can_modify = [parent, nounou]
|
|
legend = "requête de signature de certificat"
|
|
|
|
def _format_cert(self, csr):
|
|
import OpenSSL
|
|
try:
|
|
x509 = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr)
|
|
except:
|
|
print csr
|
|
raise
|
|
data = fetch_cert_info(x509)
|
|
return data
|
|
|
|
def parse_value(self, csr):
|
|
if self.parent.mode in ['w', 'rw']:
|
|
data = self._format_cert(csr)
|
|
self.data = data
|
|
|
|
for hostname in [data['subject']['CN']] + data['extensions'].get('subjectAltName',[]):
|
|
if hostname not in self.parent['hostCert']:
|
|
self.parent['hostCert'].append(unicode(hostname))
|
|
|
|
self.value = csr
|
|
|
|
@crans_attribute
|
|
class certificat(Attr):
|
|
ldap_name = "certificat"
|
|
binary = True
|
|
python_type = str
|
|
can_modify = [parent, nounou]
|
|
legend = "Certificat"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(certificat, self).__init__(*args, **kwargs)
|
|
self.data = None
|
|
|
|
def _format_cert(self, certificat):
|
|
import OpenSSL
|
|
try:
|
|
if certificat.startswith('-----BEGIN CERTIFICATE-----'):
|
|
certificat = ssl.PEM_cert_to_DER_cert(certificat)
|
|
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, certificat)
|
|
except Exception:
|
|
try:
|
|
certificat = base64.b64decode(certificat)
|
|
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, certificat)
|
|
except Exception:
|
|
raise ValueError("Format du certificat invalide, est-il bien au format DER ou PEM ?")
|
|
data = fetch_cert_info(x509)
|
|
return (certificat, data)
|
|
|
|
def parse_value(self, certificat):
|
|
if self.parent.mode in ['w', 'rw']:
|
|
(certificat, data) = self._format_cert(certificat)
|
|
self.data = data
|
|
|
|
if not 'x509Cert' in self.parent['objectClass']:
|
|
self.parent.x509(unicode(data['issuer']['CN']), data['start'], data['end'], data['serialNumber'])
|
|
else:
|
|
self.parent['issuerCN'] = unicode(data['issuer']['CN'])
|
|
self.parent['start'] = data['start']
|
|
self.parent['end'] = data['end']
|
|
self.parent['serialNumber'] = data['serialNumber']
|
|
|
|
for hostname in [data['subject']['CN']] + data['extensions'].get('subjectAltName',[]):
|
|
if hostname not in self.parent['hostCert']:
|
|
self.parent['hostCert'].append(unicode(hostname))
|
|
|
|
self.value = certificat
|
|
|
|
def __getitem__(self, key):
|
|
if not self.data:
|
|
self.data=self._format_cert(self.value)[1]
|
|
return self.data[key]
|
|
|
|
def pem(self):
|
|
return ssl.DER_cert_to_PEM_cert(self.value)
|
|
|
|
def __unicode__(self):
|
|
return unicode(base64.b64encode(self.value))
|
|
def __str__(self):
|
|
return self.value
|
|
|
|
@crans_attribute
|
|
class certificatUsage(intAttr):
|
|
ldap_name = "certificatUsage"
|
|
singlevalue = True
|
|
can_modify = [parent, nounou]
|
|
legend = "Utilisation du certificat pour un enregistrement TLSA"
|
|
|
|
@crans_attribute
|
|
class selector(intAttr):
|
|
ldap_name = "selector"
|
|
singlevalue = True
|
|
can_modify = [parent, nounou]
|
|
legend = "Détermine si le certificat est complet ou contient juste la clef publique pour TLSA"
|
|
|
|
@crans_attribute
|
|
class matchingType(intAttr):
|
|
ldap_name = "matchingType"
|
|
singlevalue = True
|
|
can_modify = [parent, nounou]
|
|
legend = "Détermine ce que l'on veux mettre dans l'enregistrement TLSA (certificat complet ou hash)"
|
|
|
|
@crans_attribute
|
|
class xid(intAttr):
|
|
ldap_name = "xid"
|
|
category = 'id'
|
|
unique = True
|
|
singlevalue = True
|
|
can_modify = []
|
|
legend = "Identifiant du certificat"
|
|
|
|
|
|
@crans_attribute
|
|
class hostCert(dnsAttr):
|
|
optional = False
|
|
can_modify = [parent, nounou]
|
|
ldap_name = "hostCert"
|
|
"Nom de domaine utilisé pour le certificat"
|
|
|
|
def parse_value(self, host):
|
|
# Pour les machine crans, on dit ques les nounous
|
|
# savent ce qu'elle font (et puis, c'est pratique quand des
|
|
# alias migrent vers des vm)
|
|
if not "machineCrans" in self.parent.machine()["objectClass"]:
|
|
if not host in self.parent.machine()['host'] + self.parent.machine()['hostAlias']:
|
|
raise ValueError("hostCert (%s) doit être inclus dans les host et hostAlias de la machine parente : %s ou %s" % (str(host), ', '.join(str(item) for item in self.parent.machine()['host'] + self.parent.machine()['hostAlias'])))
|
|
self.value = host
|
|
|
|
@crans_attribute
|
|
class shadowExpire(intAttr):
|
|
"""
|
|
Durée de validité du mot de passe d'un compte.
|
|
On l'utilise en mettant sa valeur à 0 pour désactiver un compte
|
|
"""
|
|
optinal = True
|
|
can_modify = [nounou, bureau]
|
|
ldap_name = "shadowExpire"
|