From c392a2a98625324d067a1cb2cb4442dd7b1d2fbf Mon Sep 17 00:00:00 2001 From: Vincent Le Gallic Date: Wed, 15 May 2013 23:00:17 +0200 Subject: [PATCH] =?UTF-8?q?On=20utilise=20un=20champ=20ldap=5Fname=20pour?= =?UTF-8?q?=20savoir=20quelle=20classe=20doit=20=C3=AAtre=20utilis=C3=A9e?= =?UTF-8?q?=20pour=20instancier=20quel=20objet/attribut=20LDAP.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Et comme ça on fiche à la poubelle le dégueulasse globals() et on décorrelle les noms des classes des noms LDAP. --- attributs.py | 219 ++++++++++++++++++++++++++++++++++++----- lc_ldap.py | 70 ++++++++++--- services.py | 272 +++++++++++++++++++++++++-------------------------- 3 files changed, 391 insertions(+), 170 deletions(-) diff --git a/attributs.py b/attributs.py index af5bfc8..84f5bcc 100644 --- a/attributs.py +++ b/attributs.py @@ -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 == '': @@ -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 ? @@ -695,7 +818,7 @@ class portAttr(Attr): legend = u'Ouverture de port' category = 'firewall' can_modify = [nounou] - + def parse_value(self, port): if ":" in port: a,b = port.split(":", 1) @@ -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,12 +1066,14 @@ 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: # shells = [ l.strip() for l in f.readlines() if not l.startswith('#') ] @@ -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" diff --git a/lc_ldap.py b/lc_ldap.py index f601c7a..9a99cde 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -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" diff --git a/services.py b/services.py index d9434d4..c96ccaa 100644 --- a/services.py +++ b/services.py @@ -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 où cette action sera effectuée. + """ + start indique la date (en secondes depuis epoch) à partir du + moment où 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)