On utilise un champ ldap_name pour savoir quelle classe doit être utilisée pour instancier quel objet/attribut LDAP.

Et comme ça on fiche à la poubelle le dégueulasse globals() et on décorrelle les noms
des classes des noms LDAP.
This commit is contained in:
Vincent Le Gallic 2013-05-15 23:00:17 +02:00
parent 294f7ce949
commit c392a2a986
3 changed files with 391 additions and 170 deletions

View file

@ -37,6 +37,10 @@ import re
import sys
import netaddr
import time
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
@ -44,48 +48,67 @@ sys.path.append("/usr/scripts/gestion")
import config
import config.impression
import annuaires_pg
import smtplib
import random
import string
### SMTP
#: 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]
#: 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`.
@ -98,7 +121,7 @@ def attrify(val, attr, conn, Parent=None):
else:
if not isinstance(val, unicode):
val = val.decode('utf-8')
return CRANS_ATTRIBUTES.get(attr, Attr)(val, conn, Parent)
return AttributeFactory.get(attr, fallback=Attr)(val, conn, Parent)
class AttrsDict(dict):
@ -123,8 +146,8 @@ class AttrsDict(dict):
if not isinstance(values, list):
values = [ values ]
if self._parent.mode in ['w', 'rw']:
if CRANS_ATTRIBUTES.get(attr, Attr).singlevalue and len(values) > 1:
raise SingleValueError("L'attribut %s doit être monovalué." % CRANS_ATTRIBUTES.get(attr, Attr).__name__)
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)
@ -151,6 +174,8 @@ class Attr(object):
optional = True
conn = None
unique = False
#: Le nom de l'attribut dans le schéma LDAP
ldap_name = None
"""La liste des droits qui suffisent à avoir le droit de modifier la valeur"""
can_modify = [nounou]
@ -205,10 +230,44 @@ class Attr(object):
"""
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 = []
@ -249,12 +308,14 @@ class boolAttr(Attr):
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 = []
@ -264,19 +325,23 @@ class aid(intAttr):
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 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:
@ -287,33 +352,39 @@ class nom(Attr):
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)
@ -330,20 +401,27 @@ class yearAttr(intAttr):
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(yearAttr):
legend = u"Carte d'étudiant"
category = 'perso'
can_modify = [cableur, nounou, tresorier]
ldap_name = "carteEtudiant"
@crans_attribute
class derniereConnexion(intAttr):
legend = u"Dernière connexion"
can_modify = []
ldap_name = "derniereConnexion"
@crans_attribute
class mail(Attr):
singlevalue = False
optional = False
@ -351,6 +429,7 @@ class mail(Attr):
legend = "Le mail de l'adhérent"
can_modify = [soi, nounou, cableur]
category = 'mail'
ldap_name = "mail"
def check_uniqueness(self, liste_exclue):
attr = self.__class__.__name__
@ -380,12 +459,14 @@ class mail(Attr):
self.value = mail
@crans_attribute
class canonicalAlias(mail):
singlevalue = True
optional = False
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) ])
@ -393,6 +474,7 @@ class canonicalAlias(mail):
raise ValueError("Alias mail invalide (%s)" % mail)
self.value = mail
@crans_attribute
class mailAlias(mail):
singlevalue = False
optional = True
@ -400,6 +482,7 @@ class mailAlias(mail):
legend = u"Alias mail"
can_modify = [soi, cableur, nounou]
category = 'mail'
ldap_name = "mailAlias"
def parse_value(self, mail):
mail = mail.lower()
@ -407,6 +490,7 @@ class mailAlias(mail):
raise ValueError("Alias mail invalide (%r)" % mail)
self.value = mail
@crans_attribute
class mailExt(mail):
singlevalue = False
optional = True
@ -414,6 +498,7 @@ class mailExt(mail):
legend = u"Mail externe"
can_modify = [soi, cableur, nounou]
category = 'mail'
ldap_name = "mailExt"
def parse_value(self, mail):
mail = mail.lower()
@ -421,31 +506,38 @@ class mailExt(mail):
raise ValueError("Mail externe invalide (%r)" % mail)
self.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"
def parse_value(self, etudes):
# who cares
self.value = etudes
@crans_attribute
class chbre(Attr):
singlevalue = True
optional = False
@ -453,6 +545,7 @@ class chbre(Attr):
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:
@ -475,12 +568,14 @@ class chbre(Attr):
self.value = chambre
@crans_attribute
class droits(Attr):
singlevalue = False
optional = True
legend = u"Droits sur les serveurs"
can_modify = [nounou, bureau] #ne sert à rien ici, mais c'est tout à fait exceptionnel
category = 'perso'
ldap_name = "droits"
def parse_value(self, droits):
# if val.lower() not in [i.lower() for i in TOUS_DROITS]:
@ -498,11 +593,13 @@ class droits(Attr):
return self.value in modifiables
@crans_attribute
class solde(Attr):
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
@ -512,6 +609,7 @@ class solde(Attr):
class dnsAttr(Attr):
category = 'dns'
ldap_name = "dnsAttr"
def parse_value(self, val):
val = val.lower()
names = val.split('.')
@ -520,12 +618,14 @@ class dnsAttr(Attr):
raise ValueError("Nom d'hote invalide %r" % val)
self.value = val
@crans_attribute
class host(dnsAttr):
singlevalue = 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__
@ -536,14 +636,16 @@ class host(dnsAttr):
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
@ -551,6 +653,7 @@ class macAddress(Attr):
hname = "Adresse MAC"
category = 'base_tech'
can_modify = [parent, nounou, cableur]
ldap_name = "macAddress"
def parse_value(self, mac):
self.value = format_mac(mac)
@ -559,6 +662,7 @@ class macAddress(Attr):
def __unicode__(self):
return unicode(self.value).lower()
@crans_attribute
class ipHostNumber(Attr):
singlevalue = True
optional = True
@ -567,6 +671,7 @@ class ipHostNumber(Attr):
hname = "IPv4"
category = 'base_tech'
can_modify = [nounou]
ldap_name = "ipHostNumber"
def parse_value(self, ip):
if ip == '<automatique>':
@ -576,6 +681,7 @@ class ipHostNumber(Attr):
def __unicode__(self):
return unicode(self.value)
@crans_attribute
class ip6HostNumber(Attr):
singlevalue = True
optional = True
@ -584,6 +690,7 @@ class ip6HostNumber(Attr):
hname = "IPv6"
category = 'base_tech'
can_modify = [nounou]
ldap_name = "ip6HostNumber"
def parse_value(self, ip6):
ip = ip6_of_mac(str(self.parent['macAddress'][0]), int(str(self.parent['rid'][0])))
@ -592,12 +699,14 @@ class ip6HostNumber(Attr):
def __unicode__(self):
return unicode(self.value)
@crans_attribute
class mid(Attr):
singlevalue = True
optional = False
unique = True
legend = u"Identifiant de machine"
category = 'id'
ldap_name = "mid"
def parse_value(self, mid):
self.value = int(mid)
@ -605,6 +714,7 @@ class mid(Attr):
def __unicode__(self):
return unicode(self.value)
@crans_attribute
class rid(Attr):
singlevalue = True
optional = False
@ -612,6 +722,7 @@ class rid(Attr):
legend = u"Identifiant réseau de machine"
category = 'id'
can_modify = [nounou]
ldap_name = "rid"
def parse_value(self, rid):
rid = int(rid)
@ -635,11 +746,13 @@ class rid(Attr):
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"
def parse_value(self, val):
if len(val) in [10, 22]:
@ -649,41 +762,51 @@ class ipsec(Attr):
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"
def parse_value(self, pos):
self.value = unicode(pos)
@crans_attribute
class nvram(Attr):
legend = u"Configuration speciale"
optional = True
can_modify = [nounou]
ldap_name = "nvram"
def parse_value(self, nvr):
# XXX - on fait quoi ici ?
@ -724,55 +847,73 @@ class portAttr(Attr):
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"
@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"
def parse_value(self, prise):
### Tu es Beau, je te fais confiance
self.value = 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"
def nonefunction(self, resp):
"""
@ -805,11 +946,13 @@ class responsable(Attr):
def __unicode__(self):
return 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):
"""
@ -839,12 +982,14 @@ class imprimeurClub(Attr):
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"
def parse_value(self, blacklist):
bl_debut, bl_fin, bl_type, bl_comm = blacklist.split('$')
@ -865,43 +1010,53 @@ class blacklist(Attr):
def __unicode__(self):
return u'%(debut)s$%(fin)s$%(type)s$%(comm)s' % self.value
@crans_attribute
class historique(Attr):
singlevalue = False
optional = True
legend = u"Historique de l'objet"
category = 'info'
ldap_name = "historique"
@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(Attr):
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"
def parse_value(self, charteSignee):
if charteSignee.upper() not in ["TRUE", "FALSE"]:
raise ValueError("La charte MA est soit TRUE ou FALSE, pas %r" % charteSignee)
self.value = charteSignee.upper()
@crans_attribute
class homeDirectory(Attr):
singlevalue=True
optional = True
unique = True
legend="Le chemin du home de l'adhérent"
ldap_name = "homeDirectory"
def parse_value(self, home):
uid = str(self.parent['uid'][0])
@ -911,11 +1066,13 @@ class homeDirectory(Attr):
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:
@ -948,95 +1105,113 @@ class loginShell(Attr):
raise ValueError("Shell %r invalide" % shell)
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"
def parse_value(self, gecos):
self.value = gecos
@crans_attribute
class sshFingerprint(Attr):
singlevalue = False
optional = True
legend = "Clef ssh de la machine"
can_modify = [parent, nounou]
ldap_name = "sshFingerprint"
@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 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("control 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"
@crans_attribute
class recuPaiement(Attr):
pass
ldap_name = "recuPaiement"
@crans_attribute
class dnsIpv6(boolAttr):
pass
ldap_name = "dnsIpv6"
@crans_attribute
class machineAlias(boolAttr):
pass
## Penser à remplacer ça par un dictionnaire en compréhension quand on sera sous wheezy partout
#: Dictionnaire mappant "nom de l'atttribut" -> classe permettant de l'instancier
CRANS_ATTRIBUTES = {}
for (k, v) in locals().items():
if type(v) == type and issubclass(v, Attr):
CRANS_ATTRIBUTES[k] = v
ldap_name = "machineAlias"

View file

@ -53,9 +53,10 @@ try:
from Levenshtein import jaro
except ImportError:
def jaro(a, b): return 0
sys.path.append('/usr/scripts/gestion')
sys.path.append('/usr/scripts/gestion')
import config
import crans_utils
import attributs
import ldap_locks
@ -71,9 +72,9 @@ created = 'created'
modified = 'modified'
deleted = 'deleted'
# Pour enregistrer dans l'historique, on a besoin de savoir qui exécute le script
# Si le script a été exécuté via un sudo, la variable SUDO_USER (l'utilisateur qui a effectué le sudo)
# est plus pertinente que USER (qui sera root)
#: Pour enregistrer dans l'historique, on a besoin de savoir qui exécute le script
#: Si le script a été exécuté via un sudo, la variable SUDO_USER (l'utilisateur qui a effectué le sudo)
#: est plus pertinente que USER (qui sera root)
current_user = os.getenv("SUDO_USER") or os.getenv("USER")
# Quand on a besoin du fichier de secrets
@ -84,7 +85,7 @@ def import_secrets():
import secrets
return secrets
# Champs à ignorer dans l'historique
#: Champs à ignorer dans l'historique
HIST_IGNORE_FIELDS = ["modifiersName", "entryCSN", "modifyTimestamp", "historique"]
def escape(chaine):
@ -453,9 +454,9 @@ class lc_ldap_local(lc_ldap):
def new_cransldapobject(conn, dn, mode='ro', ldif = None):
"""Crée un objet :py:class:`CransLdap` en utilisant la classe correspondant à
"""Crée un objet :py:class:`CransLdapObject` en utilisant la classe correspondant à
l'``objectClass`` du ``ldif``
--pour usage interne à la libraire uniquement !"""
--pour usage interne à la librairie uniquement !"""
classe = None
@ -464,13 +465,13 @@ def new_cransldapobject(conn, dn, mode='ro', ldif = None):
elif dn == invite_dn:
classe = BaseInvites
elif ldif:
classe = globals()[ldif['objectClass'][0]]
classe = ObjectFactory.get(ldif['objectClass'][0])
else:
res = conn.search_s(dn, 0)
if not res:
raise ValueError ('objet inexistant: %s' % dn)
_, attrs = res[0]
classe = globals()[attrs['objectClass'][0]]
classe = ObjectFactory.get(attrs['objectClass'][0])
return classe(conn, dn, mode, ldif)
@ -793,6 +794,36 @@ class CransLdapObject(object):
self._modifs.setdefault('blacklist', []).append(bl)
class ObjectFactory(object):
"""Utilisée pour enregistrer toutes les classes servant à instancier un objet 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):
"""Retourne la classe qui a ``name`` pour ``ldap_name``.
Pas de fallback, on ne veut pas instancier des objets de manière hasardeuse.
"""
return cls._classes.get(name)
def crans_object(classe):
"""Pour décorer les classes permettant d'instancier des attributs LDAP,
afin de les enregistrer dans :py:class:`ObjectFactory`.
"""
ObjectFactory.register(classe.ldap_name, classe)
return classe
class proprio(CransLdapObject):
u""" Un propriétaire de machine (adhérent, club…) """
@ -944,6 +975,7 @@ class BaseInvites(proprio):
pass
@crans_object
class adherent(proprio):
u"""Adhérent crans."""
attribs = proprio.attribs + [attributs.aid, attributs.prenom, attributs.tel,
@ -951,6 +983,7 @@ class adherent(proprio):
attributs.derniereConnexion, attributs.gpgFingerprint,
attributs.carteEtudiant, attributs.droits, attributs.etudes,
attributs.postalAddress, attributs.mailExt, attributs.compteWiki]
ldap_name = "adherent"
def __init__(self, conn, dn, mode='ro', ldif = None):
super(adherent, self).__init__(conn, dn, mode, ldif)
@ -1028,17 +1061,22 @@ class adherent(proprio):
raise EnvironmentError("L'adhérent n'a pas de compte crans")
@crans_object
class club(proprio):
u"""Club crans"""
attribs = proprio.attribs + [attributs.cid, attributs.responsable, attributs.imprimeurClub]
ldap_name = "club"
@crans_object
class machineFixe(machine):
u"""Machine fixe"""
pass
ldap_name = "machineFixe"
@crans_object
class machineWifi(machine):
u"""Machine wifi"""
attribs = machine.attribs + [attributs.ipsec]
ldap_name = "machineWifi"
def set_ipv4(self, login=None):
u"""Définie une ipv4 à la machine si elle n'est possède pas déjà une."""
@ -1053,13 +1091,16 @@ class machineWifi(machine):
dhcp=dydhcp()
dhcp.add_host(str(self['ipHostNumber'][0]), str(self['macAddress'][0]), str(self['host'][0]))
@crans_object
class machineCrans(machine):
can_be_by = { created: [attributs.nounou],
modified: [attributs.nounou],
deleted: [attributs.nounou],
}
attribs = machine.attribs + [attributs.prise, attributs.nombrePrises]
ldap_name = "machineCrans"
@crans_object
class borneWifi(machine):
can_be_by = { created: [attributs.nounou],
modified: [attributs.nounou],
@ -1067,16 +1108,21 @@ class borneWifi(machine):
}
attribs = machine.attribs + [attributs.canal, attributs.puissance, attributs.hotspot,
attributs.prise, attributs.positionBorne, attributs.nvram]
ldap_name = "borneWifi"
@crans_object
class facture(CransLdapObject):
can_be_by = { created: [attributs.nounou, attributs.bureau, attributs.cableur],
modified: [attributs.nounou, attributs.bureau, attributs.cableur],
deleted: [attributs.nounou, attributs.bureau, attributs.cableur],
}
attribs = [attributs.fid, attributs.modePaiement, attributs.recuPaiement]
ldap_name = "facture"
@crans_object
class service(CransLdapObject):
pass
ldap_name = "service"
@crans_object
class lock(CransLdapObject):
pass
ldap_name = "lock"

View file

@ -69,162 +69,162 @@ def preattr(val):
# Presque identique à celle dans ldap_crans.py
def service_to_restart(conn, new=None, args=[], start=0):
"""
start indique la date (en secondes depuis epoch) à partir du
moment cette action sera effectuée.
"""
start indique la date (en secondes depuis epoch) à partir du
moment cette action sera effectuée.
Si new = None retourne la liste des services à redémarrer.
Si new = None retourne la liste des services à redémarrer.
Si new est fourni, mais ne commence pas par '-', on ajoute
le service à la liste avec les arguments args (args doit être
une liste).
Si new est fourni, mais ne commence pas par '-', on ajoute
le service à la liste avec les arguments args (args doit être
une liste).
Si new commence par '-', on supprime le service si son start
est dans le futur.
Si new commence par '-', on supprime le service si son start
est dans le futur.
Si new commence par '--', on supprime le service sans condition.
"""
if new: new = preattr(new)[1]
Si new commence par '--', on supprime le service sans condition.
"""
if new: new = preattr(new)[1]
# Quels services sont déjà à redémarrer ?
serv = {} # { service: [ arguments ] }
serv_dates = {} # { service: [ dates de restart ] }
services = []
for s in conn.search_s(services_dn, 1, 'objectClass=service'):
s = s[1]
serv[s['cn'][0]] = s.get('args', [])
serv_dates[s['cn'][0]] = s.get('start', [])
services.append(Service(s['cn'][0], s.get('args', []), s.get('start', [])))
# Quels services sont déjà à redémarrer ?
serv = {} # { service: [ arguments ] }
serv_dates = {} # { service: [ dates de restart ] }
services = []
for s in conn.search_s(services_dn, 1, 'objectClass=service'):
s = s[1]
serv[s['cn'][0]] = s.get('args', [])
serv_dates[s['cn'][0]] = s.get('start', [])
services.append(Service(s['cn'][0], s.get('args', []), s.get('start', [])))
# Retourne la liste des services à redémarrer
if not new: return services
# Retourne la liste des services à redémarrer
if not new: return services
# Effacement d'un service
if new[0] == '-':
if new[1] == '-':
# Double -- on enlève quelque soit la date
remove_dn = 'cn=%s,%s' % (new[2:], services_dn)
else:
# On enlève uniquement si la date est passée
remove_dn = 'cn=%s,%s' % (new[1:], services_dn)
if not serv.has_key(new[1:]):
# Existe pas => rien à faire
return
keep_date = []
for date in serv_dates[new[1:]]:
if time.time() < int(date):
keep_date.append(date)
if keep_date:
mods = [{'start': serv_dates[new[1:]]}, { 'start': keep_date }]
conn.modify_s(remove_dn, ldap.modlist.modifyModlist(*mods))
remove_dn = None
if remove_dn:
# Suppression
try: conn.delete_s(remove_dn)
except: pass
# Si n'existe pas => Erreur mais le résultat est là.
return
serv_dn = 'cn=%s,%s' % (new, services_dn)
# Conversion avant stockage dans la base
if isinstance(args, basestring):
args = [args]
args = map(lambda x:preattr(x)[1], args)
try:
start = [int(start)]
except TypeError:
pass
start = map(lambda x:preattr(x)[1], start)
if new in serv.keys():
modlist = []
new_args = []
# Nouveaux arguments ?
for arg in args:
if arg not in serv[new]:
new_args.append(arg)
if new_args:
modlist += ldap.modlist.modifyModlist({'args': serv[new]},
{'args': serv[new] + new_args})
new_date = []
# Nouvelle date ?
for date in start:
if date not in serv_dates[new]:
new_date.append(date)
if new_date:
modlist += ldap.modlist.modifyModlist({'start': serv_dates[new]},
{'start': serv_dates[new] + new_date})
if modlist:
try:
conn.modify_s(serv_dn, modlist)
except ldap.TYPE_OR_VALUE_EXISTS:
# Pas grave
pass
# else rien à faire
# Effacement d'un service
if new[0] == '-':
if new[1] == '-':
# Double -- on enlève quelque soit la date
remove_dn = 'cn=%s,%s' % (new[2:], services_dn)
else:
# Entrée non présente -> ajout
modlist = ldap.modlist.addModlist({ 'objectClass': 'service',
'cn': new,
'args': args,
'start': start })
# On enlève uniquement si la date est passée
remove_dn = 'cn=%s,%s' % (new[1:], services_dn)
if not serv.has_key(new[1:]):
# Existe pas => rien à faire
return
keep_date = []
for date in serv_dates[new[1:]]:
if time.time() < int(date):
keep_date.append(date)
if keep_date:
mods = [{'start': serv_dates[new[1:]]}, { 'start': keep_date }]
conn.modify_s(remove_dn, ldap.modlist.modifyModlist(*mods))
remove_dn = None
if remove_dn:
# Suppression
try: conn.delete_s(remove_dn)
except: pass
# Si n'existe pas => Erreur mais le résultat est là.
return
serv_dn = 'cn=%s,%s' % (new, services_dn)
# Conversion avant stockage dans la base
if isinstance(args, basestring):
args = [args]
args = map(lambda x:preattr(x)[1], args)
try:
start = [int(start)]
except TypeError:
pass
start = map(lambda x:preattr(x)[1], start)
if new in serv.keys():
modlist = []
new_args = []
# Nouveaux arguments ?
for arg in args:
if arg not in serv[new]:
new_args.append(arg)
if new_args:
modlist += ldap.modlist.modifyModlist({'args': serv[new]},
{'args': serv[new] + new_args})
new_date = []
# Nouvelle date ?
for date in start:
if date not in serv_dates[new]:
new_date.append(date)
if new_date:
modlist += ldap.modlist.modifyModlist({'start': serv_dates[new]},
{'start': serv_dates[new] + new_date})
if modlist:
try:
conn.add_s(serv_dn, modlist)
except ldap.ALREADY_EXISTS:
# Existe déja => rien à faire
conn.modify_s(serv_dn, modlist)
except ldap.TYPE_OR_VALUE_EXISTS:
# Pas grave
pass
# else rien à faire
else:
# Entrée non présente -> ajout
modlist = ldap.modlist.addModlist({ 'objectClass': 'service',
'cn': new,
'args': args,
'start': start })
try:
conn.add_s(serv_dn, modlist)
except ldap.ALREADY_EXISTS:
# Existe déja => rien à faire
pass
def services_to_restart(conn, old_attrs={}, new_attrs={}):
"""Détermine quels sont les services à reconfigurer"""
old_attrs_c = dict((attributs.CRANS_ATTRIBUTES[key], value) for key,value in old_attrs.items() if key in attributs.CRANS_ATTRIBUTES.keys() and value)
new_attrs_c = dict((attributs.CRANS_ATTRIBUTES[key], value) for key,value in new_attrs.items() if key in attributs.CRANS_ATTRIBUTES.keys() and value)
"""Détermine quels sont les services à reconfigurer"""
old_attrs_c = dict((attributs.AttributeFactory.get(key), value) for key,value in old_attrs.items() if attributs.AttributeFactory.get(key, fallback=None) != None and value)
new_attrs_c = dict((attributs.AttributeFactory.get(key), value) for key,value in new_attrs.items() if attributs.AttributeFactory.get(key, fallback=None) != None and value)
created_attr = [ attr for name in new_attrs_c.keys() if not name in old_attrs_c.keys() for attr in new_attrs_c[name]]
deleted_attr = [ attr for name in old_attrs_c.keys() if not name in new_attrs_c.keys() for attr in old_attrs_c[name]]
updated_attr = [ (old_attrs_c[name], new_attrs_c[name],
created_attr = [ attr for name in new_attrs_c.keys() if not name in old_attrs_c.keys() for attr in new_attrs_c[name]]
deleted_attr = [ attr for name in old_attrs_c.keys() if not name in new_attrs_c.keys() for attr in old_attrs_c[name]]
updated_attr = [ (old_attrs_c[name], new_attrs_c[name],
[ i for i in old_attrs_c[name] if i.value not in [ j.value for j in new_attrs_c[name]]],
[ i for i in new_attrs_c[name] if i.value not in [ j.value for j in old_attrs_c[name]]],
) for name in set(new_attrs_c.keys()).intersection(old_attrs_c.keys()) if old_attrs_c[name][-1].value != new_attrs_c[name][-1].value ]
updated_attr_new = [ i for j in updated_attr for i in j[3]]
updated_attr_old = [ i for j in updated_attr for i in j[2]]
) for name in set(new_attrs_c.keys()).intersection(old_attrs_c.keys()) if old_attrs_c[name][-1].value != new_attrs_c[name][-1].value ]
updated_attr_new = [ i for j in updated_attr for i in j[3]]
updated_attr_old = [ i for j in updated_attr for i in j[2]]
services_to_restart = {}
for attr in [ attr for l in [created_attr, deleted_attr, updated_attr_new ] for attr in l]:
for service in attrs_to_services.get(attr.__class__, []):
services_to_restart[service] = services_to_restart.get(service, []) + [attr]
services_to_restart = {}
for attr in [ attr for l in [created_attr, deleted_attr, updated_attr_new ] for attr in l]:
for service in attrs_to_services.get(attr.__class__, []):
services_to_restart[service] = services_to_restart.get(service, []) + [attr]
for service in services_to_restart.keys():
for attr in services_to_restart[service]:
start = 0
arg = set()
if service in services_to_args.keys():
arg = arg.union(services_to_args[service](attr))
if attr in updated_attr_new:
for old_attr in updated_attr_old:
if attr.__class__ == old_attr.__class__:
arg = arg.union(services_to_args[service](old_attr))
for service in services_to_restart.keys():
for attr in services_to_restart[service]:
start = 0
arg = set()
if service in services_to_args.keys():
arg = arg.union(services_to_args[service](attr))
if attr in updated_attr_new:
for old_attr in updated_attr_old:
if attr.__class__ == old_attr.__class__:
arg = arg.union(services_to_args[service](old_attr))
# Cas du dhcp
if attr.__class__ in services_to_attrs['dhcp']:
dhcp=dydhcp()
if old_attrs.get('ipHostNumber', []) and old_attrs.get('macAddress', []):
if new_attrs.get('ipHostNumber', []) and new_attrs.get('macAddress', []):
if str(old_attrs['ipHostNumber'][0]) != str(new_attrs['ipHostNumber'][0]) or str(old_attrs['macAddress'][0]) != str(new_attrs['macAddress'][0]):
dhcp.del_host(str(old_attrs['ipHostNumber'][0]), str(old_attrs['macAddress'][0]))
dhcp.add_host(str(new_attrs['ipHostNumber'][0]), str(new_attrs['macAddress'][0]), str(new_attrs['host'][0]))
else:
# Cas du dhcp
if attr.__class__ in services_to_attrs['dhcp']:
dhcp=dydhcp()
if old_attrs.get('ipHostNumber', []) and old_attrs.get('macAddress', []):
if new_attrs.get('ipHostNumber', []) and new_attrs.get('macAddress', []):
if str(old_attrs['ipHostNumber'][0]) != str(new_attrs['ipHostNumber'][0]) or str(old_attrs['macAddress'][0]) != str(new_attrs['macAddress'][0]):
dhcp.del_host(str(old_attrs['ipHostNumber'][0]), str(old_attrs['macAddress'][0]))
elif new_attrs.get('ipHostNumber', []) and new_attrs.get('macAddress', []):
dhcp.add_host(str(new_attrs['ipHostNumber'][0]), str(new_attrs['macAddress'][0]), str(new_attrs['host'][0]))
dhcp.add_host(str(new_attrs['ipHostNumber'][0]), str(new_attrs['macAddress'][0]), str(new_attrs['host'][0]))
else:
dhcp.del_host(str(old_attrs['ipHostNumber'][0]), str(old_attrs['macAddress'][0]))
elif new_attrs.get('ipHostNumber', []) and new_attrs.get('macAddress', []):
dhcp.add_host(str(new_attrs['ipHostNumber'][0]), str(new_attrs['macAddress'][0]), str(new_attrs['host'][0]))
if service in services_to_time.keys():
start = services_to_time[service](attr)
#print "%s,%s,%s" % (service, arg, start)
service_to_restart(conn, service, list(arg), start)
if service in services_to_time.keys():
start = services_to_time[service](attr)
#print "%s,%s,%s" % (service, arg, start)
service_to_restart(conn, service, list(arg), start)