
À noter que __getattr__ n'est appelé que si l'attribut n'existe pas déjà. Ça permet d'utiliser l'objet Attr comme sa valeur pour la plupart des opération de lecture simple. J'ai fait exprès de ne pas surcharger __setattr__, parce que sinon, on ne sait plus trop ce qui va être affecté. J'estime que les écriture doivent être traités au cas par cas comme pour blacklist ou pour article.
1424 lines
41 KiB
Python
1424 lines
41 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
|
|
""" Définition des classes permettant d'instancier les attributs LDAP. """
|
|
|
|
#
|
|
# Copyright (C) 2010-2013 Cr@ns <roots@crans.org>
|
|
# Authors: Antoine Durand-Gasselin <adg@crans.org>
|
|
# Nicolas Dandrimont <olasd@crans.org>
|
|
# Valentin Samir <samir@crans.org>
|
|
# Vincent Le Gallic <legallic@crans.org>
|
|
# Pierre-Elliott Bécue <becue@crans.org>
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
# * Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
# * Neither the name of the Cr@ns nor the names of its contributors may
|
|
# be used to endorse or promote products derived from this software
|
|
# without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT
|
|
# HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
import re
|
|
import sys
|
|
import 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:
|
|
attr_classe = AttributeFactory.get(attr, fallback=Attr)
|
|
if not isinstance(val, unicode) and not (attr_classe.python_type and isinstance(val, attr_classe.python_type)):
|
|
cranslib.deprecated.usage("attrify ne devrait être appelé qu'avec des unicode (%r)" % val, level=3)
|
|
val = val.decode('utf-8')
|
|
return attr_classe(val, conn, Parent)
|
|
|
|
class AttrsList(list):
|
|
def __init__(self, Parent, attr, attr_list):
|
|
super(AttrsList, self).__init__(attr_list)
|
|
self._parent = Parent
|
|
self._attr = attr
|
|
|
|
def _start(self):
|
|
if self._parent[self._attr] != self:
|
|
raise ValueError("La variable que vous utilisez n'est pas à jour (modifications conccurentes ?)")
|
|
|
|
def _commit(self):
|
|
try:
|
|
self._parent[self._attr] = self
|
|
finally:
|
|
super(AttrsList, self).__init__(self._parent[self._attr])
|
|
|
|
def __delitem__(self, index):
|
|
self._start()
|
|
super(AttrsList, self).__delitem__(index)
|
|
self._commit()
|
|
|
|
def append(self, val):
|
|
self._start()
|
|
super(AttrsList, self).append(val)
|
|
self._commit()
|
|
|
|
def extend(self, vals):
|
|
self._start()
|
|
super(AttrsList, self).extend(vals)
|
|
self._commit()
|
|
|
|
def insert(self, pos, val):
|
|
self._start()
|
|
super(AttrsList, self).insert(pos, val)
|
|
self._commit()
|
|
|
|
def remove(self, val):
|
|
self._start()
|
|
super(AttrsList, self).remove(val)
|
|
self._commit()
|
|
|
|
def sort(self):
|
|
self._start()
|
|
super(AttrsList, self).sort()
|
|
self._commit()
|
|
|
|
def pop(self, index=None):
|
|
self._start()
|
|
ret = super(AttrsList, self).pop(index) if index else super(AttrsList, self).pop()
|
|
self._commit()
|
|
return ret
|
|
|
|
def reverse(self):
|
|
self._start()
|
|
super(AttrsList, self).reverse()
|
|
self._commit()
|
|
|
|
class AttrsDict(dict):
|
|
def __init__(self, conn, uldif={}, Parent=None):
|
|
super(AttrsDict, self).__init__(uldif)
|
|
self._conn = conn
|
|
self._parent = Parent
|
|
self._iterator = None
|
|
|
|
def __getitem__(self, attr):
|
|
values = super(AttrsDict, self).__getitem__(attr)
|
|
if not isinstance(values, list):
|
|
values = [values]
|
|
output = []
|
|
for val in values:
|
|
output.append(attrify(val, attr, self._conn, self._parent))
|
|
self[attr] = output
|
|
return output
|
|
|
|
def __setitem__(self, attr, values):
|
|
# ne devrait par arriver
|
|
if not isinstance(values, list):
|
|
values = [ values ]
|
|
if self._parent.mode in ['w', 'rw']:
|
|
if AttributeFactory.get(attr, fallback=Attr).singlevalue and len(values) > 1:
|
|
raise SingleValueError("L'attribut %s doit être monovalué." % (attr,))
|
|
super(AttrsDict, self).__setitem__(attr, values)
|
|
else:
|
|
super(AttrsDict, self).__setitem__(attr, values)
|
|
|
|
def get(self, value, default_value):
|
|
try:
|
|
return self[value]
|
|
except KeyError:
|
|
return default_value
|
|
|
|
def items(self):
|
|
return [(key, self[key]) for key in self]
|
|
|
|
def to_ldif(self):
|
|
"""
|
|
Transforme le dico en ldif valide au sens openldap.
|
|
Ce ldif est celui qui sera transmis à la base.
|
|
"""
|
|
ldif = {}
|
|
for attr, vals in self.items():
|
|
ldif[attr] = [ str(val) for val in vals ]
|
|
return ldif
|
|
|
|
class Attr(object):
|
|
"""Objet représentant un attribut.
|
|
|
|
**Paramètres :**
|
|
|
|
* ``val`` : valeur de l'attribut
|
|
* ``ldif`` : objet contenant l'attribut (permet de faire les validations sur l'environnement)
|
|
"""
|
|
legend = "Human-readable description of attribute"
|
|
singlevalue = False
|
|
optional = True
|
|
conn = None
|
|
unique = False
|
|
#: Le nom de l'attribut dans le schéma LDAP
|
|
ldap_name = None
|
|
python_type = None
|
|
|
|
"""La liste des droits qui suffisent à avoir le droit de modifier la valeur"""
|
|
can_modify = [nounou]
|
|
|
|
"""Qui peut voir l'attribut. Par défaut, les Nounous et les Apprentis
|
|
peuvent tout voir. Par transparence, et par utilité, on autorise par
|
|
défaut l'adhérent à voir les données le concernant."""
|
|
can_view = [nounou, apprenti, soi, parent, respo]
|
|
|
|
"""Catégorie de l'attribut (pour affichage futur)"""
|
|
category = 'other'
|
|
|
|
def __init__(self, val, conn, Parent):
|
|
"""Crée un nouvel objet représentant un attribut."""
|
|
self.value = None
|
|
self.conn = conn
|
|
assert isinstance(val, unicode) or (self.python_type and isinstance(val, self.python_type))
|
|
self.parent = Parent
|
|
self.parse_value(val)
|
|
|
|
def __hash__(self):
|
|
if hasattr(self.value, "__hash__") and self.value.__hash__ is not None:
|
|
return self.value.__hash__()
|
|
else:
|
|
return str(self).__hash__()
|
|
|
|
def __getattr__(self, name):
|
|
return super(Attr, self).__getattribute__('value').__getattribute__(name)
|
|
|
|
def __eq__(self, item):
|
|
if isinstance(item, self.__class__):
|
|
return str(self) == str(item)
|
|
elif self.python_type and isinstance(item, self.python_type):
|
|
return self.value == item
|
|
elif isinstance(item, str):
|
|
return str(self) == item
|
|
elif isinstance(item, unicode):
|
|
return unicode(self) == item
|
|
else:
|
|
return False
|
|
|
|
def __cmp__(self, obj):
|
|
if isinstance(obj, Attr):
|
|
return self.value.__cmp__(obj.value)
|
|
else:
|
|
return self.value.__cmp__(obj)
|
|
|
|
def __nonzero__(self):
|
|
return self.value.__nonzero__()
|
|
|
|
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):
|
|
|
|
python_type = int
|
|
|
|
def parse_value(self, val):
|
|
if self.python_type(val) <= 0:
|
|
raise ValueError("Valeur entière invalide : %r" % val)
|
|
self.value = self.python_type(val)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value)
|
|
|
|
class boolAttr(Attr):
|
|
|
|
python_type = bool
|
|
|
|
def parse_value(self, val):
|
|
if isinstance(val, self.python_type):
|
|
self.value = val
|
|
elif val.lower() in [u'true', u'ok']:
|
|
self.value = True
|
|
elif val.lower() == u'false':
|
|
self.value = False
|
|
else:
|
|
raise ValueError("%r doit être un booléen !" % val)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value).upper()
|
|
|
|
@crans_attribute
|
|
class aid(intAttr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend = u"Identifiant de l'adhérent"
|
|
category = 'id'
|
|
unique = True
|
|
ldap_name = "aid"
|
|
|
|
""" Personne ne devrait modifier un attribut d'identification """
|
|
can_modify = []
|
|
|
|
can_view = [nounou, apprenti, cableur]
|
|
|
|
def parse_value(self, aid):
|
|
self.value = int(aid)
|
|
|
|
@crans_attribute
|
|
class uid(Attr):
|
|
singlevalue = True
|
|
option = False
|
|
legend = u"L'identifiant canonique de l'adhérent"
|
|
category = 'perso'
|
|
unique = True
|
|
ldap_name = "uid"
|
|
|
|
@crans_attribute
|
|
class nom(Attr):
|
|
singlevalue = True
|
|
optional = False
|
|
legend = "Nom"
|
|
category = 'perso'
|
|
can_modify = [nounou, cableur]
|
|
ldap_name = "nom"
|
|
|
|
def parse_value(self, nom):
|
|
if self.parent != None:
|
|
if u'club' in [o.value for o in self.parent['objectClass']]:
|
|
self.value = validate_name(nom,"0123456789\[\]")
|
|
else:
|
|
self.value = validate_name(nom)
|
|
else:
|
|
self.value = validate_name(nom)
|
|
|
|
@crans_attribute
|
|
class prenom(Attr):
|
|
singlevalue = True
|
|
optional = False
|
|
legend = u"Prénom"
|
|
category = 'perso'
|
|
can_modify = [nounou, cableur]
|
|
ldap_name = "prenom"
|
|
|
|
def parse_value(self, prenom):
|
|
self.value = validate_name(prenom)
|
|
|
|
@crans_attribute
|
|
class compteWiki(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
legend = u"Compte WiKi"
|
|
category = 'perso'
|
|
can_modify = [nounou, cableur, soi]
|
|
ldap_name = "compteWiki"
|
|
|
|
def parse_value(self, compte):
|
|
self.value = validate_name(compte)
|
|
# TODO: validate with mdp for user definition here ?
|
|
|
|
@crans_attribute
|
|
class tel(Attr):
|
|
singlevalue = True
|
|
optional = False
|
|
legend = u"Téléphone"
|
|
category = 'perso'
|
|
can_modify = [soi, nounou, cableur]
|
|
ldap_name = "tel"
|
|
|
|
def parse_value(self, tel):
|
|
self.value = format_tel(tel)
|
|
if len(self.value) == 0:
|
|
raise ValueError("Numéro de téléphone invalide (%r)" % tel)
|
|
|
|
class yearAttr(intAttr):
|
|
singlevalue = False
|
|
optional = True
|
|
|
|
def parse_value(self, annee):
|
|
annee = self.python_type(annee)
|
|
if annee < 1998:
|
|
raise ValueError("Année invalide (%r)" % annee)
|
|
self.value = annee
|
|
|
|
@crans_attribute
|
|
class paiement(yearAttr):
|
|
legend = u"Paiement"
|
|
can_modify = [cableur, nounou, tresorier]
|
|
category = 'perso'
|
|
ldap_name = "paiement"
|
|
|
|
@crans_attribute
|
|
class carteEtudiant(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:
|
|
if i in DROITS_SUPERVISEUR:
|
|
modifiables = modifiables.union(DROITS_SUPERVISEUR[i])
|
|
modifiables = list(modifiables)
|
|
|
|
return self.value in modifiables
|
|
|
|
@crans_attribute
|
|
class solde(Attr):
|
|
python_type = float
|
|
singlevalue = True
|
|
optional = True
|
|
legend = u"Solde d'impression"
|
|
can_modify = [imprimeur, nounou, tresorier]
|
|
ldap_name = "solde"
|
|
|
|
def parse_value(self, solde):
|
|
# on évite les dépassements, sauf si on nous dit de ne pas vérifier
|
|
#if not (float(solde) >= config.impression.decouvert and float(solde) <= 1024.):
|
|
# raise ValueError("Solde invalide: %r" % solde)
|
|
self.value = self.python_type(solde)
|
|
|
|
def __unicode__(self):
|
|
return u"%.2f" % self.value
|
|
|
|
class dnsAttr(Attr):
|
|
category = 'dns'
|
|
ldap_name = "dnsAttr"
|
|
|
|
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"
|
|
python_type = netaddr.IPAddress
|
|
default = u'<automatique>'
|
|
|
|
def parse_value(self, ip):
|
|
if ip == '<automatique>':
|
|
ip = ip4_of_rid(str(self.parent['rid'][0]))
|
|
self.value = self.python_type(ip)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value)
|
|
|
|
@crans_attribute
|
|
class ip6HostNumber(Attr):
|
|
singlevalue = True
|
|
optional = True
|
|
unique = True
|
|
legend = u"Adresse IPv6 de la machine"
|
|
hname = "IPv6"
|
|
category = 'base_tech'
|
|
can_modify = [nounou]
|
|
ldap_name = "ip6HostNumber"
|
|
python_type = netaddr.IPAddress
|
|
default = u'<automatique>'
|
|
|
|
def parse_value(self, ip6):
|
|
if ip6 == '<automatique>':
|
|
ip6 = ip6_of_mac(str(self.parent['macAddress'][0]), int(str(self.parent['rid'][0])))
|
|
self.value = self.python_type(ip6)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value)
|
|
|
|
@crans_attribute
|
|
class mid(intAttr):
|
|
singlevalue = True
|
|
optional = False
|
|
unique = True
|
|
legend = u"Identifiant de machine"
|
|
category = 'id'
|
|
ldap_name = "mid"
|
|
|
|
@crans_attribute
|
|
class rid(intAttr):
|
|
singlevalue = True
|
|
optional = True
|
|
unique = True
|
|
legend = u"Identifiant réseau de machine"
|
|
category = 'id'
|
|
can_modify = [nounou]
|
|
ldap_name = "rid"
|
|
|
|
def parse_value(self, rid):
|
|
rid = self.python_type(rid)
|
|
|
|
# On veut éviter les rid qui recoupent les ipv4 finissant par
|
|
# .0 ou .255
|
|
plages = [itertools.chain(*[xrange(plage[0], plage[1]+1) for plage in value]) for (key, value) in config.rid_primaires.iteritems() if ('v6' not in key) and ('special' not in key)]
|
|
|
|
for plage in plages:
|
|
if rid in plage:
|
|
if rid % 256 == 0 or rid % 256 == 255:
|
|
rid = -1
|
|
break
|
|
else:
|
|
continue
|
|
else:
|
|
continue
|
|
self.value = rid
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value)
|
|
|
|
@crans_attribute
|
|
class ipsec(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
legend = u'Clef wifi'
|
|
category = 'wifi'
|
|
ldap_name = "ipsec"
|
|
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"
|
|
python_type = int # en vrai, c'est l'aid du responsable
|
|
|
|
def nonefunction(self, resp):
|
|
"""
|
|
Just... do... nothing.
|
|
L'idée est qu'on initialise self.value à None
|
|
dans Attr. Simplement ici, self.value est une property,
|
|
donc il faut une fonction pour l'attribution.
|
|
"""
|
|
pass
|
|
|
|
def get_respo(self):
|
|
"""Méthode spéciale, pour aller avec
|
|
property. On génère le respo quand
|
|
c'est nécessaire, pas avant."""
|
|
if hasattr(self, "_value"):
|
|
return self._value
|
|
else:
|
|
try:
|
|
res = self.conn.search(u'aid=%s' % self.__value)[0]
|
|
except IndexError:
|
|
raise ValueError("get_respo: L'adherent %s n'existe pas ou plus" % (self.__value))
|
|
self._value = res
|
|
return res
|
|
|
|
def parse_value(self, resp):
|
|
self.__value = resp
|
|
|
|
value = property(get_respo, nonefunction)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.__value)
|
|
|
|
@crans_attribute
|
|
class imprimeurClub(Attr):
|
|
optional = True
|
|
legend = u"Imprimeur du club"
|
|
category = "perso"
|
|
can_modify = [cableur, nounou]
|
|
ldap_name = "imprimeurClub"
|
|
|
|
def nonefunction(self, imprimeur):
|
|
"""
|
|
Just... do... nothing.
|
|
L'idée est qu'on initialise self.value à None
|
|
dans Attr. Simplement ici, self.value est une property,
|
|
donc il faut une fonction pour l'attribution.
|
|
"""
|
|
pass
|
|
|
|
def get_imprimeur(self):
|
|
if hasattr(self, "_value"):
|
|
return self._value
|
|
else:
|
|
try:
|
|
res = self.conn.search(u'aid=%s' % int(self.__value))[0]
|
|
except IndexError:
|
|
raise ValueError("get_imprimeur: L'adhérent aid=%r n'existe pas ou plus" % self.__value)
|
|
self._value = res
|
|
return res
|
|
|
|
def parse_value(self, imprimeur):
|
|
self.__value = imprimeur
|
|
|
|
value = property(get_imprimeur, nonefunction)
|
|
|
|
def __unicode__(self):
|
|
return unicode(self.value['aid'][0])
|
|
|
|
@crans_attribute
|
|
class blacklist(Attr):
|
|
singlevalue = False
|
|
optional = True
|
|
legend = u"Blackliste"
|
|
category = 'info'
|
|
can_modify = [nounou]
|
|
ldap_name = "blacklist"
|
|
python_type = dict
|
|
|
|
def parse_value(self, blacklist):
|
|
if isinstance(blacklist, self.python_type):
|
|
if set(blacklist.keys()).issuperset(['debut', 'fin', 'type', 'comm']):
|
|
bl_debut, bl_fin, bl_type, bl_comm = (blacklist['debut'], blacklist['fin'], blacklist['type'], blacklist['comm'])
|
|
else:
|
|
raise ValueError("A blacklist should contain the following keys : 'debut', 'fin', 'type', 'comm'")
|
|
else:
|
|
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(boolAttr):
|
|
singlevalue = True
|
|
optional = True
|
|
legend= "Signale si l'adhérent a signé la charte de membres actifs"
|
|
can_modify = [nounou, bureau]
|
|
category = 'perso'
|
|
ldap_name = "charteMA"
|
|
|
|
@crans_attribute
|
|
class homeDirectory(Attr):
|
|
singlevalue=True
|
|
optional = True
|
|
unique = True
|
|
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"
|
|
python_type = dict
|
|
|
|
def parse_value(self, key):
|
|
if isinstance(key, self.python_type):
|
|
if set(key.keys()).issuperset(['type', 'key']):
|
|
details = [ key['type'], key['key'] ]
|
|
if 'comm' in key:
|
|
details.append(key['comm'])
|
|
else:
|
|
raise ValueError("sshFingerprint should have a type dans a key field")
|
|
else:
|
|
details = key.split()
|
|
if len(details)<2:
|
|
raise ValueError("Une clef ssh devrait être de la forme : 'format clef commentaire' comme 'ssh-dss AAAAB3NzaC… root@mon-pc' par exemple")
|
|
self.value = { 'type' : details[0],
|
|
'key' : details[1],
|
|
'comm' : ' '.join(details[2:]),
|
|
}
|
|
def __getitem__(self, attr):
|
|
return self.value.__getitem__(attr)
|
|
|
|
def __setitem__(self, attr, values):
|
|
if attr in ['comm']:
|
|
ret=self.value.__setitem__(attr, values)
|
|
self.parse_value(unicode(self))
|
|
return ret
|
|
else:
|
|
raise ValueError("sshFingerprint has no %r editable" % attr)
|
|
|
|
def __unicode__(self):
|
|
return u'%(type)s %(key)s %(comm)s' % self.value
|
|
|
|
@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 rewriteMailHeaders(boolAttr):
|
|
ldap_name = "rewriteMailHeaders"
|
|
|
|
@crans_attribute
|
|
class machineAlias(boolAttr):
|
|
ldap_name = "machineAlias"
|