[lc_ldap] plus de propreté
This commit is contained in:
parent
c5ece03aa5
commit
a57f02d0b2
2 changed files with 279 additions and 200 deletions
335
lc_ldap.py
335
lc_ldap.py
|
@ -18,25 +18,28 @@
|
||||||
# be used to endorse or promote products derived from this software
|
# be used to endorse or promote products derived from this software
|
||||||
# without specific prior written permission.
|
# without specific prior written permission.
|
||||||
#
|
#
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
# DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT
|
||||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
# HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
# 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.
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
from __future__ import with_statement
|
from __future__ import with_statement
|
||||||
import os, sys, ldap, ldap.modlist, re, netaddr, datetime, copy, time
|
import os, sys, ldap, re, netaddr, datetime, copy, time
|
||||||
|
from ldap.modlist import addModlist, modifyModlist
|
||||||
sys.path.append('/usr/scripts/gestion')
|
sys.path.append('/usr/scripts/gestion')
|
||||||
|
|
||||||
import config, crans_utils
|
import config, crans_utils
|
||||||
|
from ldap_locks import CransLock
|
||||||
|
|
||||||
uri = 'ldapi:///' #'ldap://ldap.adm.crans.org/'
|
uri = 'ldapi:///' #'ldap://ldap.adm.crans.org/'
|
||||||
base_dn = 'ou=data,dc=crans,dc=org'
|
base_dn = 'ou=data,dc=crans,dc=org'
|
||||||
base_lock = 'ou=lock,dc=crans,dc=org'
|
|
||||||
|
|
||||||
def is_actif(sanction):
|
def is_actif(sanction):
|
||||||
"""Retourne True ou False suivant si la sanction fournie (chaîne
|
"""Retourne True ou False suivant si la sanction fournie (chaîne
|
||||||
|
@ -57,14 +60,14 @@ def uldif_to_ldif(uldif):
|
||||||
utf-8, renvoie un ldif"""
|
utf-8, renvoie un ldif"""
|
||||||
ldif = {}
|
ldif = {}
|
||||||
for attr, vals in uldif.items():
|
for attr, vals in uldif.items():
|
||||||
ldif[attr] = [ unicode.encode(val, 'utf-8') for val in uldif[attr] ]
|
ldif[attr] = [ unicode.encode(val, 'utf-8') for val in vals ]
|
||||||
return ldif
|
return ldif
|
||||||
|
|
||||||
def ldif_to_uldif(ldif):
|
def ldif_to_uldif(ldif):
|
||||||
'Prend en argument un dico ldif, et décode toutes les chaînes en utf-8'
|
'Prend en argument un dico ldif, et décode toutes les chaînes en utf-8'
|
||||||
uldif = {}
|
uldif = {}
|
||||||
for attr, vals in ldif.items():
|
for attr, vals in ldif.items():
|
||||||
uldif[attr] = [ unicode(val, 'utf-8') for val in ldif[attr] ]
|
uldif[attr] = [ unicode(val, 'utf-8') for val in vals ]
|
||||||
return uldif
|
return uldif
|
||||||
|
|
||||||
class lc_ldap(ldap.ldapobject.LDAPObject):
|
class lc_ldap(ldap.ldapobject.LDAPObject):
|
||||||
|
@ -86,9 +89,9 @@ class lc_ldap(ldap.ldapobject.LDAPObject):
|
||||||
self.simple_bind_s(base_dn)
|
self.simple_bind_s(base_dn)
|
||||||
res = self.search_s('uid=%s' % user)
|
res = self.search_s('uid=%s' % user)
|
||||||
if len(res) < 1:
|
if len(res) < 1:
|
||||||
raise ldap.INVALID_CREDENTIALS({'desc': 'No such user: %s' %s })
|
raise ldap.INVALID_CREDENTIALS({'desc': 'No such user: %s' % user })
|
||||||
elif len(res) > 1:
|
elif len(res) > 1:
|
||||||
raise ldap.INVALID_CREDENTIALS({'desc': 'Too many matches: uid=%s' %s })
|
raise ldap.INVALID_CREDENTIALS({'desc': 'Too many matches: uid=%s' % user })
|
||||||
else:
|
else:
|
||||||
dn = res[0][0]
|
dn = res[0][0]
|
||||||
if dn:
|
if dn:
|
||||||
|
@ -143,9 +146,9 @@ class lc_ldap(ldap.ldapobject.LDAPObject):
|
||||||
lock.add(item, val)
|
lock.add(item, val)
|
||||||
uldif['historique'] = [ self._hist('Création')]
|
uldif['historique'] = [ self._hist('Création')]
|
||||||
ldif = uldif_to_ldif(uldif)
|
ldif = uldif_to_ldif(uldif)
|
||||||
modlist = ldap.modlist.addModlist(ldif)
|
modlist = addModlist(ldif)
|
||||||
with lock:
|
with lock:
|
||||||
print dn, modlist
|
# print dn, modlist
|
||||||
self.add_s(dn, modlist)
|
self.add_s(dn, modlist)
|
||||||
return CransLdapObject(self, dn, mode='w')
|
return CransLdapObject(self, dn, mode='w')
|
||||||
|
|
||||||
|
@ -173,114 +176,6 @@ class lc_ldap(ldap.ldapobject.LDAPObject):
|
||||||
# ? def reconnect(self, conn=None):
|
# ? def reconnect(self, conn=None):
|
||||||
|
|
||||||
|
|
||||||
class CransLock:
|
|
||||||
|
|
||||||
def __init__(self, conn):
|
|
||||||
self.conn = conn
|
|
||||||
self.locks = {}
|
|
||||||
self._active = []
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.lock()
|
|
||||||
|
|
||||||
def __exit__(self, *args):
|
|
||||||
# XXX - connecter correctement les tracebacks.
|
|
||||||
print "exiting with exception", args
|
|
||||||
self.release()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def add(self, item, valeur):
|
|
||||||
'''rajoute un lock, après avoir vérifié qu'il peut être posé'''
|
|
||||||
try:
|
|
||||||
locked = self._islocked(item, valeur)
|
|
||||||
if locked:
|
|
||||||
raise EnvironmentError(u'Object déjà locké', locked)
|
|
||||||
except ldap.NO_SUCH_OBJECT:
|
|
||||||
pass
|
|
||||||
|
|
||||||
locked_values = self.locks.get(item, [])
|
|
||||||
if valeur not in locked_values:
|
|
||||||
locked_values.append(valeur)
|
|
||||||
self.locks[item] = locked_values
|
|
||||||
|
|
||||||
def remove(self, item, valeur):
|
|
||||||
'''Enlève un lock'''
|
|
||||||
self.locks[item].remove(valeur)
|
|
||||||
|
|
||||||
def lock(self):
|
|
||||||
'''Essaie de prendre tous les verrous'''
|
|
||||||
items = self.locks.items()
|
|
||||||
items.sort()
|
|
||||||
try:
|
|
||||||
for item, valeurs in items:
|
|
||||||
for valeur in valeurs:
|
|
||||||
self._lockitem(item, valeur)
|
|
||||||
except Exception, e:
|
|
||||||
# XXX - connecter proprement les traceback
|
|
||||||
self.release()
|
|
||||||
raise e
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
'''Relâche tous les verrous'''
|
|
||||||
exceptions = []
|
|
||||||
print "releasing", self._active
|
|
||||||
for item in self._active[:]:
|
|
||||||
try:
|
|
||||||
self._releaseitem(item)
|
|
||||||
except Exception, e:
|
|
||||||
exceptions.append(e)
|
|
||||||
if len(exceptions) == 1:
|
|
||||||
# XXX - connecter proprement les tracebacks
|
|
||||||
raise exceptions[0]
|
|
||||||
elif len(exceptions) > 1:
|
|
||||||
raise Exception(exceptions)
|
|
||||||
|
|
||||||
def _islocked(self, item, valeur):
|
|
||||||
# XXX - return self.conn.search_s(base_dn, 2, '%s=%s' % (item, valeur))
|
|
||||||
return self.conn.search_s('%s=%s,%s' % (item, valeur, base_lock), 0)
|
|
||||||
|
|
||||||
def _lockitem(self, item, valeur):
|
|
||||||
u"""
|
|
||||||
Lock un item avec la valeur valeur, les items possibles
|
|
||||||
peuvent être :
|
|
||||||
aid $ chbre $ mail $ mailAlias $ canonicalAlias $
|
|
||||||
mid $ macAddress $ host $ hostAlias $ ipHostNumber
|
|
||||||
Retourne le dn du lock
|
|
||||||
"""
|
|
||||||
|
|
||||||
valeur = valeur.encode('utf-8')
|
|
||||||
|
|
||||||
lock_dn = '%s=%s,%s' % (item, valeur, base_lock)
|
|
||||||
lockid = '%s-%s' % ('localhost', os.getpid())
|
|
||||||
modlist = ldap.modlist.addModlist({ 'objectClass': 'lock',
|
|
||||||
'lockid': lockid,
|
|
||||||
item: valeur })
|
|
||||||
print "locking", lock_dn
|
|
||||||
try:
|
|
||||||
self.conn.add_s(lock_dn, modlist)
|
|
||||||
except ldap.ALREADY_EXISTS:
|
|
||||||
# # Pas de chance, le lock est déja pris
|
|
||||||
# try:
|
|
||||||
# res = self.conn.search_s(lock_dn, 2, 'objectClass=lock')[0]
|
|
||||||
# l = res[1]['lockid'][0]
|
|
||||||
# except: l = '%s-1' % hostname
|
|
||||||
# if l != lockid:
|
|
||||||
# # C'est locké par un autre process que le notre
|
|
||||||
# # il tourne encore ?
|
|
||||||
# if l.split('-')[0] == hostname and os.system('ps %s > /dev/null 2>&1' % l.split('-')[1] ):
|
|
||||||
# # Il ne tourne plus
|
|
||||||
# self._releaseitem(res[0]) # delock
|
|
||||||
# return self._lockitem(item, valeur) # relock
|
|
||||||
raise EnvironmentError(u'Objet (%s=%s) locké, patienter.' % (item, valeur), l)
|
|
||||||
self._active.append(lock_dn)
|
|
||||||
return lock_dn
|
|
||||||
|
|
||||||
def _releaseitem(self, lockdn):
|
|
||||||
u"""Destruction d'un lock"""
|
|
||||||
# Mettre des verifs ?
|
|
||||||
print "releasing", lockdn
|
|
||||||
self._active.remove(lockdn)
|
|
||||||
self.conn.delete_s(lockdn)
|
|
||||||
|
|
||||||
class CransLdapObject:
|
class CransLdapObject:
|
||||||
mode = 'ro'
|
mode = 'ro'
|
||||||
|
@ -288,7 +183,7 @@ class CransLdapObject:
|
||||||
attrs = None # Contient un dico uldif qui doit représenter ce qui
|
attrs = None # Contient un dico uldif qui doit représenter ce qui
|
||||||
# est dans la base
|
# 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):
|
def __init__(self, conn, dn, mode='ro', ldif = None):
|
||||||
'''Créé une instance d'un objet Crans (machine, adhérent,
|
'''Créé une instance d'un objet Crans (machine, adhérent,
|
||||||
|
@ -314,46 +209,131 @@ class CransLdapObject:
|
||||||
self.dn, self.attrs = res[0]
|
self.dn, self.attrs = res[0]
|
||||||
self.attrs = ldif_to_uldif(self.attrs)
|
self.attrs = ldif_to_uldif(self.attrs)
|
||||||
self.__class__ = eval(self.attrs['objectClass'][0])
|
self.__class__ = eval(self.attrs['objectClass'][0])
|
||||||
self._modifs = copy.deepcopy(self.attrs)
|
# self._modifs = copy.deepcopy(self.attrs)
|
||||||
|
|
||||||
def save(self):
|
# def save(self):
|
||||||
"Enregistre les modifications"
|
# "Enregistre les modifications"
|
||||||
if self.mode != 'w':
|
# if self.mode != 'w':
|
||||||
raise EnvironmentError(u"Objet en lecture seule, réessayer en lecture/écriture")
|
# 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))
|
||||||
|
|
||||||
# Vérifications et Historique
|
def get_values(self, attr):
|
||||||
histo = self._gen_hist(self._modifs)
|
"""Renvoie les valeurs d'un attribut ldap de l'objet self"""
|
||||||
self._modifs['historique'] += histo
|
attrs = self.attrs.get(attr, [])
|
||||||
|
return attrs
|
||||||
|
|
||||||
# unicode -> utf-8
|
def get_value(self, attr):
|
||||||
ldif = uldif_to_ldif(self._modifs)
|
"""Renvoie la première valeur d'un attribut ldap de l'objet self"""
|
||||||
orig_ldif = uldif_to_ldif(self.attrs)
|
return self.get_values(attr)[0]
|
||||||
|
|
||||||
# modifications
|
def set_ldapattr(self, attr, new_vals):
|
||||||
modlist = ldap.modlist.modifyModlist(orig_ldif, ldif)
|
"""Définit les nouvelles valeurs d'un attribut"""
|
||||||
|
if not isinstance(new_vals, list):
|
||||||
|
new_vals = [new_vals]
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Si ça passe, on effectue les modifications
|
||||||
|
old_vals = self.attrs.get(attr, [])
|
||||||
|
modlist = modifyModlist({attr : old_vals}, {attr : new_vals})
|
||||||
self.conn.modify_s(self.dn, modlist)
|
self.conn.modify_s(self.dn, modlist)
|
||||||
|
|
||||||
# Vérification des modifications
|
def mod_ldapattr(self, attr, new_val, old_val = None):
|
||||||
self.attrs = ldif_to_uldif(self.conn.search_s(self.dn, 0)[0][1])
|
"""Modifie l'attribut attr ayant la valeur oldVal en newVal. Si
|
||||||
if self.attrs != self._modifs:
|
l'attribut attr n'a qu'une seule valeur, il n'est pas nécessaire
|
||||||
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))
|
de préciser oldVal."""
|
||||||
|
assert isinstance(new_val, unicode)
|
||||||
|
new_vals = self.attrs.get(attr, [])[:]
|
||||||
|
if old_val: # and oldVal in attrs:
|
||||||
|
new_vals.remove(old_val)
|
||||||
|
new_vals.append(new_val)
|
||||||
|
elif len(new_vals) == 1:
|
||||||
|
new_vals = [ new_val ]
|
||||||
|
else:
|
||||||
|
raise ValueError(u"%s has multiple values, must specify old_val")
|
||||||
|
return self.set_ldapattr(attr, new_vals)
|
||||||
|
|
||||||
|
def del_ldapattr(self, attr, val):
|
||||||
|
"""Supprime la valeur val de l'attribut attr"""
|
||||||
|
new_vals = self.attrs.get(attr, [])[:]
|
||||||
|
new_vals.remove(val)
|
||||||
|
return self.set_ldapattr(attr, new_vals)
|
||||||
|
|
||||||
|
def add_ldapattr(self, attr, new_val):
|
||||||
|
"""Rajoute la valeur val à l'attribut attr"""
|
||||||
|
assert isinstance(new_val, unicode)
|
||||||
|
new_vals = self.attrs.get(attr, [])[:]
|
||||||
|
new_vals.append(new_val)
|
||||||
|
return self.set_ldapattr(attr, new_vals)
|
||||||
|
|
||||||
|
def check_vals(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 attr in self.ufields:
|
||||||
|
if len(vals) != 1:
|
||||||
|
raise ValueError('%s doit avoir exactement une valeur' % attr)
|
||||||
|
|
||||||
|
if attr in self.ofields:
|
||||||
|
if len(vals) > 1:
|
||||||
|
raise ValueError('%s doit avoir au maximum 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
|
||||||
|
|
||||||
def _gen_hist(self, modifs):
|
def _gen_hist(self, modifs):
|
||||||
"""Vérifie la correction des modifs et genère l'historique des
|
"""Genère l'historique des modifications apportées. Cette
|
||||||
modifications apportées"""
|
fonction n'est là que pour de la rétro-compatibilité,
|
||||||
|
normalement les modifications sont automatiquement loggées."""
|
||||||
histo = []
|
histo = []
|
||||||
for field in self.ufields:
|
|
||||||
if len(modifs.get(field, [])) != 1:
|
|
||||||
raise ValueError('%s doit avoir exactement une valeur' % field)
|
|
||||||
|
|
||||||
for field in self.ofields:
|
for field in self.ofields:
|
||||||
if len(modifs.get(field, [])) > 1:
|
|
||||||
raise ValueError('%s doit avoir au maximum une valeur' % field)
|
|
||||||
if modifs.get(field, []) != self.attrs.get(field, []):
|
if modifs.get(field, []) != self.attrs.get(field, []):
|
||||||
if modifs.get(field, []) == []:
|
if modifs.get(field, []) == []:
|
||||||
msg = u"[%s] %s -> RESET" % (field, self.attrs[field][0])
|
msg = u"[%s] %s -> RESET" % (field, self.attrs[field][0])
|
||||||
elif self.attrs.get(field, []) == []:
|
elif self.attrs.get(field, []) == []:
|
||||||
msg = u"[%s] := %s"(field, modifs[field][0])
|
msg = u"[%s] := %s" % (field, modifs[field][0])
|
||||||
else:
|
else:
|
||||||
msg = u"[%s] %s -> %s" % (field, self.attrs[field][0], modifs[field][0])
|
msg = u"[%s] %s -> %s" % (field, self.attrs[field][0], modifs[field][0])
|
||||||
histo.append(self.conn._hist(msg))
|
histo.append(self.conn._hist(msg))
|
||||||
|
@ -474,8 +454,8 @@ class CransLdapObject:
|
||||||
else:
|
else:
|
||||||
Blist.append(new_c)
|
Blist.append(new_c)
|
||||||
|
|
||||||
if Blist != self._modifs.get('blacklist'):
|
if Blist != self.attrs.get('blacklist'):
|
||||||
self._modifs['blacklist'] = Blist
|
self.set_ldapattr('blacklist', Blist)
|
||||||
if not hasattr(self, "_blacklist_restart"):
|
if not hasattr(self, "_blacklist_restart"):
|
||||||
self._blacklist_restart = {}
|
self._blacklist_restart = {}
|
||||||
restart = self._blacklist_restart.setdefault(new[2], [])
|
restart = self._blacklist_restart.setdefault(new[2], [])
|
||||||
|
@ -486,51 +466,6 @@ class CransLdapObject:
|
||||||
|
|
||||||
return Blist
|
return Blist
|
||||||
|
|
||||||
def __getattribute__(self, attr):
|
|
||||||
if self.__dict__.has_key(attr):
|
|
||||||
return self.__dict__[attr]
|
|
||||||
else:
|
|
||||||
if attr in self.ufields + self.ofields + self.mfields + self.xfields:
|
|
||||||
return _getormod_ldapattr(self, attr)
|
|
||||||
|
|
||||||
def get_ldapattr(self, attr):
|
|
||||||
"""Renvoie un attribut ldap de l'objet self"""
|
|
||||||
attrs = self._modifs.get(attr, self.attrs.get(attr,[]))
|
|
||||||
if len(attrs) == 1:
|
|
||||||
return attrs[0]
|
|
||||||
else: return attrs
|
|
||||||
|
|
||||||
def mod_ldapattr(self, attr, newVal, oldVal = None):
|
|
||||||
"""Modifie l'attribut attr ayant la valeur oldVal en newVal. Si
|
|
||||||
l'attribut attr n'a qu'une seule valeur, il n'est pas nécessaire
|
|
||||||
de préciser oldVal."""
|
|
||||||
assert isinstance(newVal, unicode)
|
|
||||||
attrs = self._modifs.get(attr, self.attrs[attr])[:]
|
|
||||||
if oldVal: # and oldVal in attrs:
|
|
||||||
attrs.remove(oldVal)
|
|
||||||
attrs.append(newVal)
|
|
||||||
self._modifs[attr] = attrs
|
|
||||||
elif len(attrs) == 1:
|
|
||||||
self._modifs[attr] = [newVal]
|
|
||||||
else:
|
|
||||||
raise ValueError(u"%s has multiple values, must specify oldVal")
|
|
||||||
|
|
||||||
def del_ldapattr(self, attr, val):
|
|
||||||
"""Supprime la valeur val de l'attribut attr"""
|
|
||||||
self._modifs.setdefault(attr, self.attrs.get(attr, [])[:]) .remove(newVal)
|
|
||||||
|
|
||||||
def set_ldapattr(self, attr, newVals):
|
|
||||||
"""Définit les nouvelles valeurs d'un attribut"""
|
|
||||||
if not isinstance(newVals, list):
|
|
||||||
newVals = [newVals]
|
|
||||||
for val in newVals: assert isinstance(val, unicode)
|
|
||||||
self._modifs[attr] = newVals
|
|
||||||
|
|
||||||
def add_ldapattr(self, attr, newVal):
|
|
||||||
"""Rajoute la valeur val à l'attribut attr"""
|
|
||||||
assert isinstance(newVal, unicode)
|
|
||||||
self._modifs.setdefault(attr, self.attrs.get(attr, [])[:]).append(newVal)
|
|
||||||
|
|
||||||
|
|
||||||
class proprio(CransLdapObject):
|
class proprio(CransLdapObject):
|
||||||
ufields = [ 'nom', 'chbre' ]
|
ufields = [ 'nom', 'chbre' ]
|
||||||
|
@ -540,7 +475,7 @@ class proprio(CransLdapObject):
|
||||||
def machines(self):
|
def machines(self):
|
||||||
if self._machines == None:
|
if self._machines == None:
|
||||||
self._machines = self.conn.search_s('mid=*', dn = self.dn, scope = 1)
|
self._machines = self.conn.search_s('mid=*', dn = self.dn, scope = 1)
|
||||||
for m in machines:
|
for m in self._machines:
|
||||||
m._proprio = self
|
m._proprio = self
|
||||||
return self._machines
|
return self._machines
|
||||||
|
|
||||||
|
|
144
ldap_locks.py
Normal file
144
ldap_locks.py
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# LDAP_LOCKS.PY-- Locks for lc_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 ldap, os
|
||||||
|
|
||||||
|
base_lock = 'ou=lock,dc=crans,dc=org'
|
||||||
|
|
||||||
|
class CransLock:
|
||||||
|
|
||||||
|
def __init__(self, conn):
|
||||||
|
self.conn = conn
|
||||||
|
self.locks = {}
|
||||||
|
self._active = []
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.lock()
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
# XXX - connecter correctement les tracebacks.
|
||||||
|
print "exiting with exception", args
|
||||||
|
self.release()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def add(self, item, valeur):
|
||||||
|
'''rajoute un lock, après avoir vérifié qu'il peut être posé'''
|
||||||
|
try:
|
||||||
|
locked = self._islocked(item, valeur)
|
||||||
|
if locked:
|
||||||
|
raise EnvironmentError(u'Object déjà locké', locked)
|
||||||
|
except ldap.NO_SUCH_OBJECT:
|
||||||
|
pass
|
||||||
|
|
||||||
|
locked_values = self.locks.get(item, [])
|
||||||
|
if valeur not in locked_values:
|
||||||
|
locked_values.append(valeur)
|
||||||
|
self.locks[item] = locked_values
|
||||||
|
|
||||||
|
def remove(self, item, valeur):
|
||||||
|
'''Enlève un lock'''
|
||||||
|
self.locks[item].remove(valeur)
|
||||||
|
|
||||||
|
def lock(self):
|
||||||
|
'''Essaie de prendre tous les verrous'''
|
||||||
|
items = self.locks.items()
|
||||||
|
items.sort()
|
||||||
|
try:
|
||||||
|
for item, valeurs in items:
|
||||||
|
for valeur in valeurs:
|
||||||
|
self._lockitem(item, valeur)
|
||||||
|
except Exception, e:
|
||||||
|
# XXX - connecter proprement les traceback
|
||||||
|
self.release()
|
||||||
|
raise e
|
||||||
|
|
||||||
|
def release(self):
|
||||||
|
'''Relâche tous les verrous'''
|
||||||
|
exceptions = []
|
||||||
|
print "releasing", self._active
|
||||||
|
for item in self._active[:]:
|
||||||
|
try:
|
||||||
|
self._releaseitem(item)
|
||||||
|
except Exception, e:
|
||||||
|
exceptions.append(e)
|
||||||
|
if len(exceptions) == 1:
|
||||||
|
# XXX - connecter proprement les tracebacks
|
||||||
|
raise exceptions[0]
|
||||||
|
elif len(exceptions) > 1:
|
||||||
|
raise Exception(exceptions)
|
||||||
|
|
||||||
|
def _islocked(self, item, valeur):
|
||||||
|
# XXX - return self.conn.search_s(base_dn, 2, '%s=%s' % (item, valeur))
|
||||||
|
return self.conn.search_s('%s=%s,%s' % (item, valeur, base_lock), 0)
|
||||||
|
|
||||||
|
def _lockitem(self, item, valeur):
|
||||||
|
u"""
|
||||||
|
Lock un item avec la valeur valeur, les items possibles
|
||||||
|
peuvent être :
|
||||||
|
aid $ chbre $ mail $ mailAlias $ canonicalAlias $
|
||||||
|
mid $ macAddress $ host $ hostAlias $ ipHostNumber
|
||||||
|
Retourne le dn du lock
|
||||||
|
"""
|
||||||
|
|
||||||
|
valeur = valeur.encode('utf-8')
|
||||||
|
|
||||||
|
lock_dn = '%s=%s,%s' % (item, valeur, base_lock)
|
||||||
|
lockid = '%s-%s' % ('localhost', os.getpid())
|
||||||
|
modlist = ldap.modlist.addModlist({ 'objectClass': 'lock',
|
||||||
|
'lockid': lockid,
|
||||||
|
item: valeur })
|
||||||
|
print "locking", lock_dn
|
||||||
|
try:
|
||||||
|
self.conn.add_s(lock_dn, modlist)
|
||||||
|
except ldap.ALREADY_EXISTS:
|
||||||
|
# # Pas de chance, le lock est déja pris
|
||||||
|
# try:
|
||||||
|
# res = self.conn.search_s(lock_dn, 2, 'objectClass=lock')[0]
|
||||||
|
# l = res[1]['lockid'][0]
|
||||||
|
# except: l = '%s-1' % hostname
|
||||||
|
# if l != lockid:
|
||||||
|
# # C'est locké par un autre process que le notre
|
||||||
|
# # il tourne encore ?
|
||||||
|
# if l.split('-')[0] == hostname and os.system('ps %s > /dev/null 2>&1' % l.split('-')[1] ):
|
||||||
|
# # Il ne tourne plus
|
||||||
|
# self._releaseitem(res[0]) # delock
|
||||||
|
# return self._lockitem(item, valeur) # relock
|
||||||
|
raise EnvironmentError(u'Objet (%s=%s) locké, patienter.' % (item, valeur), lockid)
|
||||||
|
self._active.append(lock_dn)
|
||||||
|
return lock_dn
|
||||||
|
|
||||||
|
def _releaseitem(self, lockdn):
|
||||||
|
u"""Destruction d'un lock"""
|
||||||
|
# Mettre des verifs ?
|
||||||
|
print "releasing", lockdn
|
||||||
|
self._active.remove(lockdn)
|
||||||
|
self.conn.delete_s(lockdn)
|
Loading…
Add table
Add a link
Reference in a new issue