1616 lines
48 KiB
Python
1616 lines
48 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 ssl
|
|
import netaddr
|
|
import time
|
|
import base64
|
|
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
|
|
from gestion import config
|
|
from gestion 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
|
|
binary = 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) or (self.python_type and isinstance(val, self.python_type))
|
|
self.parent = Parent
|
|
self.parse_value(val)
|
|
|
|
def __hash__(self):
|
|
if not self.parent or self.parent.mode in ['w', 'rw']:
|
|
raise TypeError("Mutable structure are not hashable, please use mode = 'ro' to do so")
|
|
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 getattr(self.value, name)
|
|
|
|
def __ne__(self, obj):
|
|
return not self == obj
|
|
|
|
def __eq__(self, item):
|
|
if isinstance(item, self.__class__):
|
|
return str(self) == str(item)
|
|
elif isinstance(item, Attr) and item.python_type == self.python_type:
|
|
return item.value == self.value
|
|
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):
|
|
if self.value:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
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', 'x509Cert', 'TLSACert',
|
|
'baseCert', '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"
|
|
python_type = unicode
|
|
|
|
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"
|
|
default = u'<automatique>'
|
|
|
|
def parse_value(self, mac):
|
|
mac = mac.lower()
|
|
if mac == macAddress.default:
|
|
self.value = mac
|
|
else:
|
|
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.strip().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:]),
|
|
}
|
|
if not self.value['type'] in config.sshfs_ralgo.keys():
|
|
raise ValueError("Seul les clefs ssh de type %s sont supportées" % ', '.join(config.sshfs_ralgo.keys()))
|
|
try:
|
|
base64.b64decode(self.value['key'])
|
|
except:
|
|
raise ValueError("La partie centrale de la clef ssh devrait être du base64 valide")
|
|
|
|
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).strip()
|
|
|
|
@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"
|
|
legend = "Détermine si l'ipv6 apparait dans le dns"
|
|
|
|
@crans_attribute
|
|
class rewriteMailHeaders(boolAttr):
|
|
ldap_name = "rewriteMailHeaders"
|
|
|
|
@crans_attribute
|
|
class machineAlias(boolAttr):
|
|
ldap_name = "machineAlias"
|
|
|
|
@crans_attribute
|
|
class issuerCN(Attr):
|
|
ldap_name = "issuerCN"
|
|
can_modify = [nounou]
|
|
legend = "Common Name de l'éméteur du certificat"
|
|
|
|
@crans_attribute
|
|
class serialNumber(Attr):
|
|
ldap_name = "serialNumber"
|
|
python_type = int
|
|
can_modify = [nounou]
|
|
legend = "Numéro de série du certificat"
|
|
|
|
@crans_attribute
|
|
class start(intAttr):
|
|
ldap_name = "start"
|
|
can_modify = [nounou]
|
|
legend = "Date de début"
|
|
|
|
@crans_attribute
|
|
class end(intAttr):
|
|
ldap_name = "end"
|
|
can_modify = [nounou]
|
|
legend = "Date de fin"
|
|
|
|
@crans_attribute
|
|
class crlUrl(Attr):
|
|
ldap_name = "crlUrl"
|
|
optional = True
|
|
can_modify = [parent, nounou]
|
|
legend = "Adresse de la liste de révocation pour le certificat"
|
|
|
|
@crans_attribute
|
|
class revocked(boolAttr):
|
|
ldap_name = "revocked"
|
|
singlevalue = True
|
|
optional = True
|
|
can_modify = [nounou]
|
|
legend = "Détermine si le certificat est révoqué"
|
|
|
|
@crans_attribute
|
|
class certificat(Attr):
|
|
ldap_name = "certificat"
|
|
binary = True
|
|
python_type = str
|
|
can_modify = [parent, nounou]
|
|
legend = "Certificat"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(certificat, self).__init__(*args, **kwargs)
|
|
self.data = None
|
|
|
|
def _decode_subjectAltName(self, data):
|
|
from pyasn1.codec.der import decoder
|
|
from pyasn1_modules.rfc2459 import SubjectAltName
|
|
altName = []
|
|
sa_names = decoder.decode(data, asn1Spec=SubjectAltName())[0]
|
|
for name in sa_names:
|
|
name_type = name.getName()
|
|
if name_type == 'dNSName':
|
|
altName.append(unicode(name.getComponent()))
|
|
# Cacert met des othername, du coup, on ignore juste
|
|
# else:
|
|
# raise ValueError("Seulement les dNSName sont supporté pour l'extension de certificat SubjectAltName (et pas %s)" % name_type)
|
|
return altName
|
|
|
|
def _format_cert(self, certificat):
|
|
import OpenSSL
|
|
if certificat.startswith('-----BEGIN CERTIFICATE-----'):
|
|
certificat = ssl.PEM_cert_to_DER_cert(certificat)
|
|
try:
|
|
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, certificat)
|
|
except:
|
|
certificat = base64.b64decode(certificat)
|
|
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, certificat)
|
|
data = {}
|
|
data['subject'] = dict(x509.get_subject().get_components())
|
|
data['issuer'] = dict(x509.get_issuer().get_components())
|
|
data['start'] = int(time.mktime(time.strptime(x509.get_notBefore(), '%Y%m%d%H%M%SZ')))
|
|
data['end'] = int(time.mktime(time.strptime(x509.get_notAfter(), '%Y%m%d%H%M%SZ')))
|
|
data['serialNumber'] = unicode(int(x509.get_serial_number()))
|
|
data['extensions'] = {}
|
|
for i in range(0, x509.get_extension_count()):
|
|
ext = x509.get_extension(i)
|
|
ext_name = ext.get_short_name()
|
|
if ext_name == 'subjectAltName':
|
|
data['extensions'][ext_name] = self._decode_subjectAltName(ext.get_data())
|
|
else:
|
|
data['extensions'][ext_name] = str(ext)
|
|
return (certificat, data)
|
|
|
|
def parse_value(self, certificat):
|
|
if self.parent.mode in ['w', 'rw']:
|
|
(certificat, data) = self._format_cert(certificat)
|
|
self.data = data
|
|
|
|
if not 'x509Cert' in self.parent['objectClass']:
|
|
self.parent.x509(unicode(data['issuer']['CN']), data['start'], data['end'], data['serialNumber'])
|
|
else:
|
|
self.parent['issuerCN'] = unicode(data['issuer']['CN'])
|
|
self.parent['start'] = data['start']
|
|
self.parent['end'] = data['end']
|
|
self.parent['serialNumber'] = data['serialNumber']
|
|
|
|
for hostname in [data['subject']['CN']] + data['extensions'].get('subjectAltName',[]):
|
|
if hostname not in self.parent['hostCert']:
|
|
self.parent['hostCert'].append(hostname)
|
|
|
|
self.value = certificat
|
|
|
|
def __getitem__(self, key):
|
|
if not self.data:
|
|
self.data=self._format_cert(self.value)[1]
|
|
return self.data[key]
|
|
|
|
def pem(self):
|
|
return ssl.DER_cert_to_PEM_cert(self.value)
|
|
|
|
def __unicode__(self):
|
|
return unicode(base64.b64encode(self.value))
|
|
def __str__(self):
|
|
return self.value
|
|
|
|
@crans_attribute
|
|
class certificatUsage(intAttr):
|
|
ldap_name = "certificatUsage"
|
|
singlevalue = True
|
|
can_modify = [parent, nounou]
|
|
legend = "Utilisation du certificat pour un enregistrement TLSA"
|
|
|
|
@crans_attribute
|
|
class selector(intAttr):
|
|
ldap_name = "selector"
|
|
singlevalue = True
|
|
can_modify = [parent, nounou]
|
|
legend = "Détermine si le certificat est complet ou contient juste la clef publique pour TLSA"
|
|
|
|
@crans_attribute
|
|
class matchingType(intAttr):
|
|
ldap_name = "matchingType"
|
|
singlevalue = True
|
|
can_modify = [parent, nounou]
|
|
legend = "Détermine ce que l'on veux mettre dans l'enregistrement TLSA (certificat complet ou hash)"
|
|
|
|
@crans_attribute
|
|
class xid(intAttr):
|
|
ldap_name = "xid"
|
|
category = 'id'
|
|
unique = True
|
|
singlevalue = True
|
|
can_modify = []
|
|
legend = "Identifiant du certificat"
|
|
|
|
|
|
@crans_attribute
|
|
class hostCert(dnsAttr):
|
|
optional = False
|
|
can_modify = [parent, nounou]
|
|
ldap_name = "hostCert"
|
|
"Nom de domaine utilisé pour le certificat"
|
|
|
|
def parse_value(self, host):
|
|
if not host in self.parent.machine()['host'] + self.parent.machine()['hostAlias']:
|
|
raise ValueError("hostCert doit être inclus dans les host et hostAlias de la machine parente : %s" % ', '.join(str(item) for item in self.parent.machine()['host'] + self.parent.machine()['hostAlias']))
|
|
self.value = host
|