#!/usr/bin/env python # -*- coding: utf-8 -*- # """ Définition des classes permettant d'instancier les attributs LDAP. """ # # Copyright (C) 2010-2013 Cr@ns # Authors: Antoine Durand-Gasselin # Nicolas Dandrimont # Valentin Samir # Vincent Le Gallic # Pierre-Elliott Bécue # # 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 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 netaddr import time 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 import itertools sys.path.append("/usr/scripts/") import cranslib.deprecated sys.path.append("/usr/scripts/gestion") import config import config.impression 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] #: 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: if not isinstance(val, unicode): cranslib.deprecated.usage("attrify ne devrait être appelé qu'avec des unicode (%r)" % val, level=3) val = val.decode('utf-8') return AttributeFactory.get(attr, fallback=Attr)(val, conn, Parent) 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 #: 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] """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) self.parent = Parent self.parse_value(val) 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: 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', 'cransAccount', 'service', 'facture', 'freeMid' ]: raise ValueError("Pourquoi insérer un objectClass=%r ?" % val) else: self.value = unicode(val) class intAttr(Attr): def parse_value(self, val): if int(val) <= 0: raise ValueError("Valeur entière invalide : %r" % val) self.value = int(val) def __unicode__(self): return unicode(self.value) class boolAttr(Attr): def parse_value(self, val): if val.lower() in [u'true', u'ok']: self.value = True elif val.lower() == u'false': self.value = False elif isinstance(val, bool): self.value = val 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 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 = int(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(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 unique = True legend = "Adresse 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__ 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 externe" can_modify = [soi, cableur, nounou] category = 'mail' ldap_name = "mailExt" def parse_value(self, mail): mail = mail.lower() 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 = False 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] #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]: # 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 ? """ modifiables = set() for i in liste_droits: modifiables.add(DROITS_SUPERVISEUR.get(i, [])) modifiables = list(modifiables) 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 #if not (float(solde) >= config.impression.decouvert and float(solde) <= 1024.): # raise ValueError("Solde invalide: %r" % solde) self.value = float(solde) def __unicode__(self): return u"%.2f" % self.value class dnsAttr(Attr): category = 'dns' ldap_name = "dnsAttr" 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" def parse_value(self, mac): 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] ldap_name = "ipHostNumber" default = u'' def parse_value(self, ip): if ip == '': ip = ip4_of_rid(str(self.parent['rid'][0])) self.value = netaddr.IPAddress(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' can_modify = [nounou] ldap_name = "ip6HostNumber" default = u'' def parse_value(self, ip6): if ip6 == '': ip6 = ip6_of_mac(str(self.parent['macAddress'][0]), int(str(self.parent['rid'][0]))) self.value = netaddr.IPAddress(ip6) 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) def __unicode__(self): return unicode(self.value) @crans_attribute class rid(Attr): 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 = int(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" 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" @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" 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 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" def parse_value(self, blacklist): bl_debut, bl_fin, bl_type, bl_comm = blacklist.split('$') 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" # 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(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 [u"TRUE", u"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 = 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 = [u'/bin/csh', u'/bin/sh', u'/usr/bin/es', u'/usr/bin/ksh', u'/bin/ksh', u'/usr/bin/rc', u'/usr/bin/tcsh', u'/bin/tcsh', u'/usr/bin/esh', u'/bin/bash', u'/bin/rbash', u'/bin/zsh', u'/usr/bin/zsh', u'/usr/bin/screen', u'/bin/dash', u'/usr/bin/rssh', u'/usr/local/bin/disconnect_shell', u'/usr/scripts/surveillance/disconnect_shell', u'/usr/local/bin/badPassSh', u'/usr/bin/passwd', u'/bin/false', u'/usr/sbin/nologin' u''] if shell not in shells: 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" @crans_attribute class userPassword(Attr): singlevalue = True optional = True legend = "Le mot de passe" category = '' ldap_name = "userPassword" @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 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" @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" def parse_value(self, article): art_code, art_designation, art_nombre, art_pu = article.split('~~') now = time.time() 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 ['code', 'designation', 'nombre', 'pu']: 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" @crans_attribute class machineAlias(boolAttr): ldap_name = "machineAlias"