1838 lines
55 KiB
Python
1838 lines
55 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, fetch_cert_info
|
||
from crans_utils import to_generalized_time_format, from_generalized_time_format
|
||
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, webradio]
|
||
#: Liste des droits forts
|
||
DROITS_ELEVES = [nounou, bureau, tresorier]
|
||
#: 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(config.in_encoding)
|
||
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 __setitem__(self, index, value):
|
||
self._start()
|
||
super(AttrsList, self).__setitem__(index, value)
|
||
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
|
||
concurrent = True
|
||
unique_exclue = []
|
||
#: Le nom de l'attribut dans le schéma LDAP
|
||
ldap_name = None
|
||
python_type = None
|
||
binary = False
|
||
default = 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 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 __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(config.out_encoding)
|
||
|
||
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 + self.unique_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', 'inetOrgPerson', 'posixAccount', 'shadowAccount',
|
||
'proprio', 'adherent', 'club', 'machine', 'machineCrans',
|
||
'borneWifi', 'machineWifi', 'machineFixe', 'x509Cert', 'TLSACert',
|
||
'baseCert', 'cransAccount', 'service', 'facture', 'freeMid', 'privateKey' ]:
|
||
raise ValueError("Pourquoi insérer un objectClass=%r ?" % val)
|
||
else:
|
||
self.value = unicode(val)
|
||
|
||
|
||
class intAttr(Attr):
|
||
|
||
python_type = int
|
||
|
||
def __add__(self, obj):
|
||
if isinstance(obj, self.__class__):
|
||
return self.value.__add__(obj.value)
|
||
else:
|
||
return self.value.__add__(obj)
|
||
|
||
def __sub__(self, obj):
|
||
if isinstance(obj, self.__class__):
|
||
return self.value.__sub__(obj.value)
|
||
else:
|
||
return self.value.__sub__(obj)
|
||
|
||
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 floatAttr(Attr):
|
||
|
||
python_type = float
|
||
|
||
def __add__(self, obj):
|
||
if isinstance(obj, self.__class__):
|
||
return self.value.__add__(obj.value)
|
||
else:
|
||
return self.value.__add__(obj)
|
||
|
||
def __sub__(self, obj):
|
||
if isinstance(obj, self.__class__):
|
||
return self.value.__sub__(obj.value)
|
||
else:
|
||
return self.value.__sub__(obj)
|
||
|
||
def parse_value(self, 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
|
||
can_modify = [nounou]
|
||
ldap_name = "uid"
|
||
|
||
@crans_attribute
|
||
class preferedLanguage(Attr):
|
||
singlevalue = True
|
||
option = True
|
||
legend = u"La langue préférée de l'adhérent"
|
||
category = 'perso'
|
||
unique = False
|
||
ldap_name = "preferedLanguage"
|
||
|
||
@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(Attr):
|
||
legend = u"Carte d'étudiant"
|
||
category = 'perso'
|
||
can_modify = [cableur, nounou, tresorier]
|
||
ldap_name = "carteEtudiant"
|
||
|
||
def parse_value(self, data):
|
||
self.value = data
|
||
|
||
@crans_attribute
|
||
class derniereConnexion(intAttr):
|
||
legend = u"Dernière connexion"
|
||
can_modify = [nounou, cableur, soi] # il faut bien pouvoir le modifier pour le mettre à jour
|
||
ldap_name = "derniereConnexion"
|
||
|
||
class generalizedTimeFormat(Attr):
|
||
"""Classe définissant un ensemble de données pour manipuler
|
||
une donnée de temps suivant la RFC 4517
|
||
|
||
"""
|
||
default = "19700101000000Z"
|
||
|
||
def __float__(self):
|
||
return self._stamp
|
||
|
||
def __int__(self):
|
||
return int(self._stamp)
|
||
|
||
def __eq__(self, othertime):
|
||
if isinstance(othertime, generalizedTimeFormat):
|
||
return self._stamp == othertime._stamp
|
||
else:
|
||
resource = generalizedTimeFormat(othertime, conn=None, Parent=None)
|
||
return self._stamp == resource._stamp
|
||
|
||
def __neq__(self, othertime):
|
||
return not (self == othertime)
|
||
|
||
def __lt__(self, othertime):
|
||
if isinstance(othertime, generalizedTimeFormat):
|
||
return self._stamp < othertime._stamp
|
||
else:
|
||
resource = generalizedTimeFormat(othertime, conn=None, Parent=None)
|
||
return self._stamp < resource._stamp
|
||
|
||
def __le__(self, othertime):
|
||
return not (self > othertime)
|
||
|
||
def __ge__(self, othertime):
|
||
return not (self < othertime)
|
||
|
||
def __gt__(self, othertime):
|
||
return not (self < othertime) and not (self == othertime)
|
||
|
||
def parse_value(self, gtf):
|
||
if isinstance(gtf, str) or isinstance(gtf, unicode):
|
||
if not ('Z' in gtf or '+' in gtf or '-' in gtf):
|
||
self._stamp = gtf
|
||
self.value = to_generalized_time_format(float(gtf))
|
||
else:
|
||
self._stamp = from_generalized_time_format(gtf)
|
||
self.value = gtf
|
||
elif isinstance(gtf, float):
|
||
self._stamp = gtf
|
||
self.value = to_generalized_time_format(gtf)
|
||
|
||
@crans_attribute
|
||
class debutAdhesion(generalizedTimeFormat):
|
||
legend = u"Date de début de l'adhésion"
|
||
category = 'Adh'
|
||
can_modify = [cableur, nounou]
|
||
ldap_name = 'debutAdhesion'
|
||
|
||
|
||
@crans_attribute
|
||
class finAdhesion(generalizedTimeFormat):
|
||
legend = u"Date de fin de l'adhésion"
|
||
category = 'Adh'
|
||
can_modify = [cableur, nounou]
|
||
ldap_name = 'finAdhesion'
|
||
|
||
@crans_attribute
|
||
class debutConnexion(generalizedTimeFormat):
|
||
legend = u"Date de début de la connexion"
|
||
category = 'Adh'
|
||
can_modify = [cableur, nounou]
|
||
ldap_name = 'debutConnexion'
|
||
|
||
@crans_attribute
|
||
class finConnexion(generalizedTimeFormat):
|
||
legend = u"Date de fin de la connexion"
|
||
category = 'Adh'
|
||
can_modify = [cableur, nounou]
|
||
ldap_name = 'finConnexion'
|
||
|
||
@crans_attribute
|
||
class mail(Attr):
|
||
singlevalue = False
|
||
optional = False
|
||
unique = True
|
||
legend = u"Adresse mail de l'adhérent"
|
||
can_modify = [soi, nounou, cableur]
|
||
category = 'mail'
|
||
ldap_name = "mail"
|
||
|
||
def is_modifiable(self, liste_droits):
|
||
"""
|
||
Une adresse mail n'est modifiable que si on a au moins autant de droits
|
||
que la personne à qui est l'adresse mail
|
||
"""
|
||
modifiables = set()
|
||
for i in liste_droits:
|
||
if i in DROITS_SUPERVISEUR:
|
||
modifiables = modifiables.union(DROITS_SUPERVISEUR[i])
|
||
modifiables = list(modifiables)
|
||
|
||
for droit in self.parent.get('droits', []):
|
||
if droit not in modifiables and droit in TOUS_DROITS:
|
||
return False
|
||
return super(mail, self).is_modifiable(liste_droits)
|
||
|
||
|
||
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"
|
||
|
||
# à spécifier pour les nouvelles valeurs
|
||
# 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"
|
||
|
||
@crans_attribute
|
||
class mailExt(mail):
|
||
singlevalue = False
|
||
optional = True
|
||
unique = True
|
||
legend = u"Mail de secours"
|
||
can_modify = [soi, cableur, nounou]
|
||
category = 'mail'
|
||
ldap_name = "mailExt"
|
||
|
||
def parse_value(self, mail):
|
||
# comme on utilise mailExt comme mail de secours si l'utilisateur
|
||
# à perdu ses id crans, ça ne sert à rien de mettre ne adresse crans
|
||
if mail.endswith("@crans.org"):
|
||
raise ValueError("%s ne devrait pas être une adresse crans." % str(self.legend))
|
||
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 = True
|
||
unique_exclue = [u'????', u'EXT']
|
||
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:
|
||
annuaires_pg.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]
|
||
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 ?
|
||
L'idée étant que pour modifier un droit, il faut avoir un droit plus fort
|
||
"""
|
||
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 and super(droits, self).is_modifiable(liste_droits)
|
||
|
||
@crans_attribute
|
||
class solde(floatAttr):
|
||
python_type = float
|
||
singlevalue = True
|
||
concurrent = False
|
||
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, cableur]
|
||
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'
|
||
# beware: parent est nécessaire à la modification des adresses macs par
|
||
# les adhérents lambda (see validate_changes)
|
||
can_modify = [nounou, cableur, parent]
|
||
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"
|
||
can_modify = [nounou, parent]
|
||
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"
|
||
python_type = netaddr.IPNetwork
|
||
|
||
def parse_value(self, net):
|
||
self.value = self.python_type(net)
|
||
|
||
def __unicode__(self):
|
||
return unicode(self.value)
|
||
|
||
|
||
@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(cid=%s) : l'adhérent aid=%r n'existe pas ou plus" % (self.parent['cid'][0], 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:
|
||
try:
|
||
bl_debut, bl_fin, bl_type, bl_comm = blacklist.split('$')
|
||
except:
|
||
print "valeur = %s" % blacklist
|
||
raise
|
||
|
||
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
|
||
concurrent = False
|
||
optional = True
|
||
legend = u"Historique de l'objet"
|
||
category = 'info'
|
||
ldap_name = "historique"
|
||
# Il faudrait faire quelque chose pour que soi, parent, cableur ne puissent que ajouter de nouvelles lignes
|
||
can_modify = [nounou, soi, parent, cableur]
|
||
|
||
# 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
|
||
can_modify = [nounou, bureau, cableur]
|
||
legend="Le chemin du home de l'adhérent"
|
||
ldap_name = "homeDirectory"
|
||
|
||
def is_modifiable(self, liste_droits):
|
||
"""
|
||
Une adresse mail n'est modifiable que si on a au moins autant de droits
|
||
que la personne à qui est l'adresse mail
|
||
"""
|
||
modifiables = set()
|
||
for i in liste_droits:
|
||
if i in DROITS_SUPERVISEUR:
|
||
modifiables = modifiables.union(DROITS_SUPERVISEUR[i])
|
||
modifiables = list(modifiables)
|
||
|
||
for droit in self.parent.get('droits', []):
|
||
if droit not in modifiables and droit in TOUS_DROITS:
|
||
return False
|
||
return super(homeDirectory, self).is_modifiable(liste_droits)
|
||
|
||
@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 = config.shells_possibles
|
||
if shell not in shells:
|
||
raise ValueError("Shell %r invalide.\nLes shells valident sont : %s" % (shell, ', '.join(shells)))
|
||
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"
|
||
can_modify = [nounou, bureau, cableur]
|
||
|
||
def is_modifiable(self, liste_droits):
|
||
"""
|
||
Le mot de passe n'est modifiable que si on a au moins autant de droits
|
||
que la personne à qui est le mot de passe
|
||
"""
|
||
modifiables = set()
|
||
for i in liste_droits:
|
||
if i in DROITS_SUPERVISEUR:
|
||
modifiables = modifiables.union(DROITS_SUPERVISEUR[i])
|
||
modifiables = list(modifiables)
|
||
|
||
for droit in self.parent.get('droits', []):
|
||
if droit not in modifiables and droit in TOUS_DROITS:
|
||
return False
|
||
return super(userPassword, self).is_modifiable(liste_droits)
|
||
|
||
@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", u"TRUE", u"FALSE"]:
|
||
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
|
||
can_modify = [cableur, nounou]
|
||
ldap_name = "modePaiement"
|
||
|
||
def parse_value(self, mode):
|
||
if not mode in config.modePaiement:
|
||
raise ValueError("%s n'est pas un moyen de paiement accepté. Les moyens accepté sont %s. (debug=%s)" % (mode.encode(config.out_encoding), ", ".join(config.modePaiement), self.parent.dn))
|
||
self.value = mode
|
||
|
||
@crans_attribute
|
||
class recuPaiement(Attr):
|
||
ldap_name = "recuPaiement"
|
||
can_modify = [cableur, nounou]
|
||
|
||
@crans_attribute
|
||
class article(Attr):
|
||
singlevalue = False
|
||
optional = True
|
||
legend = u"Articles"
|
||
category = 'facture'
|
||
can_modify = [cableur, nounou, tresorier]
|
||
ldap_name = "article"
|
||
python_type = dict
|
||
|
||
python_type_keys = ['code', 'designation', 'nombre', 'pu']
|
||
|
||
def parse_value(self, article):
|
||
if isinstance(article, self.python_type):
|
||
for key in self.python_type_keys:
|
||
if key not in article:
|
||
raise ValueError("Un article devrait avoir '%s'" % key)
|
||
art_code = article['code']
|
||
art_designation = article['designation']
|
||
art_nombre = article['nombre']
|
||
art_pu = article['pu']
|
||
else:
|
||
art_code, art_designation, art_nombre, art_pu = article.split('~~')
|
||
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 self.python_type_keys:
|
||
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"
|
||
can_modify = [nounou, parent, cableur]
|
||
|
||
@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 encrypted(boolAttr):
|
||
ldap_name = "encrypted"
|
||
singlevalue = True
|
||
optional = True
|
||
can_modify = [nounou, parent]
|
||
legend = "Détermine si une clef privée est chiffrée"
|
||
|
||
@crans_attribute
|
||
class privatekey(Attr):
|
||
ldap_name = "privatekey"
|
||
python_type = str
|
||
can_modify = [parent, nounou]
|
||
legend = "Clef privée"
|
||
|
||
@crans_attribute
|
||
class csr(Attr):
|
||
ldap_name = "csr"
|
||
python_type = str
|
||
can_modify = [parent, nounou]
|
||
legend = "requête de signature de certificat"
|
||
|
||
def _format_cert(self, csr):
|
||
import OpenSSL
|
||
try:
|
||
x509 = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr)
|
||
except:
|
||
print csr
|
||
raise
|
||
data = fetch_cert_info(x509)
|
||
return data
|
||
|
||
def parse_value(self, csr):
|
||
if self.parent.mode in ['w', 'rw']:
|
||
data = self._format_cert(csr)
|
||
self.data = data
|
||
|
||
for hostname in [data['subject']['CN']] + data['extensions'].get('subjectAltName',[]):
|
||
if hostname not in self.parent['hostCert']:
|
||
self.parent['hostCert'].append(unicode(hostname))
|
||
|
||
self.value = csr
|
||
|
||
@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 _format_cert(self, certificat):
|
||
import OpenSSL
|
||
try:
|
||
if certificat.startswith('-----BEGIN CERTIFICATE-----'):
|
||
certificat = ssl.PEM_cert_to_DER_cert(certificat)
|
||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, certificat)
|
||
except Exception:
|
||
try:
|
||
certificat = base64.b64decode(certificat)
|
||
x509 = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, certificat)
|
||
except Exception:
|
||
raise ValueError("Format du certificat invalide, est-il bien au format DER ou PEM ?")
|
||
data = fetch_cert_info(x509)
|
||
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(unicode(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):
|
||
# Pour les machine crans, on dit ques les nounous
|
||
# savent ce qu'elle font (et puis, c'est pratique quand des
|
||
# alias migrent vers des vm)
|
||
if not "machineCrans" in self.parent.machine()["objectClass"]:
|
||
if not host in self.parent.machine()['host'] + self.parent.machine()['hostAlias']:
|
||
raise ValueError("hostCert (%s) doit être inclus dans les host et hostAlias de la machine parente : %s ou %s" % (str(host), ', '.join(str(item) for item in self.parent.machine()['host'] + self.parent.machine()['hostAlias'])))
|
||
self.value = host
|
||
|
||
@crans_attribute
|
||
class shadowExpire(intAttr):
|
||
"""
|
||
Durée de validité du mot de passe d'un compte.
|
||
On l'utilise en mettant sa valeur à 0 pour désactiver un compte
|
||
"""
|
||
optinal = True
|
||
can_modify = [nounou, bureau]
|
||
ldap_name = "shadowExpire"
|