diff --git a/attributs.py b/attributs.py new file mode 100644 index 0000000..a1a9b10 --- /dev/null +++ b/attributs.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# ATTRIBUTS.PY-- Description des attributs ldap +# +# Copyright (C) 2010 Cr@ns +# Author: Antoine Durand-Gasselin +# 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 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. + + +def validate_name(value, more_chars=""): + return re.match("[A-Za-z][-' A-Za-z%s]*" % more_chars, + normalize('NFKD', decode(a)).encode('ASCII', 'ignore')) + +def validate_mac(value): + return True + +class Attr: + legend = "Human-readable description of attribute" + def validate(self, values, uldif): + "validates" + self._check_cardinality(values) + self._check_type(values) + self._check_uniqueness(values) + self._check_values(values) + self._check_users_restrictions(values) + + def normalize(self, values, uldif): + "normalizes" + return values + + def self._check_cardinality(values): + """Vérifie qu'il y a un nombre correct de valeur =1, <=1, {0,1}, + etc...""" + if self.singlevalue and len(vals) > 1: + raise ValueError(u'%s doit avoir au maximum une valeur (affecte %s)' % self.__class__, values) + if not self.optional and len(vals) == 0: + raise ValueError('%s doit avoir au moins une valeur' % attr) + + def _check_type(self, values): + """Vérifie que les valeurs ont le bon type (nom est un mot, tel + est un nombre, etc...)""" + for v in values: + assert isunicode(v) + + def _check_uniqueness(self, values): + """Vérifie l'unicité dans la base de la valeur (mailAlias, chbre, + etc...)""" + pass + + def _check_values(self, values): + """Vérifie que les valeurs sont valides (typiquement chbre)""" + pass + + 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...)""" + pass + + def can_modify(self): + """Vérifie si l'attribut est modifiable""" + return False + +Class nom(Attr): + singlevalue = True + optional = False + legend = "Nom" + def _check_type(self, values): return validate_name() + + def normalize(self, values): + return [ values.strip().capitalize() for v in values ] + +Class prenom(Attr): + singlevalue = True + optional = False + legend = u"Prénom" + def _check_type(self, values): return validate_name() + + def normalize(self, values): + return [ values.strip().capitalize() for v in values ] + +class tel(Attr): + singlevalue = True + optional = False + legend = u"Téléphone" + + def normalize(self, value): + return value # XXX - To implement + +class paiement(Attr): + singlevallue = False + optional = True + legend = u"Paiement" + + def normalize(self, values): + return values # XXX - To implement + +class carteEtudiant(Attr): + singlevalue = False + optional = True + legend = u"Carte d'étudiant" + +class mailAlias(Attr): + singlevalue = False + optional = True + legend = u"Alias mail" + +class canonicalAlias(Attr): + singlevalue = True + optional = False + legend = u"Alias mail canonique" + +class etudes(Attr): + singlevalue = False + optional = False + legend = u"Études" + +class chbre(Attr): + singlevalue = True + optional = False + legend = u"Chambre sur le campus" + +class droits(Attr): + singlevalue = False + optional = True + legend = u"Droits sur les serveurs" + +class solde(Attr): + singlevalue = True + optional = True + legend = u"Solde d'impression" + + +class mid(Attr): + singlevalue = True + optional = False + legend = "Identifiant de machine" + +class hostAlias(Attr): + singlevalue = False + optional = True + legend = u'Alias de nom de machine' + +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 portTCPout(Attr): + singlevalue = False + optional = True + legend = u'Port TCP ouvert vers l\'extérieur' + +class portTCPin(Attr): + singlevalue = False + optional = True + legend = u"Port TCP ouvert depuis l'extérieur" + +class portUDPout(Attr): + singlevalue = False + optional = True + legend = u"Port UDP ouvert vers l'extérieur" + +class portUDPin(Attr): + singlevalue = False + optional = True + 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" + +class cid(Attr): + singlevalue = True + optional = True + legend = u"Identifiant du club" + +class responsable(Attr): + singlevalue = True + optional =True + legend = u"Responsable du club" + + +class blacklist(Attr): + singlevalue = False + optional = True + legend = u"Blackliste" + +class historique(Attr): + singlevalue = False + optional = True + legend = u"Historique de l'objet" + + +# CRANS_ATTRIBUTES = { +# 'nom' : { 'attr' : 'nom', +# 'hname' : 'Nom', +# 'isunique' : True }, +# 'prenom' : { 'attr' : 'prenom', +# 'hname' : u'Prénom', +# 'isunique' : True }, +# 'tel' : { 'attr' : 'tel', +# 'hname' : 'Téléphone', +# 'isunique' : True }, +# 'paiement' : { 'attr' : 'paiement', +# 'hname' : u'Années de cotisations', +# 'isunique' : False }, +# 'carteEtudiant' : { 'attr' : 'carteEtudiant', +# 'hname' : u'Carte fournie pour les années', +# 'isunique' : False }, +# 'mailAlias' : { 'attr' : 'mailAlias', +# 'hname' : 'Alias mail', +# 'isunique' : False }, +# 'canonicalAlias' : { 'attr' : 'canonicalAlias', +# 'hname' : 'Alias mail canonique', +# 'isunique' : True }, +# 'etudes' : { 'attr' : 'etudes', +# 'hname' : u'Études suivies', +# 'isunique' : False }, +# 'chbre' : { 'attr' : 'chbre', +# 'hname' : 'Chambre', +# 'isunique' : True }, +# 'droits' : { 'attr' : 'droits', +# 'hname' : 'Droits', +# 'isunique' : False }, +# 'solde' : { 'attr' : 'solde', +# 'hname' : "Solde sur le compte d'impression", +# 'isunique' : True }, +# +# +# 'mid' : { 'attr' : 'mid', +# 'hname' : 'Identifiant de machine', +# 'isunique' : True }, +# 'hostAlias' : { 'attr' : 'hostAlias', +# 'hname' : 'Alias de nom de machine', +# 'isunique' : False }, +# 'ipsec' : { 'attr' : 'ipsec', +# 'hname' : 'Clef wifi', +# 'isunique' : True }, +# 'puissance' : { 'attr' : 'puissance', +# 'hname' : u"Puissance d'émission de la borne wifi", +# 'isunique' : True }, +# 'canal' : { 'attr' : 'canal', +# 'hname' : u"Canal d'émission de la borne wifi", +# 'isunique' : True }, +# 'portTCPout' : { 'attr' : 'portTCPout', +# 'hname' : u"Port TCP ouvert vers l'extérieur", +# 'isunique' : False }, +# 'portTCPin' : { 'attr' : 'portTCPin', +# 'hname' : u"Port TCP ouvert depuis l'extérieur", +# 'isunique' : False }, +# 'portUDPout' : { 'attr' : 'portUDPout', +# 'hname' : u"Port UDP ouvert vers l'extérieur", +# 'isunique' : False }, +# 'portUDPin' : { 'attr' : 'portUDPin', +# 'hname' : u"Port UDP ouvert depuis l'extérieur", +# 'isunique' : False }, +# 'prise' : { 'attr' : 'prise', +# 'hname' : 'Prise sur laquelle est branchée la machine', +# 'isunique' : True }, +# +# +# 'cid' : { 'attr' : 'cid', +# 'hname' : 'Identifiant de club', +# 'isunique' : True }, +# 'responsable' : { 'attr' : 'responsable', +# 'hname' : 'Responsable du club', +# 'isunique' : True }, +# +# 'blacklist' : {'attr' : 'blacklist', +# 'hname' : 'Historique des sanctions', +# 'isunique' : False }, +# 'historique' : { 'attr' : 'historique', +# 'hname' : 'Historique des modifications', +# 'isunique' : False }, +# } diff --git a/lc_ldap.py b/lc_ldap.py index 647d0a3..2ae150a 100644 --- a/lc_ldap.py +++ b/lc_ldap.py @@ -35,7 +35,7 @@ import os, sys, ldap, re, netaddr, datetime, copy, time from ldap.modlist import addModlist, modifyModlist sys.path.append('/usr/scripts/gestion') -import config, crans_utils +import config, crans_utils, attributs from ldap_locks import CransLock uri = 'ldapi:///' #'ldap://ldap.adm.crans.org/' @@ -147,7 +147,7 @@ class lc_ldap(ldap.ldapobject.LDAPObject): 'fid', 'cid', 'mid', 'macAddress', 'host', 'hostAlias' ]: for val in uldif.get(item, []): lock.add(item, val) - uldif['historique'] = [ self._hist('Création')] + #uldif['historique'] = [ self._hist('Création')] ldif = uldif_to_ldif(uldif) modlist = addModlist(ldif) with lock: @@ -186,7 +186,7 @@ class CransLdapObject: attrs = None # Contient un dico uldif qui doit représenter ce qui # est dans la base - # _modifs = None # C'est là qu'on met les modifications + _modifs = None # C'est là qu'on met les modifications def __init__(self, conn, dn, mode='ro', ldif = None): '''Créé une instance d'un objet Crans (machine, adhérent, @@ -214,27 +214,33 @@ class CransLdapObject: self.__class__ = eval(self.attrs['objectClass'][0]) # self._modifs = copy.deepcopy(self.attrs) - # def save(self): - # "Enregistre les modifications" - # if self.mode != 'w': - # raise EnvironmentError(u"Objet en lecture seule, réessayer en lecture/écriture") - # - # # Vérifications et Historique - # histo = self._gen_hist(self._modifs) - # self._modifs['historique'] += histo - # - # # unicode -> utf-8 - # ldif = uldif_to_ldif(self._modifs) - # orig_ldif = uldif_to_ldif(self.attrs) - # - # # modifications - # modlist = modifyModlist(orig_ldif, ldif) - # self.conn.modify_s(self.dn, modlist) - # - # # Vérification des modifications - # self.attrs = ldif_to_uldif(self.conn.search_s(self.dn, 0)[0][1]) - # if self.attrs != self._modifs: - # raise EnvironmentError(u"Les modifications apportées à l'objet %s n'ont pas été correctement sauvegardées\nexpected = %s, found = %s" % (self.dn, self._modifs, self.attrs)) + def save(self): + "Vérifie que self._modifs contient des valeurs correctes et enregistre les modifications" + if self.mode != 'w': + raise EnvironmentError(u"Objet en lecture seule, réessayer en lecture/écriture") + + # Vérifications et Historique + histo = self._gen_hist(self._modifs) + self._modifs['historique'] += histo + for attr, vals in self._modifs.items: + self._modifs[attr] = self.validate_and_normalize(attr, vals) + + # On récupère la liste des modifications + modlist = self.get_modlist() + self.conn.modify_s(self.dn, modlist) + + # Vérification des modifications + self.attrs = ldif_to_uldif(self.conn.search_s(self.dn, 0)[0][1]) + if self.attrs != self._modifs: + raise EnvironmentError(u"Les modifications apportées à l'objet %s n'ont pas été correctement sauvegardées\nexpected = %s, found = %s" % (self.dn, self._modifs, self.attrs)) + + def get_modlist(self): + """Renvoie le dico des modifs""" + # unicode -> utf-8 + ldif = uldif_to_ldif(self._modifs) + orig_ldif = uldif_to_ldif(self.attrs) + + return modifyModlist(orig_ldif, ldif) def get_values(self, attr): """Renvoie les valeurs d'un attribut ldap de l'objet self""" @@ -252,10 +258,10 @@ class CransLdapObject: for val in new_vals: assert isinstance(val, unicode) # On vérifie les nouvelles valeurs données à l'attribut - self.check_vals(attr, new_vals) + new_vals = self.normalize_and_validate(attr, new_vals) # Si ça passe, on effectue les modifications - old_vals = self.attrs.get(attr, []) + old_vals = [ unicode.encode(val, 'utf-8') for val in self.attrs.get(attr, []) ] new_vals = [ unicode.encode(val, 'utf-8') for val in new_vals ] modlist = modifyModlist({attr : old_vals}, {attr : new_vals}) self.conn.modify_s(self.dn, modlist) @@ -288,43 +294,12 @@ class CransLdapObject: new_vals.append(new_val) return self.set_ldapattr(attr, new_vals) - def check_vals(self, attr, vals): + def normalize_and_validate(self, attr, vals): """Vérifie que attr peut se voir attribuer les valeurs vals""" - self.check_cardinality(attr, vals) - self.check_type(attr, vals) - self.check_uniqueness(attr, vals) - self.check_values(attr, vals) - self.check_users_restrictions(attr, vals) - - def check_cardinality(self, attr, vals): - """Vérifie qu'il y a un nombre correct de valeur =1, <=1, {0,1}, - etc...""" - if CRANS_ATTRIBUTES[attr]['isunique']: - if len(vals) > 1: - raise ValueError('%s doit avoir au maximum une valeur' % attr) -# if not CRANS_ATTRIBUTES[attr]['isoptional']: -# if len(vals) == 0: -# raise ValueError('%s doit avoir au moins une valeur' % attr) - - def check_type(self, attr, vals): - """Vérifie que les valeurs ont le bon type (nom est un mot, tel - est un nombre, etc...)""" - pass - - def check_uniqueness(self, attr, vals): - """Vérifie l'unicité dans la base de la valeur (mailAlias, chbre, - etc...)""" - pass - - def check_values(self, attr, vals): - """Vérifie que les valeurs sont valides (typiquement chbre)""" - pass - - def check_users_restrictions(self, attrs, vals): - """Vérifie les restrictions supplémentaires imposées selon les - niveaux de droits (<= 3 mailAlias, pas de mac identiques, - etc...)""" - pass + a = eval('attributs.%s()' % attr) + new_vals = a.normalize(vals, self._modifs if self._modifs else self.attrs) + a.validate(new_vals, self._modifs if self._modifs else self.attrs) + return new_vals def _gen_hist(self, modifs): # XXX - Kill it! l'historique devrait être généré par ldap @@ -534,89 +509,3 @@ class lock(CransLdapObject): pass MODIFIABLE_ATTRS = [ 'tel', 'chbre', 'mailAlias', 'loginShell' ] - - - -CRANS_ATTRIBUTES = { - 'nom' : { 'attr' : 'nom', - 'hname' : 'Nom', - 'isunique' : True }, - 'prenom' : { 'attr' : 'prenom', - 'hname' : u'Prénom', - 'isunique' : True }, - 'tel' : { 'attr' : 'tel', - 'hname' : 'Téléphone', - 'isunique' : True }, - 'paiement' : { 'attr' : 'paiement', - 'hname' : u'Années de cotisations', - 'isunique' : False }, - 'carteEtudiant' : { 'atttr' : 'carteEtudiant', - 'hname' : u'Carte fournie pour les années', - 'isunique' : False }, - 'mailAlias' : { 'attr' : 'mailAlias', - 'hname' : 'Alias mail', - 'isunique' : False }, - 'canonicalAlias' : { 'attr' : 'canonicalAlias', - 'hname' : 'Alias mail canonique', - 'isunique' : True }, - 'etudes' : { 'attr' : 'etudes', - 'hname' : u'Études suivies', - 'isunique' : False }, - 'chbre' : { 'attr' : 'chbre', - 'hname' : 'Chambre', - 'isunique' : True }, - 'droits' : { 'attr' : 'droits', - 'hname' : 'Droits', - 'isunique' : False }, - 'solde' : { 'attr' : 'solde', - 'hname' : "Solde sur le compte d'impression", - 'isunique' : True }, - - - 'mid' : { 'attr' : 'mid', - 'hname' : 'Identifiant de machine', - 'isunique' : True }, - 'hostAlias' : { 'attr' : 'hostAlias', - 'hname' : 'Alias de nom de machine', - 'isunique' : False }, - 'ipsec' : { 'attr' : 'ipsec', - 'hname' : 'Clef wifi', - 'isunique' : True }, - 'puissance' : { 'attr' : 'puissance', - 'hname' : u"Puissance d'émission de la borne wifi", - 'isunique' : True }, - 'canal' : { 'attr' : 'canal', - 'hname' : u"Canal d'émission de la borne wifi", - 'isunique' : True }, - 'portTCPout' : { 'attr' : 'portTCPout', - 'hname' : u"Port TCP ouvert vers l'extérieur", - 'isunique' : False }, - 'portTCPin' : { 'attr' : 'portTCPin', - 'hname' : u"Port TCP ouvert depuis l'extérieur", - 'isunique' : False }, - 'portUDPout' : { 'attr' : 'portUDPout', - 'hname' : u"Port UDP ouvert vers l'extérieur", - 'isunique' : False }, - 'portUDPin' : { 'attr' : 'portUDPin', - 'hname' : u"Port UDP ouvert depuis l'extérieur", - 'isunique' : False }, - 'prise' : { 'attr' : 'prise', - 'hname' : 'Prise sur laquelle est branchée la machine', - 'isunique' : True }, - - - 'cid' : { 'attr' : 'cid', - 'hname' : 'Identifiant de club', - 'isunique' : True }, - 'responsable' : { 'attr' : 'responsable', - 'hname' : 'Responsable du club', - 'isunique' : True }, - - 'blacklist' : {'attr' : 'blacklist', - 'hname' : 'Historique des sanctions', - 'isunique' : False }, - 'historique' : { 'attr' : 'historique', - 'hname' : 'Historique des modifications', - 'isunique' : False }, -} -