1866 lines
73 KiB
Python
1866 lines
73 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
#
|
||
|
||
""" Définition des classes permettant d'instancier les objets LDAP. """
|
||
|
||
#
|
||
# Copyright (C) 2010-2013 Cr@ns <roots@crans.org>
|
||
# Authors: Antoine Durand-Gasselin <adg@crans.org>
|
||
# Nicolas Dandrimont <olasd@crans.org>
|
||
# Olivier Iffrig <iffrig@crans.org>
|
||
# Valentin Samir <samir@crans.org>
|
||
# Daniel Stan <dstan@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 de la lib standard
|
||
import os
|
||
import sys
|
||
import re
|
||
import datetime
|
||
import time
|
||
import ldap
|
||
import random
|
||
import traceback
|
||
from ldap.modlist import addModlist, modifyModlist
|
||
|
||
## import locaux
|
||
import lc_ldap
|
||
import crans_utils
|
||
import attributs
|
||
import ldap_locks
|
||
import variables
|
||
import printing
|
||
|
||
## import de /usr/scripts
|
||
if "/usr/scripts" not in sys.path:
|
||
sys.path.append('/usr/scripts')
|
||
|
||
import gestion.config as config
|
||
import gestion.config.impression
|
||
import cranslib.deprecated
|
||
|
||
#: Champs à ignorer dans l'historique
|
||
HIST_IGNORE_FIELDS = ["modifiersName", "entryCSN", "modifyTimestamp", "historique"]
|
||
FACTURES_REFRESH_PERIOD = 60
|
||
|
||
def new_cransldapobject(conn, dn, mode='ro', uldif=None, lockId=None):
|
||
"""Crée un objet :py:class:`CransLdapObject` en utilisant la classe correspondant à
|
||
l'``objectClass`` du ``ldif``
|
||
--pour usage interne à la librairie uniquement !"""
|
||
|
||
classe = None
|
||
|
||
if dn == variables.base_dn:
|
||
classe = AssociationCrans
|
||
elif dn == variables.invite_dn:
|
||
classe = BaseInvites
|
||
elif uldif:
|
||
classe = ObjectFactory.get(uldif['objectClass'][0])
|
||
else:
|
||
res = conn.search_s(dn, 0)
|
||
if not res:
|
||
raise ValueError('objet inexistant: %s' % dn)
|
||
_, attrs = res[0]
|
||
classe = ObjectFactory.get(attrs['objectClass'][0])
|
||
|
||
try:
|
||
_clas = classe(conn, dn, mode, uldif, lockId=lockId)
|
||
except Exception as e:
|
||
print "dn=%s,uldif=%r" % (dn, uldif)
|
||
raise
|
||
return _clas
|
||
|
||
class CransLdapObject(object):
|
||
"""Classe de base des objets :py:class:`CransLdap`.
|
||
Cette classe ne devrait pas être utilisée directement."""
|
||
|
||
""" Qui peut faire quoi ? """
|
||
__slots__ = ("in_context", "conn", "lockId", "attrs", "_modifs", "dn", "parent_dn", "mode")
|
||
can_be_by = {
|
||
variables.created: [attributs.nounou],
|
||
variables.modified: [attributs.nounou],
|
||
variables.deleted: [attributs.nounou],
|
||
}
|
||
|
||
attribs = []
|
||
|
||
ldap_name = "CransLdapObject"
|
||
|
||
def update_attribs(self):
|
||
"""
|
||
Fonction permettant de mettre à jours attribs, dès que les valeurs
|
||
des attributs ont été instancié
|
||
"""
|
||
pass
|
||
|
||
def exists(self):
|
||
"""Renvois True si l'objet existe dans la base de donnée, False sinon"""
|
||
try:
|
||
if self.conn.search(dn=self.dn, scope=0):
|
||
return True
|
||
else:
|
||
return False
|
||
except ldap.NO_SUCH_OBJECT:
|
||
return False
|
||
|
||
def rights(self):
|
||
"""
|
||
Retourne les droits courant de l'utilisateur sur l'objet.
|
||
Ces droits sont égaux aux droits de l'utilisateur plus :
|
||
* soit si le dn de l'utilisateur est égal au dn de l'objet
|
||
* parent si le dn de l'utilisateur est préfixe du dn de l'objet
|
||
la méthode est en anglais pour ne pas interférer avec les attributs droits et jinja2
|
||
où les méthodes de l'objet et ses attributs sont appelé de la même manière
|
||
"""
|
||
return self.conn.droits + self.conn._check_parent(self.dn) + self.conn._check_self(self.dn) + self.conn._check_respo(self)
|
||
|
||
def __init__(self, conn, dn, mode='ro', uldif=None, lockId=None):
|
||
'''
|
||
Créée une instance d'un objet Crans (machine, adhérent,
|
||
etc...) à ce ``dn``, si ``uldif`` est précisé, n'effectue pas de
|
||
recherche dans la base ldap.
|
||
'''
|
||
|
||
if not isinstance(conn, lc_ldap.lc_ldap):
|
||
raise TypeError("conn doit être une instance de lc_ldap")
|
||
self.in_context = False
|
||
self.conn = conn
|
||
|
||
if lockId:
|
||
self.lockId = lockId
|
||
else:
|
||
self.lockId = '%s_%s' % (os.getpid(), id(self))
|
||
|
||
self.attrs = attributs.AttrsDict(conn, Parent=self) # Contient un dico ldif qui doit représenter ce qui
|
||
# est dans la base. On attrify paresseusement au moment où on utilise un attribut
|
||
|
||
self._modifs = {} # C'est là qu'on met les modifications
|
||
self.dn = dn
|
||
self.parent_dn = dn.split(',', 1)[1] if ',' in dn else ''
|
||
|
||
orig = {}
|
||
if uldif:
|
||
self.attrs = attributs.AttrsDict(self.conn, uldif, Parent=self)
|
||
self._modifs = attributs.AttrsDict(self.conn, uldif, Parent=self)
|
||
|
||
else:
|
||
res = self.conn.search_s(dn, 0)
|
||
if not res:
|
||
raise ValueError('objet inexistant: %s' % dn)
|
||
self.dn, ldif = res[0]
|
||
|
||
# L'objet sortant de la base ldap, on ne fait pas de vérifications sur
|
||
# l'état des données.
|
||
uldif = lc_ldap.ldif_to_uldif(ldif)
|
||
self.attrs = attributs.AttrsDict(self.conn, uldif, Parent=self)
|
||
self._modifs = attributs.AttrsDict(self.conn, uldif, Parent=self)
|
||
|
||
if dn == variables.base_dn:
|
||
mode = 'ro'
|
||
|
||
self.mode = mode
|
||
|
||
if self.mode in ['w', 'rw']:
|
||
if not self.may_be(variables.modified):
|
||
# On ne peut pas modifier l'objet, on droppe silencieusement ici, et
|
||
# on fera un raise quand la tentative de modif aura effectivement lieu.
|
||
self.mode = 'ro'
|
||
|
||
self.update_attribs()
|
||
|
||
if self.mode in ['w', 'rw']:
|
||
# Vérification que `λv. str(Attr(v))` est bien une projection
|
||
# C'est-à-dire que si on str(Attr(str(Attr(v)))) on retombe sur str(Attr(v))
|
||
oldif = lc_ldap.ldif_to_uldif(self.attrs.to_ldif())
|
||
nldif = lc_ldap.ldif_to_uldif(attributs.AttrsDict(self.conn, lc_ldap.ldif_to_uldif(self.attrs.to_ldif()), Parent=self).to_ldif())
|
||
|
||
for attr, vals in oldif.items():
|
||
if nldif[attr] != vals:
|
||
for v in nldif[attr]:
|
||
if v in vals:
|
||
vals.remove(v)
|
||
nvals = [nldif[attr][vals.index(v)] for v in vals]
|
||
raise EnvironmentError("λv. str(Attr(v)) n'est peut-être pas une projection (ie non idempotente):", attr, nvals, vals)
|
||
|
||
def _id(self):
|
||
"""Retourne l'id de l'objet courant"""
|
||
if isinstance(self, adherent):
|
||
return self['aid']
|
||
elif isinstance(self, machine):
|
||
return self['mid']
|
||
elif isinstance(self, club):
|
||
return self['cid']
|
||
elif isinstance(self, facture):
|
||
return self['fid']
|
||
else:
|
||
return [self.dn]
|
||
|
||
def _out_of_context(self, *args, **kwargs):
|
||
raise EnvironmentError("Hors du context, impossible de faire des écritures")
|
||
|
||
def __exit__(self, exc_type, exc_value, traceback):
|
||
# Sortie du context manager
|
||
self.in_context = False
|
||
# On rend les écriture impossible
|
||
#self.save = self._out_of_context
|
||
#self.create = self._out_of_context
|
||
#self.delete = self._out_of_context
|
||
# On retombe en read only
|
||
self.mode = 'ro'
|
||
# On purge les lock de l'objet
|
||
self.conn.lockholder.purge(self.lockId)
|
||
|
||
def __enter__(self):
|
||
# On est dans un context, normalement, c'est locksafe
|
||
self.in_context = True
|
||
return self
|
||
|
||
def __ne__(self, obj):
|
||
return not self == obj
|
||
|
||
def __eq__(self, obj):
|
||
if isinstance(obj, self.__class__):
|
||
if self.mode in ['w', 'rw']:
|
||
return self.dn == obj.dn and self._modifs == obj._modifs and self.attrs == obj.attrs
|
||
else:
|
||
return self.dn == obj.dn and self.attrs == obj.attrs
|
||
elif (isinstance(obj, str) or isinstance(obj, unicode)) and '=' in obj:
|
||
attr, val = obj.split('=', 1)
|
||
return attr in self.attrs.keys() and val in self[attr]
|
||
else:
|
||
return False
|
||
|
||
def __hash__(self):
|
||
if self.mode in ['w', 'rw']:
|
||
raise TypeError("Mutable structure are not hashable, please use mode = 'ro' to do so")
|
||
def c_mul(a, b):
|
||
return eval(hex((long(a) * b) & 0xFFFFFFFFL)[:-1])
|
||
value = 0x345678
|
||
l = 0
|
||
keys = self.keys()
|
||
keys.sort()
|
||
for key in keys:
|
||
l += len(self.attrs[key])
|
||
for item in self.attrs[key]:
|
||
value = c_mul(1000003, value) ^ hash(item)
|
||
value = value ^ l
|
||
if value == -1:
|
||
value = -2
|
||
return value
|
||
|
||
def __iter__(self):
|
||
if self.mode in ['w', 'rw']:
|
||
return self._modifs.__iter__()
|
||
else:
|
||
return self.attrs.__iter__()
|
||
|
||
def keys(self):
|
||
if self.mode in ['w', 'rw']:
|
||
return self._modifs.keys()
|
||
else:
|
||
return self.attrs.keys()
|
||
|
||
def values(self):
|
||
if self.mode in ['w', 'rw']:
|
||
return self._modifs.values()
|
||
else:
|
||
return self.attrs.values()
|
||
|
||
def items(self):
|
||
if self.mode in ['w', 'rw']:
|
||
return self._modifs.items()
|
||
else:
|
||
return self.attrs.items()
|
||
|
||
def display(self, *args, **kwargs):
|
||
print printing.sprint(self, *args, **kwargs)
|
||
|
||
def history_add(self, login, chain):
|
||
"""Ajoute une ligne à l'historique de l'objet.
|
||
###ATTENTION : C'est un kludge pour pouvoir continuer à faire "comme avant",
|
||
### mais on devrait tout recoder pour utiliser l'historique LDAP"""
|
||
assert isinstance(login, unicode)
|
||
assert isinstance(chain, unicode)
|
||
|
||
new_line = u"%s, %s : %s" % (time.strftime(attributs.historique.FORMAT), login, chain)
|
||
self["historique"] = self.get("historique", []) + [new_line]
|
||
|
||
def history_gen(self, attr=None, login=None):
|
||
"Génère une ligne d'historique pour l'arribut attr ou une ligne par attributs pour l'objet courant"
|
||
if attr is None:
|
||
for attr in self.keys():
|
||
self.history_gen(attr, login=login)
|
||
return
|
||
def partial_name(name, max_len=14, start=7, end=7):
|
||
if len(name) > max_len:
|
||
return "%s…%s" % (name[:start], name[-end:])
|
||
else:
|
||
return name
|
||
if login is None:
|
||
login = self.conn.current_login
|
||
if isinstance(attr, str) or isinstance(attr, unicode):
|
||
attr = attributs.AttributeFactory.get(attr)
|
||
elif isinstance(attr, Attr):
|
||
attr = type(attr)
|
||
elif issubclass(attr, Attr):
|
||
pass
|
||
else:
|
||
raise ValueError("%r ne correspont pas a un attribut" % attr)
|
||
if not attr.historique:
|
||
return
|
||
if attr.historique not in ["full", "partial", "info"]:
|
||
raise ValueError("Format d'historique %s inconnu" % attr.historique)
|
||
old_values = self.attrs.get(attr.ldap_name, [])
|
||
new_values = self._modifs.get(attr.ldap_name, [])
|
||
if old_values == new_values:
|
||
return
|
||
comm = None
|
||
if attr.singlevalue:
|
||
# modification
|
||
if old_values and new_values:
|
||
if attr.historique == "full":
|
||
comm = u"%s (%s -> %s)" % (attr.ldap_name, old_values[0], new_values[0])
|
||
elif attr.historique == "partial":
|
||
old = partial_name(unicode(old_values[0]))
|
||
new = partial_name(unicode(new_values[0]))
|
||
comm = u"%s (%s -> %s)" % (attr.ldap_name, old, new)
|
||
elif attr.historique == "info":
|
||
comm = u"%s" % attr.ldap_name
|
||
# création
|
||
elif not old_values and new_values:
|
||
if attr.historique == "info":
|
||
comm = u"+%s" % attr.ldap_name
|
||
elif attr.historique in ["full", "partial"]:
|
||
new = unicode(new_values[0])
|
||
if attr.historique == "partial":
|
||
new = partial_name(new)
|
||
comm = u"%s+%s" % (attr.ldap_name, new)
|
||
# suppréssion
|
||
elif not new_values and old_values:
|
||
if attr.historique == "info":
|
||
comm = u"-%s" % attr.ldap_name
|
||
elif attr.historique in ["full", "partial"]:
|
||
old = unicode(old_values[0])
|
||
if attr.historique == "partial":
|
||
old = partial_name(old)
|
||
comm = u"%s-%s" % (attr.ldap_name, old)
|
||
else:
|
||
added = []
|
||
deleted = []
|
||
if attr.historique == "partial":
|
||
append = lambda x: partial_name(unicode(x))
|
||
else:
|
||
append = lambda x: unicode(x)
|
||
for a in new_values:
|
||
if not a in old_values:
|
||
added.append(append(a))
|
||
for a in old_values:
|
||
if not a in new_values:
|
||
deleted.append(append(a))
|
||
if attr.historique == "info":
|
||
comm = u"%s%s%s" % ('+' if added else "", '-' if deleted else "", attr.ldap_name)
|
||
elif attr.historique in ["full", "partial"]:
|
||
comm = u"%s%s%s%s%s" % (attr.ldap_name, '+' if added else "", '+'.join(added), '-' if deleted else "", '-'.join(deleted))
|
||
|
||
if comm:
|
||
new_line = u"%s, %s : %s" % (time.strftime(attributs.historique.FORMAT), login, comm)
|
||
if not new_line in self["historique"]:
|
||
self["historique"].append(new_line)
|
||
else:
|
||
raise ValueError("impossible de générer l'historique pour %s %s %s" % (attr, old_values, new_values))
|
||
|
||
def _check_optionnal(self, comment):
|
||
"""Vérifie que les attributs qui ne sont pas optionnels sont effectivement peuplés."""
|
||
objet = self.ldap_name
|
||
|
||
for attribut in self.attribs:
|
||
if not attribut.optional:
|
||
nom_attr = attribut.ldap_name
|
||
if len(self._modifs.get(nom_attr, [])) <= 0:
|
||
raise attributs.OptionalError("L'objet %s que vous %s doit posséder au moins un attribut %s" % (objet, comment, nom_attr))
|
||
|
||
def _post_creation(self):
|
||
"""Fonction qui effectue quelques tâches lorsque la création est
|
||
faite"""
|
||
pass
|
||
|
||
def _post_deletion(self):
|
||
"""Fonction qui effectue quelques tâches lorsque la création est
|
||
faite"""
|
||
pass
|
||
|
||
def check_changes(self):
|
||
"""
|
||
Vérifie la consistence d'un objet
|
||
"""
|
||
pass
|
||
|
||
def validate_changes(self):
|
||
"""
|
||
Après vérification, harmonise l'objet
|
||
"""
|
||
pass
|
||
|
||
def create(self, login=None):
|
||
"""Crée l'objet dans la base ldap, cette méthode vise à faire en sorte que
|
||
l'objet se crée lui-même, si celui qui essaye de le modifier a les droits
|
||
de le faire."""
|
||
if not self.in_context:
|
||
# forcer l'utilisation d'un context manager permet d'être certain que les locks seront libéré quoi qu'il arrive
|
||
cranslib.deprecated.usage("Les nouveaux objets ne devrait être initialisés qu'avec des contexts managers (with func() as variable)", level=2)
|
||
try:
|
||
if login is None:
|
||
login = self.conn.current_login
|
||
self._check_optionnal(comment="créez")
|
||
|
||
try:
|
||
if self.conn.search(dn=self.dn):
|
||
raise ValueError('objet existant: %s' % self.dn)
|
||
except ldap.NO_SUCH_OBJECT:
|
||
pass
|
||
|
||
binary = set()
|
||
for attr in self.keys():
|
||
for attribut in self[attr]:
|
||
attribut.check_uniqueness([])
|
||
if self[attr] and self[attr][0].binary:
|
||
binary.add(attr)
|
||
|
||
self.history_add(login, u"Inscription")
|
||
|
||
ldif = self._modifs.to_ldif()
|
||
for attr in binary:
|
||
ldif['%s;binary' % attr] = ldif[attr]
|
||
del ldif[attr]
|
||
# Création de la requête LDAP
|
||
modlist = addModlist(ldif)
|
||
# Requête LDAP de création de l'objet
|
||
try:
|
||
self.conn.lockholder.check(self.lockId, delai=10)
|
||
self.conn.add_s(self.dn, modlist)
|
||
except Exception:
|
||
print traceback.format_exc()
|
||
return
|
||
finally:
|
||
# On nettoie les locks
|
||
self.conn.lockholder.purge(self.lockId)
|
||
|
||
# Services à relancer
|
||
services.services_to_restart(self.conn, {}, self._modifs, created_object=[self])
|
||
self._post_creation()
|
||
|
||
# Vérifications après insertion.
|
||
self.check_modifs()
|
||
|
||
def bury(self, comm, login):
|
||
"""Sauvegarde l'objet dans un fichier dans le cimetière."""
|
||
self.history_add(login, u"destruction (%s)" % comm)
|
||
self.save()
|
||
# On produit un ldif
|
||
ldif = u"dn: %s\n" % self.dn
|
||
for key in self.keys():
|
||
for value in self[key]:
|
||
ldif += u"%s: %s\n" % (key, value)
|
||
|
||
file = "%s %s" % (datetime.datetime.now(), self.dn)
|
||
try:
|
||
os.mkdir('%s/%s' % (lc_ldap.cimetiere.cimetiere_root, self['objectClass'][0]))
|
||
except OSError:
|
||
pass
|
||
f = open('%s/%s/%s' % (lc_ldap.cimetiere.cimetiere_root, self['objectClass'][0], file.replace(' ', '_')), 'w')
|
||
f.write(ldif.encode("UTF-8"))
|
||
f.close()
|
||
|
||
def delete(self, comm="", login=None):
|
||
"""Supprime l'objet de la base LDAP. Appelle :py:meth:`CransLdapObject.bury`."""
|
||
if login is None:
|
||
login = self.conn.current_login
|
||
if self.mode not in ['w', 'rw']:
|
||
raise EnvironmentError("Objet en lecture seule, réessayer en lecture/écriture")
|
||
if not self.may_be(variables.deleted):
|
||
raise EnvironmentError("Vous n'avez pas le droit de supprimer %s." % self.dn)
|
||
if not self.in_context:
|
||
# forcer l'utilisation d'un context manager permet d'être certain que les locks seront libéré quoi qu'il arrive
|
||
cranslib.deprecated.usage("La suppression d'un objet ne devrait être faite qu'avec des contexts managers (with func() as variable)", level=2)
|
||
self.conn.lockholder.check(self.lockId, delai=10)
|
||
self.bury(comm, login)
|
||
self.conn.delete_s(self.dn)
|
||
self.conn.lockholder.purge(self.lockId)
|
||
self._post_deletion()
|
||
services.services_to_restart(self.conn, self.attrs, {}, deleted_object=[self])
|
||
|
||
def save(self):
|
||
"""Sauvegarde dans la base les modifications apportées à l'objet.
|
||
Interne: Vérifie que ``self._modifs`` contient des valeurs correctes et
|
||
enregistre les modifications."""
|
||
if self.mode not in ['w', 'rw']:
|
||
raise EnvironmentError("Objet en lecture seule, réessayer en lecture/écriture")
|
||
if not self.in_context:
|
||
# forcer l'utilisation d'un context manager permet d'être certain que les locks seront libéré quoi qu'il arrive
|
||
cranslib.deprecated.usage("Les écritures sur un objet ne devrait être faite qu'avec des contexts managers (with func() as variable)", level=2)
|
||
|
||
self._check_optionnal(comment="modifiez")
|
||
|
||
# On récupère la liste des modifications
|
||
modlist = self.get_modlist()
|
||
try:
|
||
self.conn.lockholder.check(self.lockId, delai=10)
|
||
self.conn.modify_s(self.dn, modlist)
|
||
self.conn.lockholder.purge(self.lockId)
|
||
except Exception as error:
|
||
self.cancel()
|
||
raise EnvironmentError("Impossible de modifier l'objet: %r" % error)
|
||
|
||
# On programme le redémarrage des services
|
||
services.services_to_restart(self.conn, self.attrs, self._modifs)
|
||
|
||
# Vérification des modifications.
|
||
self.check_modifs()
|
||
|
||
def cancel(self):
|
||
"""
|
||
Annule les changements en attente
|
||
"""
|
||
old_uldif = lc_ldap.ldif_to_uldif(self.conn.search_s(self.dn, ldap.SCOPE_BASE)[0][1])
|
||
self._modifs = attributs.AttrsDict(self.conn, old_uldif, Parent=self)
|
||
# On nettoie les locks
|
||
self.conn.lockholder.purge(self.lockId)
|
||
|
||
def check_modifs(self):
|
||
"""
|
||
Fonction qui vérifie que les modifications se sont bien
|
||
passées.
|
||
"""
|
||
# Vérification des modifications
|
||
old_uldif = lc_ldap.ldif_to_uldif(self.conn.search_s(self.dn, ldap.SCOPE_BASE)[0][1])
|
||
self.attrs = attributs.AttrsDict(self.conn, old_uldif, Parent=self)
|
||
differences = []
|
||
# On fait les différences entre les deux dicos
|
||
for attr in set(self.attrs.keys()).union(set(self._modifs.keys())):
|
||
exp_vals = set([str(i) for i in self.attrs.get(attr, [])])
|
||
new_vals = set([str(i) for i in self._modifs.get(attr, [])])
|
||
if exp_vals != new_vals:
|
||
differences.append({"missing": exp_vals - new_vals, "having": new_vals - exp_vals})
|
||
print differences[-1]
|
||
if differences:
|
||
raise EnvironmentError("Les modifications apportées à l'objet %s n'ont pas été correctement sauvegardées\n%s" % (self.dn, differences))
|
||
|
||
def may_be(self, what, liste=None):
|
||
"""Teste si liste peut faire ce qui est dans what, pour
|
||
what élément de {create, delete, modify}.
|
||
On passe une liste de droits plutôt que l'objet car il faut ajouter
|
||
les droits soi et parent.
|
||
Retourne un booléen
|
||
"""
|
||
if liste is None:
|
||
liste = self.rights()
|
||
if set(liste).intersection(self.can_be_by[what]) != set([]):
|
||
return True
|
||
else:
|
||
return False
|
||
|
||
def get_modlist(self):
|
||
"""Renvoie un dictionnaire des modifications apportées à l'objet"""
|
||
# unicode -> utf-8
|
||
binary = set()
|
||
for attr in self.keys():
|
||
if self[attr] and self[attr][0].binary:
|
||
binary.add(attr)
|
||
|
||
ldif = self._modifs.to_ldif()
|
||
orig_ldif = self.attrs.to_ldif()
|
||
for attr in binary:
|
||
ldif['%s;binary' % (attr,)] = ldif[attr]
|
||
orig_ldif['%s;binary' % (attr,)] = orig_ldif.get(attr, [])
|
||
del ldif[attr]
|
||
try:
|
||
del orig_ldif[attr]
|
||
except KeyError:
|
||
pass
|
||
|
||
return modifyModlist(orig_ldif, ldif)
|
||
|
||
def get(self, attr, default):
|
||
"""Renvoie l'attribut demandé ou default si introuvable"""
|
||
try:
|
||
return self.__getitem__(attr, default)
|
||
except KeyError:
|
||
return default
|
||
|
||
def __getitem__(self, attr, default=None):
|
||
if self._modifs.has_key(attr) and self.mode in ['w', 'rw']:
|
||
return attributs.AttrsList(self, attr, [v for v in self._modifs[attr]])
|
||
elif self.attrs.has_key(attr):
|
||
return attributs.AttrsList(self, attr, [v for v in self.attrs[attr]])
|
||
elif self.has_key(attr):
|
||
return attributs.AttrsList(self, attr, []) if default is None else default
|
||
else:
|
||
raise KeyError(attr)
|
||
|
||
def has_key(self, attr):
|
||
"""Est-ce que notre objet a l'attribut en question ?"""
|
||
return attr in [attrib.__name__ for attrib in self.attribs]
|
||
|
||
def _check_setitem(self, attr, values):
|
||
"""
|
||
Vérifie des contraintes non liées à LDAP lors d'un __setitem__,
|
||
lève une exception si elles ne sont pas vérifiées
|
||
"""
|
||
pass
|
||
|
||
def replace_id(self, attr, realm=None):
|
||
ids = ["aid", "mid", "fid", "cid", "xid", "rid"]
|
||
if attr not in ids or not attr in self.keys():
|
||
raise ValueError("On ne peut remplacer que les id suivant : %s" % ", ".join(attr for attr in ids if attr in self.keys()))
|
||
id = self.conn._find_id(attr, realm, lockId=self.lockId)
|
||
# Pas de lock sur l'unicité car _find_id vient d'en placer un
|
||
self.__setitem__(attr, unicode(id), no_uniq_lock=True)
|
||
|
||
def __setitem__(self, attr, values, no_uniq_lock=False, no_concurrent_lock=False):
|
||
"""Permet d'affecter des valeurs à l'objet comme
|
||
s'il était un dictionnaire."""
|
||
# Quand on est pas en mode d'écriture, ça plante.
|
||
if self.mode not in ['w', 'rw']:
|
||
raise ValueError("Objet en lecture seule")
|
||
if not self.has_key(attr):
|
||
raise ValueError("L'objet que vous modifiez n'a pas d'attribut %s" % (attr))
|
||
# Les valeurs sont nécessairement stockées en liste
|
||
if not isinstance(values, list):
|
||
values = [values]
|
||
|
||
# On génére une liste des attributs, le dictionnaire ldif
|
||
# sert à permettre les vérifications de cardinalité
|
||
# (on peut pas utiliser self._modifs, car il ne faut
|
||
# faire le changement que si on peut)
|
||
attrs_before_verif = [attributs.attrify(val, attr, self.conn, Parent=self) for val in values]
|
||
|
||
# Methode de vérification diverse ayant pour but d'être surcharger
|
||
# par les classes enfants. Ainsi, elle ne touche pas à __setitem__
|
||
# qui est assez sensible.
|
||
self._check_setitem(attr, attrs_before_verif)
|
||
|
||
old_attrs = self[attr]
|
||
|
||
# On groupe les attributs précédents, et les nouveaux
|
||
mixed_attrs = attrs_before_verif + old_attrs
|
||
|
||
# Si c'est vide, on fait pas de vérifs, on avait une liste
|
||
# vide avant, puis on en a une nouvelle après.
|
||
if mixed_attrs:
|
||
# Tests de droits.
|
||
if not mixed_attrs[0].is_modifiable(self.conn.droits + self.conn._check_parent(self.dn) + self.conn._check_self(self.dn) + self.conn._check_respo(self)):
|
||
raise EnvironmentError("Vous ne pouvez pas modifier l'attribut %r de l'objet %r." % (attr, self))
|
||
|
||
# On ajoute des locks sur les nouvelles valeurs
|
||
locked = []
|
||
try:
|
||
for attribut in attrs_before_verif:
|
||
# Vérification que (attr, value) est localement unique
|
||
# Il vaut mieux le vérifier ici, car l'erreur que LDAP
|
||
# lève n'est pas très claire. (mais il est clair qu'il ne
|
||
# tolère pas les doublons dans un objet)
|
||
if attrs_before_verif.count(attribut) > 1:
|
||
raise ValueError("%r en double\n(%r)" % (attribut.legend if attribut.legend else attr, attribut))
|
||
|
||
# On lock les nouvelles valeurs globalement unique
|
||
if not no_uniq_lock and attribut.unique and not attribut in self._modifs.get(attr, []) and not attribut in attribut.unique_exclue:
|
||
if not self.in_context:
|
||
cranslib.deprecated.usage("Des locks ne devrait être ajoutés que dans un context manager", level=2)
|
||
self.conn.lockholder.addlock(attr, str(attribut), self.lockId)
|
||
locked.append((attr, str(attribut), self.lockId))
|
||
|
||
# si attr doit être globalement unique, vérification de l'unicité globale
|
||
# Dans ce cas, on ne tiens pas compte de old_attrs cas ils
|
||
# vont être effacé si le setitem réussi
|
||
attribut.check_uniqueness(old_attrs)
|
||
|
||
# On lock si l'attribut ne supporte pas les modifications concurrente (comme pour le solde) si :
|
||
# * on effectue réellement un modification sur l'attribut
|
||
# * on a pas déjà effectuer un modification qui nous a déjà fait acquérir le lock
|
||
if not no_concurrent_lock and not attributs.AttributeFactory.get(attr).concurrent and self._modifs.get(attr, []) == self.attrs.get(attr, []) and attrs_before_verif != self.attrs.get(attr, []):
|
||
if not self.in_context:
|
||
cranslib.deprecated.usage("Des locks ne devrait être ajoutés que dans un context manager", level=2)
|
||
self.conn.lockholder.addlock("dn", "%s_%s" % (self.dn.replace('=', '-').replace(',', '_'), attr), self.lockId)
|
||
locked.append(("dn", "%s_%s" % (self.dn.replace('=', '-').replace(',', '_'), attr), self.lockId))
|
||
try:
|
||
# une fois le lock acquit, on vérifie que l'attribut n'a pas été édité entre temps
|
||
if self.conn.search(dn=self.dn, scope=0)[0].get(attr, []) != self.attrs.get(attr, []):
|
||
raise ldap_locks.LockError("L'attribut %s a été modifié dans la base ldap avant l'acquisition du lock" % attr)
|
||
# L'objet n'existe pas dans la base ldap (resurection par exemple), donc pas de problème
|
||
except ldap.NO_SUCH_OBJECT:
|
||
pass
|
||
except (ldap_locks.LockError, ValueError, attributs.UniquenessError):
|
||
# Si on ne parvient pas à prendre le lock pour l'une des valeurs
|
||
# on libère les locks pris jusque là et on propage l'erreur
|
||
# les anciens locks et self._modifs reste bien inchangés
|
||
for (a, b, c) in locked:
|
||
self.conn.lockholder.removelock(a, b, c)
|
||
raise
|
||
|
||
# On retire les locks des attributs que l'on ne va plus utiliser
|
||
for attribut in self._modifs.get(attr, []):
|
||
if attribut.unique and not attribut in attrs_before_verif and not attribut in attribut.unique_exclue:
|
||
self.conn.lockholder.removelock(attr, str(attribut), self.lockId)
|
||
# Si on remet la valeur antérieure au lock, on le libère
|
||
if not attributs.AttributeFactory.get(attr).concurrent and self._modifs.get(attr, []) != self.attrs.get(attr, []) and attrs_before_verif == self.attrs.get(attr, []):
|
||
self.conn.lockholder.removelock("dn", "%s_%s" % (self.dn.replace('=', '-').replace(',', '_'), attr), self.lockId)
|
||
|
||
# On met à jour self._modifs avec les nouvelles valeurs
|
||
self._modifs[attr] = attrs_before_verif
|
||
|
||
def search_historique(self, ign_fields=HIST_IGNORE_FIELDS):
|
||
u"""Récupère l'historique
|
||
l'argument optionnel ign_fields contient la liste des champs
|
||
à ignorer, HIST_IGNORE_FIELDS par défaut
|
||
Renvoie une liste de lignes de texte."""
|
||
res = self.conn.search_s(variables.log_dn, ldap.SCOPE_SUBTREE, 'reqDN=%s' % self.dn)
|
||
res.sort(key=(lambda a: a[1]['reqEnd'][0]))
|
||
out = []
|
||
for cn, attrs in res:
|
||
date = crans_utils.format_ldap_time(attrs['reqEnd'][0])
|
||
author = attrs['reqAuthzID'][0]
|
||
if author == "cn=admin,dc=crans,dc=org":
|
||
author = u"respbats"
|
||
else:
|
||
author = author.split(",", 1)[0]
|
||
res = self.conn.search(author, scope=ldap.SCOPE_ONELEVEL)
|
||
if res != []:
|
||
author = res[0].compte()
|
||
|
||
if attrs['reqType'][0] == variables.deleted:
|
||
out.append(u"%s : [%s] Suppression" % (date, author))
|
||
elif attrs['reqType'][0] == variables.modified:
|
||
fields = {}
|
||
for mod in attrs['reqMod']:
|
||
mod = mod.decode('utf-8')
|
||
field, change = mod.split(':', 1)
|
||
if field not in ign_fields:
|
||
if field in fields:
|
||
fields[field].append(change)
|
||
else:
|
||
fields[field] = [change]
|
||
mod_list = []
|
||
for field in fields:
|
||
mods = fields[field]
|
||
mod_list.append(u"%s %s" %(field, ", ".join(mods)))
|
||
if mod_list != []:
|
||
out.append(u"%s : [%s] %s" % (date, author, u" ; ".join(mod_list)))
|
||
return out
|
||
|
||
# On utilise carte_ok et paiement_ok dans blacklist_actif, qui sont définis dans les
|
||
# objets enfants. On définit des méthodes vides ici pour la cohérence.
|
||
def carte_ok(self):
|
||
pass
|
||
|
||
def paiement_ok(self, no_bl=False):
|
||
pass
|
||
|
||
def blacklist_actif(self, excepts=[]):
|
||
"""Renvoie la liste des blacklistes actives sur l'entité
|
||
Améliorations possibles:
|
||
- Vérifier les blacklistes des machines pour les adhérents ?
|
||
"""
|
||
blacklist_liste = []
|
||
# blacklistes virtuelle si on est un adhérent pour carte étudiant et chambre invalides
|
||
if isinstance(self, adherent):
|
||
if self['chbre'][0] == '????':
|
||
bl = attributs.blacklist(u'%s$%s$%s$%s' % ('-', '-', 'chambre_invalide', ''), {}, self.conn)
|
||
blacklist_liste.append(bl)
|
||
if isinstance(self, proprio):
|
||
if not self.paiement_ok(no_bl=True):
|
||
bl = attributs.blacklist(u'%s$%s$%s$%s' % ('-', '-', 'paiement', ''), {}, self.conn)
|
||
blacklist_liste.append(bl)
|
||
blacklist_liste.extend(bl for bl in self.get("blacklist", []) if bl.is_actif())
|
||
if excepts:
|
||
return [b for b in blacklist_liste if b['type'] not in excepts]
|
||
else:
|
||
return blacklist_liste
|
||
|
||
def blacklist(self, sanction, commentaire, debut="now", fin='-'):
|
||
"""
|
||
Blacklistage de la ou de toutes la machines du propriétaire
|
||
* debut et fin sont le nombre de secondes depuis epoch
|
||
* pour un début ou fin immédiate mettre now
|
||
* pour une fin indéterminée mettre '-'
|
||
Les données sont stockées dans la base sous la forme :
|
||
debut$fin$sanction$commentaire
|
||
"""
|
||
if debut == 'now':
|
||
debut = int(time.time())
|
||
if fin == 'now':
|
||
fin = int(time.time())
|
||
bl = attributs.blacklist(u'%s$%s$%s$%s' % (debut, fin, sanction, commentaire), {}, self.conn)
|
||
|
||
self._modifs.setdefault('blacklist', []).append(bl)
|
||
|
||
class ObjectFactory(object):
|
||
"""Utilisée pour enregistrer toutes les classes servant à instancier un objet LDAP.
|
||
Elle sert à les récupérer à partir de leur nom LDAP.
|
||
|
||
Cette classe n'est jamais instanciée.
|
||
|
||
"""
|
||
__slots__ = ()
|
||
_classes = {}
|
||
|
||
@classmethod
|
||
def register(cls, name, classe):
|
||
"""Enregistre l'association ``name`` -> ``classe``"""
|
||
cls._classes[name] = classe
|
||
|
||
@classmethod
|
||
def get(cls, name):
|
||
"""Retourne la classe qui a ``name`` pour ``ldap_name``.
|
||
|
||
Pas de fallback, on ne veut pas instancier des objets de manière hasardeuse.
|
||
"""
|
||
return cls._classes.get(name)
|
||
|
||
def crans_object(classe):
|
||
"""Pour décorer les classes permettant d'instancier des attributs LDAP,
|
||
afin de les enregistrer dans :py:class:`ObjectFactory`.
|
||
|
||
"""
|
||
ObjectFactory.register(classe.ldap_name, classe)
|
||
return classe
|
||
|
||
@crans_object
|
||
class InetOrgPerson(CransLdapObject):
|
||
__slots__ = ()
|
||
ldap_name = "inetOrgPerson"
|
||
def __unicode__(self):
|
||
return u"%s : cn=%s" % (self.__class__.__name__, self['cn'][0])
|
||
|
||
def __repr__(self):
|
||
return repr(self.__unicode__())
|
||
pass
|
||
|
||
class proprio(CransLdapObject):
|
||
u""" Un propriétaire de machine (adhérent, club…) """
|
||
__slots__ = ("_machines", "_factures", "full", '_factures_last_update')
|
||
can_be_by = {
|
||
variables.created: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.cableur,
|
||
],
|
||
variables.modified: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.soi,
|
||
attributs.cableur,
|
||
],
|
||
variables.deleted: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
],
|
||
}
|
||
|
||
|
||
crans_account_attribs = [
|
||
attributs.uid, attributs.canonicalAlias, attributs.solde,
|
||
attributs.contourneGreylist, attributs.derniereConnexion,
|
||
attributs.homepageAlias, attributs.loginShell, attributs.gecos,
|
||
attributs.uidNumber, attributs.homeDirectory,
|
||
attributs.gidNumber, attributs.userPassword,
|
||
attributs.mailAlias, attributs.cn, attributs.rewriteMailHeaders,
|
||
attributs.mailExt, attributs.compteWiki, attributs.droits,
|
||
attributs.shadowExpire,
|
||
]
|
||
default_attribs = [
|
||
attributs.nom, attributs.chbre, attributs.paiement, attributs.info,
|
||
attributs.blacklist, attributs.controle, attributs.historique,
|
||
attributs.debutAdhesion, attributs.finAdhesion, attributs.debutConnexion,
|
||
attributs.finConnexion,
|
||
]
|
||
|
||
@property
|
||
def attribs(self):
|
||
if u'cransAccount' in self['objectClass']:
|
||
return self.default_attribs + self.crans_account_attribs
|
||
else:
|
||
return self.default_attribs
|
||
|
||
def clubs(self):
|
||
"""Renvoie la liste des clubs dont l'adhérent est responsable (surchargée dans les objets adherent)"""
|
||
return []
|
||
|
||
def imprimeur_clubs(self):
|
||
"""Renvoie la liste des clubs dont l'adherent est imprimeur (surchargée dans les objets adherent)"""
|
||
return []
|
||
|
||
def __unicode__(self):
|
||
return u"%s : nom=%s" % (self.__class__.__name__, self['nom'][0])
|
||
|
||
def __repr__(self):
|
||
return repr(self.__unicode__())
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super(proprio, self).__init__(*args, **kwargs)
|
||
self._machines = None
|
||
self._factures = None
|
||
self._factures_last_update = 0
|
||
|
||
def delete_compte(self, mail):
|
||
# Je pense qu'en pratique cette vérification ne sert à rien puisqu'on se fera jetter à la tentative de modification
|
||
# de userPassword, mail, homeDirectory, canonicalAlias, etc…
|
||
if not self.may_be(variables.deleted):
|
||
raise EnvironmentError("Vous n'avez pas le droit de supprimer %s et donc vous ne pouvez supprimer son compte." % self.dn)
|
||
if not u'cransAccount' in self['objectClass']:
|
||
raise EnvironmentError("L'adhérent n'a pas de compte crans")
|
||
else:
|
||
self['userPassword'] = []
|
||
self['mail'] = mail
|
||
self['homeDirectory'] = []
|
||
self['canonicalAlias'] = []
|
||
self['cn'] = []
|
||
self['loginShell'] = []
|
||
self['uidNumber'] = []
|
||
self['gidNumber'] = []
|
||
self['gecos'] = []
|
||
self['shadowExpire'] = []
|
||
self['derniereConnexion'] = []
|
||
self['mailExt'] = []
|
||
self['uid'] = []
|
||
self._modifs['objectClass'] = [u'adherent']
|
||
self.full = False
|
||
|
||
|
||
def compte(self, login=None, uidNumber=0, hash_pass='', shell=config.login_shell):
|
||
u"""Renvoie le nom du compte crans. S'il n'existe pas, et que login
|
||
est précisé, le crée."""
|
||
|
||
if u'posixAccount' in self['objectClass']:
|
||
return self['uid'][0]
|
||
|
||
elif login:
|
||
fn = crans_utils.strip_accents(unicode(self['prenom'][0]).capitalize())
|
||
ln = crans_utils.strip_accents(unicode(self['nom'][0]).capitalize())
|
||
login = crans_utils.strip_spaces(crans_utils.strip_accents(login), by=u'-').lower()
|
||
if not re.match('^[a-z][-a-z]{1,15}$', login):
|
||
raise ValueError("Le login a entre 2 et 16 lettres, il peut contenir (pas au début) des - ")
|
||
if crans_utils.mailexist(login):
|
||
raise ValueError("Login existant ou correspondant à un alias mail.")
|
||
|
||
home = u'/home/' + login
|
||
if os.path.exists(home):
|
||
raise ValueError('Création du compte impossible : home existant')
|
||
|
||
if os.path.exists("/var/mail/" + login):
|
||
raise ValueError('Création du compte impossible : /var/mail/%s existant' % str(login))
|
||
|
||
self._modifs['objectClass'] = [u'adherent', u'cransAccount', u'posixAccount', u'shadowAccount']
|
||
self['uid'] = [login]
|
||
self['homeDirectory'] = [home]
|
||
self['mail'] = [login + u"@crans.org"]
|
||
calias = crans_utils.strip_spaces(fn) + u'.' + crans_utils.strip_spaces(ln) + '@crans.org'
|
||
if crans_utils.mailexist(calias):
|
||
calias = login
|
||
self['canonicalAlias'] = [calias]
|
||
self['cn'] = [fn + u' ' + ln]
|
||
self['loginShell'] = [unicode(shell)]
|
||
self['userPassword'] = [unicode(hash_pass)]
|
||
self["solde"] = 0.0
|
||
|
||
if uidNumber:
|
||
if self.conn.search(u'(uidNumber=%s)' % uidNumber):
|
||
raise ValueError(u'uidNumber pris')
|
||
else:
|
||
pool_uid = range(1001, 9999)
|
||
random.shuffle(pool_uid)
|
||
while len(pool_uid) > 0:
|
||
uidNumber = pool_uid.pop() # On choisit un uid
|
||
if not self.conn.search(u'(uidNumber=%s)' % uidNumber):
|
||
break
|
||
if not len(pool_uid):
|
||
raise ValueError("Plus d'uid disponibles !")
|
||
|
||
self['uidNumber'] = [unicode(uidNumber)]
|
||
self['gidNumber'] = [unicode(config.gid)]
|
||
self['gecos'] = [unicode(self._modifs['cn'][0]) + u',,,']
|
||
|
||
else:
|
||
raise EnvironmentError("L'adhérent n'a pas de compte crans")
|
||
|
||
def may_be(self, what, liste=None):
|
||
"""Teste si liste peut faire ce qui est dans what, pour
|
||
what élément de {create, delete, modify}.
|
||
On passe une liste de droits plutôt que l'objet car il faut ajouter
|
||
les droits soi et parent.
|
||
Retourne un booléen
|
||
"""
|
||
if liste is None:
|
||
liste = self.rights()
|
||
# On ne peut supprimer un objet que si on a au moins autant de droit que lui
|
||
if what == variables.deleted:
|
||
modifiables = set()
|
||
for i in liste:
|
||
if i in attributs.DROITS_SUPERVISEUR:
|
||
modifiables = modifiables.union(attributs.DROITS_SUPERVISEUR[i])
|
||
modifiables = list(modifiables)
|
||
|
||
for droit in self.get('droits', []):
|
||
if droit not in modifiables:
|
||
return False
|
||
# Notez qu'en pratique, ça ne sert à rien puisque can_be_by[variables.deleted]
|
||
# ne contient que nounou et bureau. Ça va juste empêcher le bureau de supprimer
|
||
# des nounous
|
||
|
||
return super(proprio, self).may_be(what, liste)
|
||
|
||
def access_ok(self):
|
||
u"""Renvoie si le propriétaire a payé et donné sa carte pour l'année en cours"""
|
||
return self.paiement_ok()
|
||
|
||
def fin_adhesion(self):
|
||
"""Retourne la date de fin d'adhésion"""
|
||
return max([
|
||
float(facture.get('finAdhesion', [crans_utils.from_generalized_time_format(attributs.finAdhesion.default)])[0])
|
||
for facture in self.factures(refresh=(time.time() - self._factures_last_update > FACTURES_REFRESH_PERIOD))
|
||
if facture.get('controle', [''])[0] != u"FALSE" and facture.get('recuPaiement', [''])[0] != ''
|
||
] + [0.0])
|
||
|
||
def fin_connexion_datetime(self):
|
||
return datetime.datetime.fromtimestamp(self.fin_connexion())
|
||
|
||
def fin_adhesion_datetime(self):
|
||
return datetime.datetime.fromtimestamp(self.fin_adhesion())
|
||
|
||
def fin_connexion(self):
|
||
"""Retourne la date de fin de connexion"""
|
||
return max([
|
||
float(facture.get('finConnexion', [crans_utils.from_generalized_time_format(attributs.finConnexion.default)])[0])
|
||
for facture in self.factures(refresh=(time.time() - self._factures_last_update > FACTURES_REFRESH_PERIOD))
|
||
if facture.get('controle', [''])[0] != u"FALSE" and facture.get('recuPaiement', [''])[0] != ''
|
||
] + [0.0])
|
||
|
||
def adhesion_ok(self, no_bl=False):
|
||
"""Renvoie si le propriétaire a une adhésion en cours."""
|
||
if self.dn == variables.base_dn:
|
||
return True
|
||
|
||
fin_paiement = self.fin_adhesion()
|
||
|
||
paiement = time.time() < fin_paiement or (config.periode_transitoire and config.debut_periode_transitoire <= fin_paiement <= config.fin_periode_transitoire)
|
||
|
||
return paiement
|
||
|
||
def paiement_ok(self, no_bl=False):
|
||
u"""
|
||
Renvoie si le propriétaire a payé pour l'année en cours, en prenant en compte les périodes de transition et les blacklistes.
|
||
``no_bl`` ne devrait être utilisé que par la fonction blacklist_actif lors de la construction des blacklistes virtuelles
|
||
"""
|
||
if self.dn == variables.base_dn:
|
||
return True
|
||
|
||
if not no_bl:
|
||
for bl in self.blacklist_actif():
|
||
if bl['type'] == 'paiement':
|
||
return False
|
||
|
||
if isinstance(self, adherent):
|
||
fin_paiement = min(self.fin_adhesion(), self.fin_connexion())
|
||
else:
|
||
fin_paiement = self.fin_adhesion()
|
||
|
||
paiement = time.time() < fin_paiement or (config.periode_transitoire and config.debut_periode_transitoire <= fin_paiement <= config.fin_periode_transitoire)
|
||
|
||
return paiement
|
||
|
||
def carte_ok(self):
|
||
"""Dummy"""
|
||
return True
|
||
|
||
def carte_controle(self):
|
||
"""Dummy"""
|
||
return True
|
||
|
||
# TODO: gérer cela en modifiant un attribut ?
|
||
def solde(self, diff, comment=u"", login=None):
|
||
"""Modifie le solde du proprio. diff peut être négatif ou positif."""
|
||
if login is None:
|
||
login = self.conn.current_login
|
||
assert isinstance(diff, int) or isinstance(diff, float)
|
||
assert isinstance(comment, unicode)
|
||
|
||
solde = float((self["solde"] or [0.])[0])
|
||
new_solde = solde + diff
|
||
|
||
# On vérifie qu'on ne dépasse par le découvert autorisé
|
||
if new_solde < config.impression.decouvert:
|
||
raise ValueError(u"Solde minimal atteint, opération non effectuée.")
|
||
|
||
transaction = u"credit" if diff >= 0 else u"debit"
|
||
new_solde = u"%.2f" % new_solde
|
||
self.history_add(login, u"%s %.2f Euros [%s]" % (transaction, abs(diff), comment))
|
||
self["solde"] = new_solde
|
||
|
||
def update_solde(self, *args, **kw):
|
||
cranslib.deprecated.usage("Utilisez la méthode solde !", level=2)
|
||
return self.solde(*args, **kw)
|
||
|
||
def machines(self, mode=None, refresh=False):
|
||
"""Renvoie la liste des machines"""
|
||
if self._machines is None or refresh:
|
||
try:
|
||
self._machines = self.conn.search(u'mid=*', dn=self.dn, scope=1, mode=self.mode if mode is None else mode)
|
||
for m in self._machines:
|
||
m._proprio = self
|
||
except ldap.NO_SUCH_OBJECT:
|
||
self._machines = []
|
||
return self._machines
|
||
|
||
def factures(self, refresh=False, mode=None):
|
||
"""Renvoie la liste des factures"""
|
||
if mode is None:
|
||
mode = self.mode
|
||
if self._factures:
|
||
if self._factures[0].mode != mode:
|
||
refresh = True
|
||
if self._factures is None or refresh:
|
||
try:
|
||
self._factures = self.conn.search(u'fid=*', dn=self.dn, scope=1, mode=mode)
|
||
for m in self._factures:
|
||
m._proprio = self
|
||
self._factures_last_update = time.time()
|
||
# Si on manipule un objet pas encore enregistré dans la la bdd
|
||
except ldap.NO_SUCH_OBJECT:
|
||
self._factures = []
|
||
return self._factures
|
||
|
||
def delete(self, comm="", login=None):
|
||
"""Supprimme l'objet de la base LDAP. En supprimant ses enfants d'abord."""
|
||
if login is None:
|
||
login = self.conn.current_login
|
||
if self.mode not in ['w', 'rw']:
|
||
raise EnvironmentError("Objet en lecture seule, réessayer en lecture/écriture")
|
||
if not self.may_be(variables.deleted):
|
||
raise EnvironmentError("Vous n'avez pas le droit de supprimer %s." % self.dn)
|
||
for machine in self.machines():
|
||
machine.delete(comm, login)
|
||
for facture in self.factures():
|
||
with facture:
|
||
facture.delete(comm, login)
|
||
super(proprio, self).delete(comm, login)
|
||
|
||
def get_mail(self):
|
||
"""Renvoie un mail de contact valide, or None"""
|
||
# On récupère les mails parmi les diverses possibilités.
|
||
mails = (
|
||
self.get('canonicalAlias', [])
|
||
or self.get('mail', [])
|
||
or self.get('mailExt', [])
|
||
or self.get('uid', [])
|
||
)
|
||
|
||
# On vérifie si l'adhérent est en mail invalide
|
||
mail_invalide = any([
|
||
b['type'] == 'mail_invalide'
|
||
and b['fin'] == '-'
|
||
for b in self.get('blacklist', [])
|
||
])
|
||
|
||
# Si pas de mail ou mail invalide
|
||
if not mails or mail_invalide:
|
||
# Pour les clubs, on essaye le proprio
|
||
for respo in self.get('responsable', []):
|
||
mail = respo.get_mail()
|
||
if mail:
|
||
return mail
|
||
return None
|
||
|
||
# On récupère le premier de la liste.
|
||
mail = mails[0].value
|
||
|
||
# Login ? @crans.org
|
||
if '@' not in mail:
|
||
mail += '@crans.org'
|
||
|
||
return mail
|
||
|
||
|
||
class machine(CransLdapObject):
|
||
u""" Une machine """
|
||
__slots__ = ("_proprio", "_certificats")
|
||
can_be_by = {
|
||
variables.created: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.cableur,
|
||
attributs.parent,
|
||
attributs.respo,
|
||
],
|
||
variables.modified: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.cableur,
|
||
attributs.parent,
|
||
attributs.respo,
|
||
],
|
||
variables.deleted: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.cableur,
|
||
attributs.parent,
|
||
attributs.respo,
|
||
],
|
||
}
|
||
|
||
attribs = [
|
||
attributs.mid, attributs.macAddress, attributs.host,
|
||
attributs.rid, attributs.info, attributs.blacklist, attributs.hostAlias,
|
||
attributs.exempt, attributs.portTCPout, attributs.portTCPin,
|
||
attributs.portUDPout, attributs.portUDPin, attributs.sshFingerprint,
|
||
attributs.ipHostNumber, attributs.ip6HostNumber, attributs.historique,
|
||
attributs.dnsIpv6, attributs.machineAlias,
|
||
]
|
||
|
||
def __unicode__(self):
|
||
return u"%s : host=%s" % (self.__class__.__name__, self['host'][0])
|
||
|
||
def __repr__(self):
|
||
return repr(self.__unicode__())
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super(machine, self).__init__(*args, **kwargs)
|
||
self._proprio = None
|
||
self._certificats = None
|
||
|
||
def _check_setitem(self, attr, values):
|
||
"""
|
||
Vérifie des contraintes non liées à LDAP lors d'un __setitem__,
|
||
lève une exception si elles ne sont pas vérifiées.
|
||
Ici on vérifie qu'on ne retire pas un host à une machine tant
|
||
qu'un de ses certificats l'utilise.
|
||
"""
|
||
if attr in ['host', 'hostAlias']:
|
||
deleted = [value for value in self[attr] if value not in values]
|
||
for domain in deleted:
|
||
for certificat in self.certificats():
|
||
if domain in certificat['hostCert']:
|
||
raise EnvironmentError("Vous devez d'abord supprimer ou éditer les certificats utilisant le nom de domaine %s avant de le retirer de la machine" % domain)
|
||
|
||
def proprio(self, mode=None, refresh=False):
|
||
u"""Renvoie le propriétaire de la machine"""
|
||
if not hasattr(self, '_proprio') or not self._proprio or refresh:
|
||
self._proprio = new_cransldapobject(self.conn, self.parent_dn, self.mode if mode is None else mode)
|
||
return self._proprio
|
||
|
||
def certificats(self, refresh=False):
|
||
"""Renvoie la liste des certificats de la machine"""
|
||
if refresh or self._certificats is None:
|
||
try:
|
||
self._certificats = self.conn.search(u'xid=*', dn=self.dn, scope=1, mode=self.mode)
|
||
for m in self._certificats:
|
||
m._machine = self
|
||
except ldap.NO_SUCH_OBJECT:
|
||
self._certificats = []
|
||
return self._certificats
|
||
|
||
def blacklist_actif(self, excepts=[]):
|
||
u"""Renvoie la liste des blacklistes actives sur la machine et le proprio"""
|
||
black = self.proprio().blacklist_actif(excepts)
|
||
black.extend(super(machine, self).blacklist_actif(excepts))
|
||
return black
|
||
|
||
def _post_creation(self):
|
||
"""Fonction qui effectue quelques tâches lorsque la création est
|
||
faite"""
|
||
if self._proprio is not None:
|
||
if self._proprio._machines is not None:
|
||
self._proprio._machines.append(self)
|
||
|
||
def _post_deletion(self):
|
||
"""Fonction qui effectue quelques tâches lorsque l'on veut effacer"""
|
||
if self._proprio is not None:
|
||
if self._proprio._machines is not None:
|
||
# On duplique la liste pour ne pas la modifier en place
|
||
# C'est très important, car des scripts pourraient se servir
|
||
# de l'ancienne (itérer dessus par exemple)
|
||
self._proprio._machines = list(self._proprio._machines)
|
||
self._proprio._machines.remove(self)
|
||
|
||
def check_changes(self):
|
||
"""Certaines propriétés sont liées les unes aux autres de façon non-
|
||
intrinsèque à LDAP, parce qu'on veut qu'il en soit ainsi. Cette
|
||
fonction crée un dictionnaire des choses qui devraient être modifiées,
|
||
sbm, et le renvoie. Par exemple, si le rid devrait passer de 42 à 1337,
|
||
alors la clef 'rid' de sbm contiendra le tuple (42, 1337)."""
|
||
old = {}
|
||
new = {}
|
||
sbm = {'rid' : (), 'ipHostNumber' : (), 'ip6HostNumber' : ()}
|
||
default = {'rid': -1, 'ipHostNumber': u'', 'macAddress': u''}
|
||
for i in ['rid', 'ipHostNumber', 'macAddress']:
|
||
# On stocke dans old et new l'ancienne et la nouvelle
|
||
# valeur de chaque attribut concerné.
|
||
try:
|
||
old[i] = self.attrs[i][0].value
|
||
except:
|
||
old[i] = default[i]
|
||
try:
|
||
new[i] = self._modifs[i][0].value
|
||
except:
|
||
new[i] = default[i]
|
||
# Si le rid est changé, on met à jour ip4 et 6, en tenant compte
|
||
# des éventuels changements de mac.
|
||
if old['rid'] != new['rid']:
|
||
nip4 = unicode(crans_utils.ip4_of_rid(new['rid']))
|
||
oip4 = unicode(new['ipHostNumber'])
|
||
if oip4 != nip4:
|
||
sbm['ipHostNumber'] = (oip4, nip4)
|
||
nip6 = unicode(crans_utils.ip6_of_mac(new['macAddress'], new['rid']))
|
||
try:
|
||
oip6 = unicode(self._modifs['ip6HostNumber'][0])
|
||
except:
|
||
oip6 = u""
|
||
if oip6 != nip6:
|
||
sbm['ip6HostNumber'] = (oip6, nip6)
|
||
# Les ipHostNumber sont des objets netaddr, on les cast en unicode
|
||
# si l'ip4 a changé, il suffit de changer le rid, en effet, l'ip6 peut
|
||
# subsister malgré tout. On retourne alors -1 pour le rid, et on fera
|
||
# le changement adapté ensuite.
|
||
elif unicode(old['ipHostNumber']) != unicode(new['ipHostNumber']):
|
||
nrid = crans_utils.rid_of_ip4(new['ipHostNumber'])
|
||
orid = new['rid']
|
||
if nrid != orid:
|
||
sbm['rid'] = (orid, nrid)
|
||
# Les macAddress sont déjà des unicodes.
|
||
# On change l'ip6
|
||
elif old['macAddress'] != new['macAddress']:
|
||
nip6 = unicode(crans_utils.ip6_of_mac(new['macAddress'], new['rid']))
|
||
try:
|
||
oip6 = unicode(self._modifs['ip6HostNumber'][0])
|
||
except:
|
||
oip6 = u""
|
||
if oip6 != nip6:
|
||
sbm['ip6HostNumber'] = (oip6, nip6)
|
||
return sbm
|
||
|
||
def validate_changes(self):
|
||
sbm = self.check_changes()
|
||
if sbm['rid']:
|
||
# Si le rid est à -1, on agit en conséquence si on a une ipv6.
|
||
# Je me demande simplement pourquoi je l'ai pas fait au dessus, dans
|
||
# check_changes.
|
||
if sbm['rid'][1] == -1:
|
||
try:
|
||
ip6 = unicode(self._modifs['ip6HostNumber'][0])
|
||
except:
|
||
ip6 = u""
|
||
if ip6 != u"":
|
||
realm = crans_utils.find_rid_plage(sbm['rid'][0])[0]
|
||
if 'v6' not in realm:
|
||
realm = realm + "-v6"
|
||
self['rid'] = [unicode(self.conn._find_id('rid', realm))]
|
||
self['ip6HostNumber'] = [unicode(crans_utils.ip6_of_mac(self['macAddress'][0].value, self['rid'][0].value))]
|
||
else:
|
||
self['ipHostNumber'] = []
|
||
self['ip6HostNumber'] = []
|
||
else:
|
||
if unicode(self['ipHostNumber'][0]) != unicode(crans_utils.ip4_of_rid(sbm['rid'][1])):
|
||
raise ValueError("L'ipv4 et le rid ne concordent pas !")
|
||
self['ip6HostNumber'] = [unicode(crans_utils.ip6_of_mac(self['macAddress'][0].value, self['rid'][0].value))]
|
||
if sbm['ipHostNumber']:
|
||
if sbm['ipHostNumber'][1] == u"":
|
||
ip4 = []
|
||
else:
|
||
ip4 = sbm['ipHostNumber'][1]
|
||
self['ipHostNumber'] = ip4
|
||
if sbm['ip6HostNumber']:
|
||
if sbm['ip6HostNumber'][1] == u"":
|
||
ip6 = []
|
||
else:
|
||
ip6 = sbm['ip6HostNumber'][1]
|
||
self['ip6HostNumber'] = ip6
|
||
|
||
def delete(self, comm="", login=None):
|
||
"""Supprimme l'objet de la base LDAP. En supprimant ses enfants d'abord."""
|
||
if login is None:
|
||
login = self.conn.current_login
|
||
if self.mode not in ['w', 'rw']:
|
||
raise EnvironmentError("Objet en lecture seule, réessayer en lecture/écriture")
|
||
if not self.may_be(variables.deleted):
|
||
raise EnvironmentError("Vous n'avez pas le droit de supprimer %s." % self.dn)
|
||
for certificat in self.certificats():
|
||
certificat.delete(comm, login)
|
||
super(machine, self).delete(comm, login)
|
||
|
||
class AssociationCrans(proprio):
|
||
""" Association crans (propriétaire particulier)."""
|
||
__slots__ = ()
|
||
def save(self):
|
||
pass
|
||
|
||
def ressuscite(self, comm, login):
|
||
pass
|
||
|
||
def delete(self, comm, login):
|
||
pass
|
||
|
||
def __unicode__(self):
|
||
return u"Le Crans"
|
||
|
||
def __repr__(self):
|
||
return repr(self.__unicode__())
|
||
|
||
class BaseInvites(proprio):
|
||
u"""Un artefact de la base ldap"""
|
||
__slots__ = ()
|
||
def __unicode__(self):
|
||
return u"%s" % (self.__class__.__name__,)
|
||
|
||
def __repr__(self):
|
||
return repr(self.__unicode__())
|
||
|
||
def delete(self, comm, login):
|
||
raise EnvironmentError("Les pauvres invites")
|
||
|
||
@crans_object
|
||
class adherent(proprio):
|
||
u"""Adhérent crans."""
|
||
__slots__ = ("_clubs", "_imprimeur_clubs")
|
||
|
||
@property
|
||
def attribs(self):
|
||
return super(adherent, self).attribs + [
|
||
attributs.aid, attributs.prenom, attributs.tel,
|
||
attributs.mail, attributs.mailInvalide, attributs.charteMA,
|
||
attributs.derniereConnexion, attributs.gpgFingerprint,
|
||
attributs.carteEtudiant, attributs.etudes,
|
||
attributs.postalAddress, attributs.gpgMail,
|
||
]
|
||
ldap_name = "adherent"
|
||
|
||
def __unicode__(self):
|
||
return u"Adhérent : %s %s" % (self['prenom'][0], self['nom'][0])
|
||
|
||
def __repr__(self):
|
||
return repr(self.__unicode__())
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super(adherent, self).__init__(*args, **kwargs)
|
||
self.full = False
|
||
self._clubs = None
|
||
self._imprimeur_clubs = None
|
||
|
||
def clubs(self):
|
||
"""Renvoie la liste des clubs dont l'adherent est responsable"""
|
||
if self._clubs is None:
|
||
self._clubs = self.conn.search(u'responsable=%s' % self['aid'][0], scope=1, mode=self.mode)
|
||
return self._clubs
|
||
|
||
def imprimeur_clubs(self):
|
||
"""Renvoie la liste des clubs dont l'adherent est imprimeur"""
|
||
if self._imprimeur_clubs is None:
|
||
self._imprimeur_clubs = self.conn.search(u'imprimeurClub=%s' % self['aid'][0], scope=1, mode=self.mode)
|
||
return self._imprimeur_clubs
|
||
|
||
def delete(self, comm="", login=None):
|
||
clubs = self.conn.search(u"(|(responsable=%s)(imprimeurClub=%s))" % (self['aid'][0], self['aid'][0]))
|
||
if clubs:
|
||
raise EnvironmentError("L'adhérent est responsable ou imprimeur pour les clubs %s, suppression impossible" % ", ".join(str(c["cid"][0]) for c in clubs))
|
||
super(adherent, self).delete(comm, login)
|
||
|
||
@crans_object
|
||
class club(proprio):
|
||
u"""Club crans"""
|
||
__slots__ = ()
|
||
can_be_by = {
|
||
variables.created: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.cableur,
|
||
],
|
||
variables.modified: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.respo,
|
||
attributs.cableur,
|
||
attributs.soi,
|
||
],
|
||
variables.deleted: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
],
|
||
}
|
||
ldap_name = "club"
|
||
|
||
@property
|
||
def attribs(self):
|
||
return super(club, self).attribs + [attributs.cid, attributs.responsable, attributs.imprimeurClub]
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super(club, self).__init__(*args, **kwargs)
|
||
|
||
def __unicode__(self):
|
||
return u"Club : %s" % (self['nom'][0],)
|
||
|
||
def __repr__(self):
|
||
return repr(self.__unicode__())
|
||
|
||
@crans_object
|
||
class machineFixe(machine):
|
||
u"""Machine fixe"""
|
||
__slots__ = ()
|
||
ldap_name = "machineFixe"
|
||
|
||
class machineMulticast(machine):
|
||
u"""Machine pour inféré à partir des announces sap"""
|
||
__slots__ = ()
|
||
ldap_name = None
|
||
def save(self):
|
||
pass
|
||
def delete(self):
|
||
pass
|
||
def create(self):
|
||
pass
|
||
def ressuscite(self, comm, login):
|
||
pass
|
||
def proprio(self, mode=None):
|
||
return None
|
||
def certificats(self):
|
||
return []
|
||
|
||
@crans_object
|
||
class machineWifi(machine):
|
||
u"""Machine wifi"""
|
||
__slots__ = ()
|
||
attribs = machine.attribs + [attributs.ipsec]
|
||
ldap_name = "machineWifi"
|
||
|
||
# À passer là où il faut
|
||
# def set_ipv4(self, login=None):
|
||
# u"""Définie une ipv4 à la machine si elle n'est possède pas déjà une."""
|
||
# if login is None:
|
||
# login = self.conn.current_login
|
||
# if not 'ipHostNumber' in self.attrs.keys() or not self['ipHostNumber']:
|
||
# rid = self['rid']=[ unicode(self.conn._find_id('rid', range(config.rid['wifi'][0], config.rid['wifi'][1]+1))) ]
|
||
# ip = self['ipHostNumber'] = [ unicode(crans_utils.ip4_of_rid(int(rid[0]))) ]
|
||
# self.history_add(login, u"rid")
|
||
# self.history_add(login, u"ipHostNumber (N/A -> %s)" % ip[0])
|
||
# self.save()
|
||
# from gen_confs.dhcpd_new import dydhcp
|
||
# dhcp=dydhcp()
|
||
# dhcp.add_host(str(self['ipHostNumber'][0]), str(self['macAddress'][0]), str(self['host'][0]))
|
||
|
||
@crans_object
|
||
class machineCrans(machine):
|
||
__slots__ = ()
|
||
can_be_by = {
|
||
variables.created: [
|
||
attributs.nounou,
|
||
],
|
||
variables.modified: [
|
||
attributs.nounou,
|
||
],
|
||
variables.deleted: [
|
||
attributs.nounou,
|
||
],
|
||
}
|
||
attribs = machine.attribs + [
|
||
attributs.prise, attributs.nombrePrises,
|
||
]
|
||
ldap_name = "machineCrans"
|
||
|
||
@crans_object
|
||
class borneWifi(machine):
|
||
__slots__ = ()
|
||
can_be_by = {
|
||
variables.created: [
|
||
attributs.nounou,
|
||
],
|
||
variables.modified: [
|
||
attributs.nounou,
|
||
],
|
||
variables.deleted: [
|
||
attributs.nounou,
|
||
],
|
||
}
|
||
attribs = machine.attribs + [
|
||
attributs.canal, attributs.puissance, attributs.hotspot,
|
||
attributs.prise, attributs.positionBorne, attributs.nvram,
|
||
]
|
||
ldap_name = "borneWifi"
|
||
|
||
@crans_object
|
||
class switchCrans(machine):
|
||
__slots__ = ()
|
||
can_be_by = {
|
||
variables.created: [
|
||
attributs.nounou,
|
||
],
|
||
variables.modified: [
|
||
attributs.nounou,
|
||
],
|
||
variables.deleted: [
|
||
attributs.nounou,
|
||
],
|
||
}
|
||
attribs = machine.attribs + [
|
||
attributs.nombrePrises,
|
||
]
|
||
|
||
ldap_name = "switchCrans"
|
||
|
||
@crans_object
|
||
class facture(CransLdapObject):
|
||
__slots__ = ("_proprio", "_recuPaiement")
|
||
can_be_by = {
|
||
variables.created: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.cableur,
|
||
],
|
||
variables.modified: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.cableur,
|
||
],
|
||
variables.deleted: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.cableur,
|
||
],
|
||
}
|
||
attribs = [
|
||
attributs.fid, attributs.modePaiement, attributs.recuPaiement,
|
||
attributs.historique, attributs.article, attributs.info,
|
||
attributs.debutAdhesion, attributs.finAdhesion, attributs.debutConnexion,
|
||
attributs.finConnexion, attributs.controle,
|
||
]
|
||
ldap_name = "facture"
|
||
|
||
def __unicode__(self):
|
||
return u"Facture : fid=%s" % (self['fid'][0],)
|
||
|
||
def __repr__(self):
|
||
return repr(self.__unicode__())
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
self._proprio = None
|
||
super(facture, self).__init__(*args, **kwargs)
|
||
self._recuPaiement = True if self['recuPaiement'] else False
|
||
|
||
def __setitem__(self, attr, value):
|
||
if self._recuPaiement and attr in ['article', 'modePaiement', 'recuPaiement']:
|
||
raise EnvironmentError("Paiement déjà effectué pour cette facture, impossible de modifier son contenu")
|
||
return super(facture, self).__setitem__(attr, value)
|
||
|
||
def total(self):
|
||
total = 0
|
||
for article in self["article"]:
|
||
total += int(article['nombre'])*float(article['pu'])
|
||
return total
|
||
|
||
def crediter(self):
|
||
"""
|
||
Crédite les articles à son propriétaire
|
||
"""
|
||
|
||
def credite_arts(proprio, cancel=False):
|
||
sign = -1 if cancel else +1
|
||
proprio_save = False
|
||
# on crédite les articles
|
||
for art in self['article']:
|
||
# solde impression (on débite d'abord si jamais quelqu'un s'amuse à recharger son solde avec son solde)
|
||
if self['modePaiement'][0] == 'solde':
|
||
proprio.solde(sign * (0.0 - self.total()), u"Facture n°%s" % self['fid'][0])
|
||
proprio_save = True
|
||
if art["code"] == "SOLDE":
|
||
proprio.solde(sign * (int(art['nombre'])*float(art["pu"])), u"Facture n°%s : %s" % (self['fid'][0], art['designation']))
|
||
proprio_save = True
|
||
return proprio_save
|
||
|
||
if not self._recuPaiement:
|
||
with self.proprio() as proprio:
|
||
proprio_save = credite_arts(proprio)
|
||
# On vient de créditer, le paiement a été reçu
|
||
self['recuPaiement'] = unicode(time.strftime("%Y-%m-%d %H:%M:%S"))
|
||
self._recuPaiement = True
|
||
|
||
# Il faudrait faire quelquechose pour que si l'enregistrement suivant de la facture crash,
|
||
# on défait ce qu'on fait sur le proprio plus proprement
|
||
if proprio_save:
|
||
proprio.save()
|
||
|
||
# On force l'enregistrement de la facture après avoir crédité
|
||
try:
|
||
if self.exists():
|
||
self.save()
|
||
else:
|
||
self.create()
|
||
except:
|
||
if proprio_save:
|
||
credite_arts(proprio, cancel=True)
|
||
proprio.save()
|
||
raise
|
||
|
||
def proprio(self, refresh=False):
|
||
u"""Renvoie le propriétaire de la facture"""
|
||
if refresh or not self._proprio:
|
||
self._proprio = new_cransldapobject(self.conn, self.parent_dn, self.mode)
|
||
return self._proprio
|
||
|
||
@crans_object
|
||
class baseCert(CransLdapObject):
|
||
__slots__ = ("_machine",)
|
||
can_be_by = {
|
||
variables.created: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.parent,
|
||
],
|
||
variables.modified: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.parent,
|
||
],
|
||
variables.deleted: [
|
||
attributs.nounou,
|
||
attributs.bureau,
|
||
attributs.parent,
|
||
],
|
||
}
|
||
default_attribs = [
|
||
attributs.xid, attributs.certificat, attributs.hostCert, attributs.historique,
|
||
attributs.info, attributs.csr,
|
||
]
|
||
|
||
tlsa_attribs = [
|
||
attributs.certificatUsage, attributs.selector, attributs.matchingType,
|
||
attributs.portTCPin, attributs.portUDPin,
|
||
]
|
||
|
||
x509_attribs = [
|
||
attributs.issuerCN, attributs.start, attributs.end,
|
||
attributs.crlUrl, attributs.revocked, attributs.serialNumber,
|
||
]
|
||
|
||
private_attribs = [
|
||
attributs.privatekey, attributs.encrypted,
|
||
]
|
||
|
||
@property
|
||
def attribs(self):
|
||
attribs = list(self.default_attribs)
|
||
if "TLSACert" in self['objectClass']:
|
||
attribs.extend(self.tlsa_attribs)
|
||
if 'x509Cert' in self['objectClass']:
|
||
attribs.extend(self.x509_attribs)
|
||
if "privateKey" in self['objectClass']:
|
||
attribs.extend(self.private_attribs)
|
||
return attribs
|
||
ldap_name = "baseCert"
|
||
|
||
protected_issuer = [u'CAcert Class 3 Root', u'CA Cert Signing Authority']
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
self._machine = None
|
||
super(baseCert, self).__init__(*args, **kwargs)
|
||
|
||
|
||
def __repr__(self):
|
||
if self['info']:
|
||
return "Certificat : %s (xid=%s)" % (self['info'][0], str(self['xid'][0]))
|
||
else:
|
||
return "Certificat : xid=" + str(self['xid'][0])
|
||
|
||
def _check_setitem(self, attr, values):
|
||
"""
|
||
Vérifie des contraintes non liées à LDAP lors d'un __setitem__,
|
||
lève une exception si elles ne sont pas vérifiées.
|
||
Ici on vérifie d'on ne retire pas un host du certificat
|
||
s'il est réèlement présent dans les données du certificat.
|
||
"""
|
||
if attr in ['hostCert']:
|
||
deleted = [value for value in self[attr] if value not in values]
|
||
for domain in deleted:
|
||
if domain in [self['certificat'][0]['subject']['CN']] + self['certificat'][0]['extensions'].get('subjectAltName', []):
|
||
raise EnvironmentError("Vous ne pouvez pas retirer le domaine %s alors qu'il est déclaré dans le certificat" % domain)
|
||
|
||
def private(self, privatekey, encrypted):
|
||
if not self.mode in ['w', 'rw']:
|
||
return
|
||
if u"privateKey" in self['objectClass']:
|
||
return
|
||
self._modifs['objectClass'].append(u"privateKey")
|
||
#self.attribs.extend(self.private_attribs)
|
||
self['encrypted'] = encrypted
|
||
self['privatekey'] = privatekey
|
||
|
||
def tlsa(self, certificatUsage, matchingType):
|
||
if not self.mode in ['w', 'rw']:
|
||
return
|
||
if u"TLSACert" in self['objectClass']:
|
||
return
|
||
self._modifs['objectClass'].append(u"TLSACert")
|
||
#self.attribs.extend(self.tlsa_attribs)
|
||
self['certificatUsage'] = certificatUsage
|
||
self['matchingType'] = matchingType
|
||
self['selector'] = 0
|
||
|
||
def x509(self, issuerCN, start, end, serialNumber, crlUrl=None):
|
||
if not self.mode in ['w', 'rw']:
|
||
return
|
||
if u"x509Cert" in self['objectClass']:
|
||
return
|
||
self._modifs['objectClass'].append(u"x509Cert")
|
||
#self.attribs.extend(self.x509_attribs)
|
||
self['issuerCN'] = issuerCN
|
||
self['start'] = start
|
||
self['end'] = end
|
||
self['serialNumber'] = serialNumber
|
||
if crlUrl:
|
||
self['crlUrl'] = crlUrl
|
||
|
||
|
||
def machine(self, refresh=False):
|
||
u"""Renvoie la machine du certificat"""
|
||
if refresh or not self._machine:
|
||
self._machine = new_cransldapobject(self.conn, self.parent_dn, self.mode)
|
||
return self._machine
|
||
|
||
|
||
def delete(self, comm="", login=None):
|
||
"""Supprimme l'objet de la base LDAP."""
|
||
if u"x509Cert" in self['objectClass']:
|
||
# Si le certificat est encore valide et qu'il n'a pas été révoqué
|
||
if self['end'] > int(time.time()) and (not self['revocked'] or not self['revocked'][0]):
|
||
for issuer in self['issuerCN']:
|
||
if issuer in self.protected_issuer:
|
||
raise EnvironmentError("Vous n'avez pas le droit de supprimer %s tant que le certificat n'aura pas expiré ou été marqué comme révoqué" % self.dn)
|
||
super(baseCert, self).delete(comm, login)
|
||
|
||
|
||
@crans_object
|
||
class service(CransLdapObject):
|
||
__slots__ = ()
|
||
ldap_name = "service"
|
||
|
||
|
||
import services
|