#!/usr/bin/env python # -*- coding: utf-8 -*- # # ATTRIBUTS.PY-- Description des 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 from unicodedata import normalize from crans_utils import format_tel, format_mac, mailexist, validate_name, ip4_of_rid, ip6_of_mac sys.path.append("/usr/scripts/gestion") import config import config.impression import annuaires_pg import smtplib import random import string ### 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 nounou = u"Nounou" cableur = u"Cableur" apprenti = u"Apprenti" tresorier = u"Tresorier" bureau = u"Bureau" imprimeur = u"Imprimeur" moderateur = u"Moderateur" multimachines = u"Multimachines" webmaster = u"Webmaster" webradio = u"Webradio" parent = u"parent" soi = u"soi" respo = u"responsable" TOUS_DROITS = [nounou, apprenti, bureau, tresorier, imprimeur, moderateur, multimachines, cableur, webmaster] DROITS_ELEVES = [nounou, bureau] DROITS_MOYEN = [apprenti, moderateur] DROITS_FAIBLES = [cableur, imprimeur, multimachines] DROITS_SUPERVISEUR = { nounou : TOUS_DROITS, bureau : DROITS_FAIBLES + [bureau, tresorier], } class SingleValueError(ValueError): pass class UniquenessError(EnvironmentError): pass class OptionalError(EnvironmentError): 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): val = val.decode('utf-8') return CRANS_ATTRIBUTES.get(attr, Attr)(val, conn, Parent) class AttrsDict(dict) : def __init__(self, conn, ldif={}, Parent=None): super(AttrsDict, self).__init__(ldif) 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 CRANS_ATTRIBUTES.get(attr, Attr).singlevalue and len(values) > 1: raise SingleValueError("L'attribut %s doit être monovalué.") 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] 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 """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 __unicode__(self): # XXX - Vérifier que cette méthode produit un objet parsable assert isinstance(self.value, unicode) return 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 str(self) in liste_exclue: return if self.unique: res = self.conn.search('%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 objectClass(Attr): singlevalue = False optional = False legend = "entité" """ 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() class aid(intAttr): singlevalue = True optional = True legend = u"Identifiant de l'adhérent" category = 'id' unique = True """ Personne ne devrait modifier un attribut d'identification """ can_modify = [] can_view = [nounou, apprenti, cableur] def parse_value(self, aid): self.value = int(aid) class uid(Attr): singlevalue = True option = False legend = u"L'identifiant canonique de l'adhérent" category = 'perso' unique = True class nom(Attr): singlevalue = True optional = False legend = "Nom" category = 'perso' can_modify = [nounou, cableur] def parse_value(self, nom): if self.parent != None: if u'club' in self.parent['objectClass']: self.value = validate_name(nom,"0123456789\[\]") else: self.value = validate_name(nom) else: self.value = validate_name(nom) class prenom(Attr): singlevalue = True optional = False legend = u"Prénom" category = 'perso' can_modify = [nounou, cableur] def parse_value(self, prenom): self.value = validate_name(prenom) class compteWiki(Attr): singlevalue = False optional = True legend = u"Compte WiKi" category = 'perso' can_modify = [nounou, cableur, soi] def parse_value(self, compte): self.value = validate_name(compte) # TODO: validate with mdp for user definition here ? class tel(Attr): singlevalue = True optional = False legend = u"Téléphone" category = 'perso' can_modify = [soi, nounou, cableur] 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 class paiement(yearAttr): legend = u"Paiement" can_modify = [cableur, nounou, tresorier] category = 'perso' class carteEtudiant(yearAttr): legend = u"Carte d'étudiant" category = 'perso' can_modify = [cableur, nounou, tresorier] class derniereConnexion(intAttr): legend = u"Dernière connexion" can_modify = [] class mail(Attr): singlevalue = True optional = False unique = True legend = "Le mail de l'adhérent" can_modify = [soi, nounou, cableur] category = '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 = str(self).split('@', 1)[0] try: smtp = smtplib.SMTP(smtpserv) smtp.putcmd("vrfy", mail) res = smtp.getreply()[0] in [250, 252] smtp.close() except: raise ValueError(u'Serveur de mail injoignable') if res: raise ValueError("Le mail %s est déjà pris." % (str(self))) def parse_value(self, mail): if not re.match('^[-_.0-9A-Za-z]+@([A-Za-z0-9]{1}[A-Za-z0-9-_]+[.])+[a-z]{2,6}$', mail): raise ValueError("Adresse mail invalide (%s)" % mail) self.value = mail class canonicalAlias(mail): singlevalue = True optional = False unique = True legend = u"Alias mail canonique" category = 'mail' def parse_value(self, mail): mail = u".".join([ a.capitalize() for a in mail.split(u'.', 1) ]) if not re.match('^[-_.0-9A-Za-z]+@([A-Za-z0-9]{1}[A-Za-z0-9-_]+[.])+[a-z]{2,6}$', mail): raise ValueError("Alias mail invalide (%s)" % mail) self.value = mail class mailAlias(mail): singlevalue = False optional = True unique = True legend = u"Alias mail" can_modify = [soi, cableur, nounou] category = 'mail' def parse_value(self, mail): mail = mail.lower() if not re.match('^[-_.0-9A-Za-z]+@([A-Za-z0-9]{2}[A-Za-z0-9-_]+[.])+[a-z]{2,6}$', mail): raise ValueError("Alias mail invalide (%r)" % mail) self.value = mail class mailExt(mail): singlevalue = False optional = True unique = True legend = u"Mail externe" can_modify = [soi, cableur, nounou] category = 'mail' def parse_value(self, mail): mail = mail.lower() if not re.match('^[-_.0-9A-Za-z]+@([A-Za-z0-9]{2}[A-Za-z0-9-_]+[.])+[a-z]{2,6}$', mail): raise ValueError("Mail externe invalide (%r)" % mail) self.value = mail class mailInvalide(boolAttr): optional = True legend = u"Mail invalide" can_modify = [bureau, nounou] class contourneGreylist(boolAttr): optionnal = True legend = u"Contourner la greylist" category = 'mail' can_modify = [soi] def __unicode__(self): return u"OK" class etudes(Attr): singlevalue = False optional = False legend = u"Études" can_modify = [soi, cableur, nounou] category = 'perso' def parse_value(self, etudes): # who cares self.value = etudes class chbre(Attr): singlevalue = True optional = False unique = True legend = u"Chambre sur le campus" can_modify = [soi, cableur, nounou] category = 'perso' def parse_value(self, chambre): if self.parent != None: if u'club' in self.parent['objectClass']: if chambre in annuaires_pg.locaux_clubs(): self.value = chambre return else: raise ValueError("Club devrait etre en XclN, pas en %r" % chambre) if chambre in (u"EXT", u"????"): self.value = chambre return 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 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' 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 class solde(Attr): singlevalue = True optional = True legend = u"Solde d'impression" can_modify = [imprimeur, nounou, tresorier] 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 = solde class dnsAttr(Attr): category = 'dns' def parse_value(self, val): val = val.lower() name, _ = val.split('.', 1) if not re.match('^[a-z](-*[a-z0-9]+)*$', name): raise ValueError("Nom d'hote invalide %r" % val) self.value = val class host(dnsAttr): singlevalue = True optional = False hname = legend = u"Nom d'hôte" can_modify = [parent, nounou, cableur] category = 'base_tech' 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('(|(host=%s)(hostAlias=%s))' % ((str(self),)*2)) if res: raise ValueError("Hôte déjà existant", [r.dn for r in res]) class hostAlias(host): singlevalue = False unique = True optional = True legend = u'Alias de nom de machine' can_modify = [nounou, cableur] 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] def parse_value(self, mac): self.value = format_mac(mac) # XXX self.parent['ip6HostNumber'] = cequ'ilfaut def __unicode__(self): return unicode(self.value).lower() class ipHostNumber(Attr): singlevalue = True optional = True unique = True legend = u"Adresse IPv4 de la machine" hname = "IPv4" category = 'base_tech' can_modify = [nounou] def parse_value(self, ip): if ip == '': ip = ip4_of_rid(str(self.parent['rid'][0])) self.value = netaddr.ip.IPAddress(ip) def __unicode__(self): return unicode(self.value) class ip6HostNumber(Attr): singlevalue = True optional = True unique = True legend = u"Adresse IPv6 de la machine" hname = "IPv6" category = 'base_tech' can_modify = [nounou] def parse_value(self, ip6): ip = ip6_of_mac(str(self.parent['macAddress'][0]), int(str(self.parent['rid'][0]))) self.value = netaddr.ip.IPAddress(ip) def __unicode__(self): return unicode(self.value) class mid(Attr): singlevalue = True optional = False unique = True legend = u"Identifiant de machine" category = 'id' def parse_value(self, mid): self.value = int(mid) def __unicode__(self): return unicode(self.value) class rid(Attr): singlevalue = True optional = False unique = True legend = u"Identifiant réseau de machine" category = 'id' can_modify = [nounou] def parse_value(self, rid): rid = int(rid) # On veut éviter les rid qui recoupent les ipv4 finissant par # .0 ou .255 plages = [xrange(config.rid[a][0], config.rid[a][1]+1) for a in config.rid.keys() if ('v6' not in a) and ('special' not in a)] 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) class ipsec(Attr): singlevalue = False optional = True legend = u'Clef wifi' category = 'wifi' def parse_value(self, val): if len(val) == 10: 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)) class puissance(Attr): singlevalue = True optional = True legend = u"puissance d'émission pour les bornes wifi" category = 'wifi' can_modify = [nounou] class canal(intAttr): singlevalue = True optional = True legend = u'Canal d\'émission de la borne' category = 'wifi' can_modify = [nounou] class hotspot(boolAttr): singlevalue = True optional = True legend = u'Hotspot' category = 'wifi' can_modify = [nounou] class positionBorne(Attr): legend = u"Position de la borne" category = "wifi" can_modify = [nounou] singlevalue = True optional = True def parse_value(self, pos): if pos == u'N/A N/A': self.value = u"N/A°N N/A°E" else: lat, lon = pos.split(" ") self.value = u"%f°N %f°E" % (float(lat), float(lon)) class nvram(Attr): legend = u"Configuration speciale" optional = True can_modify = [nounou] def parse_value(self, nvr): # XXX - on fait quoi ici ? self.value = nvr 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]) class portTCPout(portAttr): legend = u'Port TCP ouvert vers l\'extérieur' class portTCPin(portAttr): legend = u"Port TCP ouvert depuis l'extérieur" class portUDPout(portAttr): legend = u"Port UDP ouvert vers l'extérieur" class portUDPin(portAttr): legend = u"Port UDP ouvert depuis l'extérieur" class exempt(Attr): legend = u"Exemption vers une IP" class nombrePrises(intAttr): legend = u"Nombre de prises ethernet de la machine" singlevalue = True optional = True categoriy = 'base_tech' can_modify = [nounou] class prise(Attr): singlevalue = True optional = True legend = u"Prise sur laquelle est branchée la machine" category = 'base_tech' can_modify = [nounou] def parse_value(self, prise): ### Tu es Beau, je te fais confiance self.value = prise class cid(intAttr): singlevalue = True optional = True unique = True legend = u"Identifiant du club" category = 'id' def parse_value(self, val): self.value = int(val) class responsable(Attr): singlevalue = True optional = True legend = u"Responsable du club" category = 'perso' can_modify = [cableur, nounou] 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('aid=%s' % self.__value)[0] except IndexError: raise ValueError("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 class imprimeurClub(Attr): optional = True legend = u"Imprimeur du club" category = "perso" can_modify = [cableur, nounou] 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('aid=%s' % int(self.__value))[0] except IndexError: raise ValueError("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]) class blacklist(Attr): singlevalue = False optional = True legend = u"Blackliste" category = 'info' can_modify = [nounou] 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 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 class historique(Attr): singlevalue = False optional = True legend = u"Historique de l'objet" category = 'info' class info(Attr): singlevalue = False optional = True legend = u"Quelques informations" category = 'info' can_modify = [nounou, cableur, bureau] class homepageAlias(Attr): singlevalue = True optional = True legend = u'Un alias pour la page personnelle' can_modify = [nounou, cableur] category = 'webpage' 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' 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() class homeDirectory(Attr): singlevalue=True optional = True unique = True legend="Le chemin du home de l'adhérent" def parse_value(self, home): uid = str(self.parent['uid'][0]) if uid.startswith('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 class loginShell(Attr): singlevalue = True optional = True legend = "Le shell de l'adherent" can_modify = [soi, nounou, cableur] def parse_value(self, shell): #with open('/etc/shells') as f: # shells = [ l.strip() for l in f.readlines() if not l.startswith('#') ] shells = ['/bin/csh', '/bin/sh', '/usr/bin/es', '/usr/bin/ksh', '/bin/ksh', '/usr/bin/rc', '/usr/bin/tcsh', '/bin/tcsh', '/usr/bin/esh', '/bin/bash', '/bin/rbash', '/bin/zsh', '/usr/bin/zsh', '/usr/bin/screen', '/bin/dash', '/usr/bin/rssh', '/usr/local/bin/disconnect_shell', '/usr/scripts/surveillance/disconnect_shell', '/usr/local/bin/badPassSh', '/usr/bin/passwd', '/bin/false', '/bin//zsh' ''] if shell not in shells: raise ValueError("Shell %r invalide" % shell) self.value = shell class uidNumber(intAttr): singlevalue = True optional = True unique = True legend = "L'uid du compte de l'adherent" category = 'id' class gidNumber(intAttr): singlevalue = True optional = True legend = "Le gid du compte de l'adhérent" category = 'id' class gecos(Attr): singlevalue = True optional = True legend = "Le gecos" category = 'id' def parse_value(self, gecos): self.value = gecos class sshFingerprint(Attr): singlevalue = False optional = True legend = "Clef ssh de la machine" can_modify = [parent, nounou] class gpgFingerprint(Attr): singlevalue = False optional = True unique = True legend = "Clef gpg d'un adhérent" can_modify = [soi, nounou] class cn(Attr): singlevalue = True optional = False category = 'id' class dn(Attr): singlevalue = True optional = False unique = True category = 'id' class postalAddress(Attr): singlevalue = False optional = True can_modify = [soi, cableur, nounou, bureau] legend = u"Adresse" category = 'perso' class controle(Attr): singlevalue = True optional = False legend = u"Contrôle" can_modify = [tresorier, nounou] category = 'perso' 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]") if ctrl == u'cp': self.value = u'pc' else: self.value = ctrl class fid(intAttr): legend = u"Id de facture" category = 'factures' optional = False singlevalue = True class modePaiement(Attr): legend = u"Mode de paiement" category = 'factures' optional = False singlevalue = True class recuPaiement(Attr): pass class dnsIpv6(boolAttr): pass class machineAlias(boolAttr): pass ### Les classes ADHERENT_ATTRS = [ nom, prenom, tel, chbre, postalAddress, mail, uid, canonicalAlias, mailAlias, etudes, paiement, solde, carteEtudiant, droits, loginShell, blacklist ] MACHINE_ATTRS = [ host, macAddress, hostAlias, ipHostNumber, portTCPout, portTCPin, portUDPout, portUDPin ] CRANS_ATTRIBUTES = { 'objectClass' : objectClass, 'cn' : cn, 'dn' : dn, 'aid': aid, 'uid': uid, 'nom' : nom, 'prenom' : prenom, 'compteWiki' : compteWiki, 'tel' : tel, 'paiement' : paiement, 'controle': controle, 'derniereConnexion' : derniereConnexion, 'carteEtudiant' : carteEtudiant, 'mailAlias' : mailAlias, 'canonicalAlias' : canonicalAlias, 'etudes' : etudes, 'chbre' : chbre, 'droits' : droits, 'solde' : solde, 'gpgFingerprint' : gpgFingerprint, 'mid' : mid, 'rid' : rid, 'host' : host, 'sshFingerprint' : sshFingerprint, 'macAddress': macAddress, 'ipHostNumber': ipHostNumber, 'ip6HostNumber': ip6HostNumber, 'hostAlias' : hostAlias, 'ipsec' : ipsec, 'puissance' : puissance, 'canal' : canal, 'portTCPout' : portTCPout, 'portTCPin' : portTCPin, 'portUDPout' : portUDPout, 'portUDPin' : portUDPin, 'exempt' : exempt, 'prise' : prise, 'nombrePrises' : nombrePrises, 'hotspot' : hotspot, 'mailInvalide' : mailInvalide, 'positionBorne' : positionBorne, 'nvram' : nvram, 'contourneGreylist' : contourneGreylist, 'imprimeurClub' : imprimeurClub, 'fid' : fid, 'dnsIpv6' : dnsIpv6, 'machineAlias' : machineAlias, 'modePaiement' : modePaiement, 'recuPaiement' : recuPaiement, 'cid' : cid, 'responsable' : responsable, 'blacklist' : blacklist, 'historique' : historique, 'info': info, 'homepageAlias': homepageAlias, 'charteMA': charteMA, 'mail' : mail, 'postalAddress': postalAddress, # {posix,shadow}Account 'homeDirectory': homeDirectory, 'loginShell': loginShell, 'uidNumber': uidNumber, 'gecos': gecos, 'gidNumber': gidNumber }