lc_ldap/attributs.py
2010-10-17 14:21:37 +02:00

441 lines
12 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# ATTRIBUTS.PY-- Description des attributs ldap
#
# Copyright (C) 2010 Cr@ns <roots@crans.org>
# Author: Antoine Durand-Gasselin <adg@crans.org>
# All rights reserved.
#
# 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, sys, netaddr, time
from unicodedata import normalize
from crans_utils import format_tel, format_mac, mailexist, validate_name
sys.path.append("/usr/scripts/gestion")
import config, annuaires_pg
from midtools import Mid
def attrify(val, attr, ldif, conn, ctxt_check = True):
"""Transforme un n'importe quoi en 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.
Doit effectuer les vérifications de contexte dans le *ldif* si
ctxt_check est vrai"""
if isinstance(val, Attr):
return val
else:
return CRANS_ATTRIBUTES[attr](val, ldif, conn, ctxt_check)
class Attr(object):
legend = "Human-readable description of attribute"
singlevalue = None
optional = None
conn = None
def __init__(self, val, ldif, conn, ctxt_check):
"""Crée un nouvel objet représentant un attribut.
val: valeur de l'attribut
ldif: objet contenant l'attribut (permet de faire les validations sur l'environnement)
ctxt_check: effectue les validations
"""
self.value = None
self.conn = conn
assert isinstance(val, unicode)
self.parse_value(val, ldif)
if ctxt_check:
self.validate(ldif)
def parse_value(self, val, ldif):
"""Transforme l'attribut pour travailler avec notre validateur"""
self.value = val
def __str__(self):
return unicode(self).encode('utf-8')
def __unicode__(self):
assert isinstance(self.value, unicode)
return self.value
def validate(self, ldif):
"""validates:
vérifie déjà que ce qu'on a rentré est parsable"""
own_values = ldif[self.__class__.__name__]
self._check_cardinality(own_values)
self._check_uniqueness()
self._check_users_restrictions(own_values)
def _check_cardinality(self, values):
"""Vérifie qu'il y a un nombre correct de valeur =1, <=1, {0,1},
etc..."""
if self.singlevalue and len(values) > 1:
raise ValueError(u'%s doit avoir au maximum une valeur (affecte %s)' % (self.__class__, values))
if not self.optional and len(values) == 0:
raise ValueError('%s doit avoir au moins une valeur' % self.__class__)
def _check_uniqueness(self):
"""Vérifie l'unicité dans la base de la valeur (mailAlias, chbre,
etc...)"""
attr = self.__class__.__name__
if attr in [ "mid", "uid", "cid", "fid"]: #... etc
assert not self.conn.search('%s=%s' % (attr, str(self)))
if attr in [ "mailAlias", "canonicalAlias"]:
assert not self.conn.search('|(mailAlias=%s)(canonicalAlias=%s)' % ((str(self),)*2))
assert not mailexist(str(self))
def _check_users_restrictions(self, values):
"""Vérifie les restrictions supplémentaires imposées selon les
niveaux de droits (<= 3 mailAlias, pas de mac identiques,
etc...)"""
### On l'implémente dans les classes filles !
pass
class objectClass(Attr):
singlevalue = False
optional = False
legend = "entité"
def parse_value(self, val, ldif):
if val not in [ 'top', 'posixAccount', 'shadowAccount', 'proprio',
'adherent', 'club', 'machine', 'machineCrans',
'borneWifi', 'machineWifi', 'machineFixe',
'cransAccount', 'service', 'facture', 'freeMid' ]:
raise ValueError("Pourquoi insérer un objectClass=%s ?" % val)
else:
self.value = unicode(val)
class nom(Attr):
singlevalue = True
optional = False
legend = "Nom"
def parse_value(self, val, ldif):
self.value = validate_name(val)
class prenom(Attr):
singlevalue = True
optional = False
legend = u"Prénom"
def parse_value(self, val, ldif):
self.value = validate_name(val)
class tel(Attr):
singlevalue = True
optional = False
legend = u"Téléphone"
def parse_value(self, val, ldif):
self.value = format_tel(val)
if len(self.value) == 0:
raise ValueError("Numéro de téléphone invalide ('%s')" % val)
class paiement(Attr):
singlevalue = False
optional = True
legend = u"Paiement"
def parse_value(self, val, ldif):
if int(val) < 1998 or int(val) > config.ann_scol:
raise ValueError("Année de cotisation invalide (%s)" % val)
self.value = int(val)
def __unicode__(self):
return unicode(self.value)
class carteEtudiant(Attr):
singlevalue = False
optional = True
legend = u"Carte d'étudiant"
def parse_value(self, val, ldif):
if int(val) < 1998 or int(val) > config.ann_scol:
raise ValueError("Année de cotisation invalide (%s)" % val)
self.value = int(val)
def __unicode__(self):
return unicode(self.value)
class mailAlias(Attr):
singlevalue = False
optional = True
legend = u"Alias mail"
def parse_value(self, val, ldif):
val = val.lower()
if not re.match('[a-z][-_.0-9a-z]+', val):
raise ValueError("Alias mail invalide (%s)" % val)
self.value = val
class canonicalAlias(Attr):
singlevalue = True
optional = False
legend = u"Alias mail canonique"
def parse_value(self, val, ldif):
val = val.lower()
if not re.match('[a-z][-_.0-9a-z]+', val):
raise ValueError("Alias mail invalide (%s)" % val)
self.value = val
class etudes(Attr):
singlevalue = False
optional = False
legend = u"Études"
def parse_value(self, val, ldif):
# who cares
self.value = val
class chbre(Attr):
singlevalue = True
optional = False
legend = u"Chambre sur le campus"
def parse_value(self, val, ldif):
annuaires_pg.chbre_prises(val[0], val[1:])
self.value = val
class droits(Attr):
singlevalue = False
optional = True
legend = u"Droits sur les serveurs"
def parse_value(self, val, ldif):
if val not in ['apprenti', 'nounou', 'cableur', 'tresorier', 'bureau',
'webmaster', 'webradio', 'imprimeur', 'multimachines', 'victime']:
raise ValueError("Ces droits n'existent pas ('%s')" % val)
self.value = val
class solde(Attr):
singlevalue = True
optional = True
legend = u"Solde d'impression"
def parse_value(self, solde, ldif):
# on évite les dépassements
assert 0. >= float(solde) and float(solde) <= 1024.
self.value = solde
class host(Attr):
singlevalue = True
optional = False
hname = legend = u"Nom d'hôte"
def parse_value(self, dns, ldif):
dns = dns.lower()
assert dns.endswith('.crans.org')
assert re.match('[a-z][-_a-z0-9]+', dns)
self.value = dns
class macAddress(Attr):
singlevalue = True
optional = False
legend = u"Adresse physique de la carte réseau"
hname = "Adresse MAC"
def parse_value(self, mac, ldif):
self.value = format_mac(mac)
def __unicode__(self):
return unicode(self.value)
class ipHostNumber(Attr):
singlevalue = True
optional = False
legend = u"Adresse IPv4 de la machine"
hname = "IPv4"
def parse_value(self, ip, ldif):
if ip == '<automatique>':
ip = Mid(mid= ldif['mid'][0]).ipv4()
self.value = netaddr.ip.IPAddress(ip)
def __unicode__(self):
return unicode(self.value)
class mid(Attr):
singlevalue = True
optional = False
legend = "Identifiant de machine"
def parse_value(self, mid, ldif):
self.value = Mid(mid = mid)
def __unicode__(self):
return unicode(int(self.value))
class hostAlias(Attr):
singlevalue = False
optional = True
legend = u'Alias de nom de machine'
def parse_value(self, dns, ldif):
dns = dns.lower()
assert dns.endswith('.crans.org')
assert re.match('[a-z][-_a-z0-9]+', dns)
self.value = dns
class ipsec(Attr):
singlevalue = False
optional = True
legend = u'Clef wifi'
class puissance(Attr):
singlevalue = True
optional = True
legend = u"puissance d'émission pour les bornes wifi"
class canal(Attr):
singlevalue = True
optional = True
legend = u'Canal d\'émission de la borne'
class portAttr(Attr):
singlevalue = False
optional = True
def parse_value(self, port, ldif):
self.value = int(port)
def __unicode__(self):
return unicode(self.value)
class portTCPout(portAttr):
legend = u'Port TCP ouvert vers l\'extérieur'
class portTCPin(portAttr):
legend = u"Port TCP ouvert depuis l'extérieur"
class portUDPout(portAttr):
legend = u"Port UDP ouvert vers l'extérieur"
class portUDPin(portAttr):
legend = u"Port UDP ouvert depuis l'extérieur"
class prise(Attr):
singlevalue = True
optional = True
legend = u"Prise sur laquelle est branchée la machine"
def parse_value(self, prise, ldif):
### Tu es une nounou, je te fais confiance
self.value = prise
class cid(Attr):
singlevalue = True
optional = True
legend = u"Identifiant du club"
def parse_value(self, cid, ldif):
self.value = int(cid)
def __unicode__(self):
return unicode(self.value)
class responsable(Attr):
singlevalue = True
optional = True
legend = u"Responsable du club"
def parse_value(self, resp, ldif):
self.value = self.conn.search('aid=%d' % self.value)[0]
def __unicode__(self):
return self.value.attrs['aid'][0]
class blacklist(Attr):
singlevalue = False
optional = True
legend = u"Blackliste"
def parse_value(self, bl, ldif):
bl_debut, bl_fin, bl_comm = bl.split('$', 3)
now = time.time()
self.value = { 'debut' : int (bl_debut),
'fin' : bl_fin if bl_fin == '-' else int(bl_fin),
'comm' : bl_comm,
'actif' : int(bl_debut) < now and (bl_fin == '-' or int(bl_fin) > now) }
def __unicode__(self):
return u'$%(debut)s$%(fin)s$%(comm)s' % self.value
class historique(Attr):
singlevalue = False
optional = True
legend = u"Historique de l'objet"
CRANS_ATTRIBUTES= {
'objectClass' : objectClass,
'nom' : nom,
'prenom' : prenom,
'tel' : tel,
'paiement' : paiement,
'carteEtudiant' : carteEtudiant,
'mailAlias' : mailAlias,
'canonicalAlias' : canonicalAlias,
'etudes' : etudes,
'chbre' : chbre,
'droits' : droits,
'solde' : solde,
'mid' : mid,
'host' : host,
'macAddress': macAddress,
'ipHostNumber': ipHostNumber,
'hostAlias' : hostAlias,
'ipsec' : ipsec,
'puissance' : puissance,
'canal' : canal,
'portTCPout' : portTCPout,
'portTCPin' : portTCPin,
'portUDPout' : portUDPout,
'portUDPin' : portUDPin,
'prise' : prise,
'cid' : cid,
'responsable' : responsable,
'blacklist' : blacklist,
'historique' : historique
}