1057 lines
44 KiB
Python
1057 lines
44 KiB
Python
#!/usr/bin/env python
|
||
# -*- coding: utf-8 -*-
|
||
#
|
||
# LC_LDAP.PY-- LightWeight CransLdap
|
||
#
|
||
# 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.
|
||
|
||
from __future__ import with_statement
|
||
import os
|
||
import sys
|
||
|
||
import ldap
|
||
import ldap.filter
|
||
from ldap.modlist import addModlist, modifyModlist
|
||
|
||
import re
|
||
import netaddr
|
||
import datetime
|
||
import copy
|
||
import time
|
||
import random
|
||
import string
|
||
|
||
try:
|
||
from Levenshtein import jaro
|
||
except ImportError:
|
||
def jaro(a, b): return 0
|
||
sys.path.append('/usr/scripts/gestion')
|
||
|
||
import config
|
||
import crans_utils
|
||
import attributs
|
||
import ldap_locks
|
||
|
||
uri = 'ldap://ldap.adm.crans.org/'
|
||
base_dn = 'ou=data,dc=crans,dc=org'
|
||
log_dn = "cn=log"
|
||
admin_dn = "cn=admin,dc=crans,dc=org"
|
||
|
||
# Protection contre les typos
|
||
created = 'created'
|
||
modified = 'modified'
|
||
deleted = 'deleted'
|
||
|
||
# Pour enregistrer dans l'historique, on a besoin de savoir qui exécute le script
|
||
# Si le script a été exécuté via un sudo, la variable SUDO_USER (l'utilisateur qui a effectué le sudo)
|
||
# est plus pertinente que USER (qui sera root)
|
||
current_user = os.getenv("SUDO_USER") or os.getenv("USER")
|
||
|
||
# Quand on a besoin du fichier de secrets
|
||
def import_secrets():
|
||
"""Importe le fichier de secrets"""
|
||
if not "/etc/crans/secrets/" in sys.path:
|
||
sys.path.append("/etc/crans/secrets/")
|
||
import secrets
|
||
return secrets
|
||
|
||
# Champs à ignorer dans l'historique
|
||
HIST_IGNORE_FIELDS = ["modifiersName", "entryCSN", "modifyTimestamp", "historique"]
|
||
|
||
def escape(chaine):
|
||
"""Renvoie une chaîne échapée pour pouvoir la mettre en toute sécurité
|
||
dans une requête ldap."""
|
||
return ldap.filter.escape_filter_chars(chaine)
|
||
|
||
# A priori, ldif_to_uldif et ldif_to_cldif sont obsolètes,
|
||
# du fait de l'apparition de AttrsDict dans attributs.py
|
||
def ldif_to_uldif(ldif):
|
||
"""
|
||
Transforme un dictionnaire ldif en un dictionnaire
|
||
ldif unicode.
|
||
"""
|
||
uldif = {}
|
||
for attr, vals in ldif.items():
|
||
uldif[attr] = [ unicode(val, 'utf-8') for val in vals ]
|
||
return uldif
|
||
|
||
#def ldif_to_cldif(ldif, conn):
|
||
# """Transforme un dictionnaire renvoyé par python-ldap, en
|
||
# un dictionnaire dont les valeurs sont des instances de Attr
|
||
# Lorsqu'on récupère le ldif de la base ldap, on n'a pas besoin
|
||
# de faire de tests...
|
||
# """
|
||
# cldif = {}
|
||
# for attr, vals in ldif.items():
|
||
# cldif[attr] = [ attributs.attrify(val, attr, conn, ldif) for val in vals]
|
||
# return cldif
|
||
|
||
def cldif_to_ldif(cldif):
|
||
"""
|
||
Transforme un ldif crans en ldif valide au sens openldap.
|
||
Ce ldif est celui qui sera transmis à la base.
|
||
"""
|
||
ldif = {}
|
||
for attr, vals in cldif.items():
|
||
ldif[attr] = [ str(val) for val in vals ]
|
||
return ldif
|
||
|
||
class lc_ldap(ldap.ldapobject.LDAPObject, object):
|
||
"""Connexion à la base ldap crans, chaque instance représente une connexion
|
||
"""
|
||
def __init__(self, dn=None, user=None, cred=None, uri=uri, test=False):
|
||
"""Initialise la connexion ldap,
|
||
- En authentifiant avec ``dn`` et ``cred`` s'ils sont précisés
|
||
- Si ``dn`` n'est pas précisé, mais que ``user`` est précisé, récupère
|
||
le ``dn`` associé à l'uid ``user``, et effectue l'authentification
|
||
avec ce ``dn`` et ``cred``
|
||
- Sinon effectue une authentification anonyme
|
||
Si ``test`` est à ``True``, on se connecte à la base de test sur ``vo``.
|
||
"""
|
||
if test:
|
||
uri = "ldapi:///"
|
||
ldap.ldapobject.LDAPObject.__init__(self, uri)
|
||
|
||
if user and not re.match('[a-z_][a-z0-9_-]*', user):
|
||
raise ValueError('Invalid user name: %r' % user)
|
||
|
||
# Si un username, on récupère le dn associé…
|
||
if user and not dn:
|
||
dn = self.user_to_dn(user)
|
||
|
||
# Si on a un dn, on se connecte avec à la base ldap sinon on s'y
|
||
# connecte en anonyme
|
||
if dn:
|
||
#secrets = import_secrets()
|
||
self.conn = self.bind_s(dn, cred)
|
||
self.dn = dn
|
||
self.droits = self.search_s(dn, ldap.SCOPE_BASE, attrlist=['droits'])[0][1].get('droits', [])
|
||
if dn == admin_dn:
|
||
self.droits += [attributs.nounou]
|
||
else:
|
||
self.conn = self.simple_bind_s()
|
||
self.dn = None
|
||
self.droits = []
|
||
|
||
def ressuscite(self, ldif_file, login=current_user):
|
||
ldif={}
|
||
for line in open(ldif_file).readlines():
|
||
line = line.split(':',1)
|
||
if len(line)==2:
|
||
(key, value) = line
|
||
ldif[key]=ldif.get(key, []) + [value.strip()]
|
||
dn = ldif['dn'][0]
|
||
del(ldif['dn'])
|
||
try:
|
||
if self.search(dn=dn):
|
||
raise ValueError ('objet existant: %s' % dn)
|
||
except ldap.NO_SUCH_OBJECT:
|
||
pass
|
||
obj = new_cransldapobject(self, dn, mode='rw', ldif=ldif)
|
||
obj.history_add(login, u"resurrection")
|
||
return obj
|
||
|
||
def user_to_dn(self, user):
|
||
"""Cherche le dn à partir de l'username."""
|
||
self._first_bind()
|
||
res = self.search_s(base_dn, 1, 'uid=%s' % user)
|
||
if len(res) < 1:
|
||
raise ldap.INVALID_CREDENTIALS({'desc': 'No such user: %s' % user })
|
||
elif len(res) > 1:
|
||
raise ldap.INVALID_CREDENTIALS({'desc': 'Too many matches: uid=%s' % user })
|
||
else:
|
||
dn = res[0][0]
|
||
return dn
|
||
|
||
def _first_bind(self):
|
||
"""Premier bind, avant de connaître le vrai dn."""
|
||
secrets = import_secrets()
|
||
self.simple_bind_s(who=secrets.ldap_readonly_auth_dn, cred=secrets.ldap_readonly_password)
|
||
|
||
def search(self, filterstr='(objectClass=*)', mode='ro', dn= base_dn, scope=ldap.SCOPE_SUBTREE, sizelimit=1000):
|
||
"""La fonction de recherche dans la base LDAP qui renvoie un liste de
|
||
:py:class:`CransLdapObject`. On utilise la feature de ``sizelimit`` de
|
||
``python-ldap``"""
|
||
ldap_res = self.search_ext_s(dn, scope, filterstr, sizelimit=sizelimit)
|
||
ret = []
|
||
for dn, ldif in ldap_res:
|
||
ret.append(new_cransldapobject(self, dn, mode, ldif))
|
||
return ret
|
||
|
||
def allMachinesAdherents(self):
|
||
"""Renvoie la liste de toutes les machines et de tous les adherents
|
||
(club et Association Crans compris). Conçue pour s'éxécuter le plus
|
||
rapidement possible. On dumpe malgré tout toute la base."""
|
||
res = {}
|
||
parent = {}
|
||
machines = {}
|
||
# (proxying de la base ldap)
|
||
for dn, attrs in self.search_s(base_dn, scope=2):
|
||
# On crée les listes des machines et propriétaires
|
||
if dn.startswith('mid='): # les machines
|
||
m = new_cransldapobject(self, dn, ldif = attrs)
|
||
parent_dn = dn.split(',', 1)[1]
|
||
if not machines.has_key(parent_dn):
|
||
machines[parent_dn] = []
|
||
machines[parent_dn].append(m)
|
||
elif (dn.startswith('aid=') or dn.startswith('cid=') or dn == base_dn) and not parent.has_key(dn):
|
||
parent[dn] = new_cransldapobject(self, dn, ldif = attrs)
|
||
allmachines = []
|
||
for dn,mlist in machines.iteritems(): # on associe propriétaires et machines
|
||
parent[dn]._machines = mlist
|
||
for m in mlist:
|
||
m._proprio = parent[dn]
|
||
allmachines.append(m)
|
||
return allmachines, parent.values() # on renvoie la liste des machines et des adherents (dont club et crans)
|
||
|
||
def allMachines(self):
|
||
"""Renvoie la liste de toutes les machines, Conçue pour
|
||
s'éxécuter le plus rapidement possible. On dumpe malgré tout
|
||
toute la base, c'est pour pouvoir aussi rajouter à moindre coût
|
||
les propriétaires."""
|
||
machines,_ = self.allMachinesAdherents()
|
||
return machines
|
||
|
||
def allAdherents(self):
|
||
"""Renvoie la liste de toutes les adherents, Conçue pour
|
||
s'éxécuter le plus rapidement possible. On dumpe malgré tout
|
||
toute la base, c'est pour pouvoir aussi rajouter à moindre coût
|
||
les machines."""
|
||
_,adherents = self.allMachinesAdherents()
|
||
return adherents
|
||
|
||
def newMachine(self, parent, realm, ldif, login=None):
|
||
"""Crée une nouvelle machine: ``realm`` peut être:
|
||
fil, fil-v6, wifi, wifi-v6, adm, gratuit, personnel-ens, special
|
||
--Partiellement implémenté"""
|
||
#adm, serveurs, bornes, wifi, adherents, gratuit ou personnel-ens"""
|
||
owner = self.search('objectClass=*', dn=parent, scope=0)[0]
|
||
|
||
if realm in ["adm", "serveurs", "serveurs-v6", "adm-v6"]:
|
||
ldif['objectClass'] = ['machineCrans']
|
||
assert isinstance(owner, AssociationCrans)
|
||
|
||
elif realm == "bornes":
|
||
ldif['objectClass'] = ['borneWifi']
|
||
assert isinstance(owner, AssociationCrans)
|
||
|
||
elif realm in ["wifi", "wifi-v6"]:
|
||
ldif['objectClass'] = ['machineWifi']
|
||
assert isinstance(owner, adherent) or isinstance(owner, club)
|
||
|
||
elif realm in ["adherents", "fil-adherents", "fil-v6", "personnel-ens"]:
|
||
ldif['objectClass'] = ['machineFixe']
|
||
assert isinstance(owner, adherent) or isinstance(owner, club)
|
||
|
||
else: raise ValueError("Realm inconnu: %r" % realm)
|
||
|
||
# On récupère la plage des mids
|
||
plage = xrange( *(config.rid[realm]))
|
||
# On récupère le premier id libre dans la plages s'il n'est pas
|
||
# déjà précisé dans le ldiff
|
||
rid = ldif.setdefault('rid', [ self._find_id('rid', plage) ])
|
||
|
||
# La machine peut-elle avoir une ipv4 ?
|
||
if 'v6' not in realm:
|
||
ldif['ipHostNumber'] = [ crans_utils.ip4_of_rid(int(rid[0])) ]
|
||
ldif['ip6HostNumber'] = [ crans_utils.ip6_of_mac(ldif['macAddress'][0], int(rid[0])) ]
|
||
|
||
# Mid
|
||
ldif['mid'] = [ self._find_id('mid') ]
|
||
|
||
# Tout doit disparaître !!
|
||
machine = self._create_entity('mid=%s,%s' % (ldif['mid'][0], parent), ldif)
|
||
login = login or current_user
|
||
machine.history_add(login, "inscription")
|
||
if machine.may_be(created, self.droits + self._check_parent(machine.dn)):
|
||
return machine
|
||
else:
|
||
raise EnvironmentError("Vous n'avez pas le droit de créer cette machine.")
|
||
|
||
def newAdherent(self, ldif):
|
||
"""Crée un nouvel adhérent"""
|
||
aid = ldif.setdefault('aid', [ self._find_id('aid') ])
|
||
ldif['objectClass'] = ['adherent']
|
||
adherent = self._create_entity('aid=%s,%s' % (aid[0], base_dn), ldif)
|
||
if adherent.may_be(created, self.droits):
|
||
return adherent
|
||
else:
|
||
raise EnvironmentError("Vous n'avez pas le droit de créer cet adhérent.")
|
||
|
||
def newClub(self, ldif):
|
||
"""Crée un nouveau club"""
|
||
cid = ldif.setdefault('cid', [ self._find_id('cid') ])
|
||
ldif['objectClass'] = ['club']
|
||
club = self._create_entity('cid=%s,%s' % (cid[0], base_dn), ldif)
|
||
if club.may_be(created, self.droits):
|
||
return club
|
||
else:
|
||
raise EnvironmentError("Vous n'avez pas le droit de créer cet adhérent.")
|
||
|
||
def newFacture(self, ldif):
|
||
"""Crée une nouvelle facture
|
||
--Non implémenté !"""
|
||
raise NotImplementedError()
|
||
|
||
def _create_entity(self, dn, ldif):
|
||
'''Crée une nouvelle entité ldap avec le dn ``dn`` et les
|
||
attributs de ``ldif``. Attention, ldif doit contenir des
|
||
données encodées.'''
|
||
# Conversion en cldif pour vérification des valeurs
|
||
cldif = attributs.AttrsDict(self, ldif, Parent=None)
|
||
# Conversion en ascii
|
||
data = cldif_to_ldif(cldif)
|
||
# Renvoi du CransLdapObject
|
||
return new_cransldapobject(self, dn, 'rw', data)
|
||
|
||
def _find_id(self, attr, plage=None):
|
||
'''Trouve un id libre. Si une plage est fournie, cherche
|
||
l'id dans celle-ci, sinon, prend le plus élevé possible.'''
|
||
res = self.search_s(base_dn, ldap.SCOPE_SUBTREE, '%s=*' % attr, attrlist = [attr])
|
||
nonfree = [ int(r[1].get(attr)[0]) for r in res if r[1].get(attr) ]
|
||
nonfree.sort()
|
||
|
||
if plage != None:
|
||
for i in plage:
|
||
if i in nonfree:
|
||
continue
|
||
else:
|
||
# On crée l'attribut associé, pour parser sa valeur.
|
||
my_id = attributs.attrify(unicode(i), attr, self, None)
|
||
if my_id.value != i:
|
||
continue
|
||
else:
|
||
break
|
||
else:
|
||
raise EnvironmentError('Aucun %s libre dans la plage [%d, %d]' %
|
||
(attr, plage[0], i))
|
||
else:
|
||
i = nonfree[-1]+1
|
||
return i
|
||
|
||
def _check_parent(self, objdn):
|
||
"""
|
||
Teste le rapport entre le dn fourni et self
|
||
Retourne une liste qui s'ajoutera à la liste des droits
|
||
"""
|
||
|
||
if objdn.endswith(self.dn) and objdn != self.dn:
|
||
return [attributs.parent]
|
||
|
||
else:
|
||
return []
|
||
|
||
|
||
def _check_self(self, objdn):
|
||
"""
|
||
Teste si le dn fourni est celui de self.
|
||
Retourne une liste qui s'ajoutera à la liste des droits
|
||
"""
|
||
|
||
if objdn == self.dn:
|
||
return [attributs.soi]
|
||
else:
|
||
return []
|
||
|
||
|
||
class lc_ldap_test(lc_ldap):
|
||
"""Connexion LDAP à la base de tests"""
|
||
def __init__(self, *args, **kwargs):
|
||
# On impose le serveur
|
||
kwargs["uri"] = 'ldap://vo.adm.crans.org'
|
||
# On pense à laisser la possibilité de se connecter par username ou dn…
|
||
if not kwargs.has_key("user"):
|
||
# … mais si rien n'est spécifié, on fournit le dn par défaut
|
||
kwargs.setdefault("dn", 'cn=admin,dc=crans,dc=org')
|
||
# Le mot de passe de la base de test
|
||
kwargs.setdefault("cred", '75bdb64f32')
|
||
super(lc_ldap_test, self).__init__(*args, **kwargs)
|
||
|
||
def _first_bind(self):
|
||
"""Sur la base de test, on peut lookup en anonyme"""
|
||
self.simple_bind_s()
|
||
|
||
class lc_ldap_admin(lc_ldap):
|
||
"""Connexion LDAP à la vraie base, en admin.
|
||
Possible seulement si on peut lire secrets.py"""
|
||
def __init__(self):
|
||
secrets = import_secrets()
|
||
super(lc_ldap_admin, self).__init__(uri='ldap://ldap.adm.crans.org/', dn=secrets.ldap_auth_dn, cred=secrets.ldap_password)
|
||
|
||
class lc_ldap_readonly(lc_ldap):
|
||
"""Connexion LDAP à la vraie base, en readonly.
|
||
Possible seulement si on peut lire secrets.py"""
|
||
def __init__(self):
|
||
secrets = import_secrets()
|
||
super(lc_ldap_readonly, self).__init__(uri='ldap://ldap.adm.crans.org/', dn=secrets.ldap_readonly_auth_dn, cred=secrets.ldap_readonly_password)
|
||
|
||
class lc_ldap_local(lc_ldap):
|
||
"""Connexion LDAP en lecture seule sur la base locale.
|
||
L'idée est que les machines avec un réplica bossent
|
||
avec elles-mêmes pour la lecture, pas avec vert.
|
||
|
||
Attention, les accès internes en lecture seule
|
||
ou avec une socket ldapi semblent moins prioritaires
|
||
qu'avec cn=admin. Ne pas utiliser cette fonction
|
||
si vous souhaitez faire beaucoup de recherches
|
||
indépendantes (c'est le temps d'accès à la socket
|
||
qui est problématique)"""
|
||
def __init__(self):
|
||
if os.path.exists('/var/run/slapd/ldapi'):
|
||
ro_uri = 'ldapi://%2fvar%2frun%2fslapd%2fldapi/'
|
||
auth_dn = auth_pw = ""
|
||
elif os.path.exists('/var/run/ldapi'):
|
||
ro_uri = 'ldapi://%2fvar%2frun%2fldapi/'
|
||
auth_dn = auth_pw = ""
|
||
else:
|
||
secrets = import_secrets()
|
||
ro_uri = 'ldap://127.0.0.1'
|
||
auth_dn = secrets.ldap_readonly_auth_dn
|
||
auth_pw = secrets.ldap_readonly_password
|
||
|
||
super(lc_ldap_local, self).__init__(uri=ro_uri, dn=auth_dn, cred=auth_pw)
|
||
|
||
|
||
def new_cransldapobject(conn, dn, mode='ro', ldif = None):
|
||
"""Crée un objet :py:class:`CransLdap` en utilisant la classe correspondant à
|
||
l'``objectClass`` du ``ldif``
|
||
--pour usage interne à la libraire uniquement !"""
|
||
|
||
classe = None
|
||
|
||
if dn == base_dn:
|
||
classe = AssociationCrans
|
||
elif ldif:
|
||
classe = globals()[ldif['objectClass'][0]]
|
||
else:
|
||
res = conn.search_s(dn, 0)
|
||
if not res:
|
||
raise ValueError ('objet inexistant: %s' % dn)
|
||
_, attrs = res[0]
|
||
classe = globals()[attrs['objectClass'][0]]
|
||
|
||
return classe(conn, dn, mode, ldif)
|
||
|
||
class CransLdapObject(object):
|
||
"""Classe de base des objets :py:class:`CransLdap`.
|
||
Cette classe ne devrait pas être utilisée directement."""
|
||
|
||
""" Qui peut faire quoi ? """
|
||
can_be_by = { created: [attributs.nounou],
|
||
modified: [attributs.nounou],
|
||
deleted: [attributs.nounou],
|
||
}
|
||
|
||
attribs = []
|
||
|
||
def __init__(self, conn, dn, mode='ro', ldif = None):
|
||
'''
|
||
Créée une instance d'un objet Crans (machine, adhérent,
|
||
etc...) à ce ``dn``, si ``ldif`` est précisé, n'effectue pas de
|
||
recherche dans la base ldap.
|
||
'''
|
||
|
||
if not isinstance(conn, lc_ldap):
|
||
raise TypeError("conn doit être une instance de lc_ldap")
|
||
self.conn = conn
|
||
|
||
self.attrs = attributs.AttrsDict(conn, Parent=self) # Contient un dico uldif qui doit représenter ce qui
|
||
# est dans la base. On attrify paresseusement au moment où on utilise un attribut
|
||
|
||
self._modifs = None # C'est là qu'on met les modifications
|
||
self.dn = dn
|
||
|
||
orig = {}
|
||
if ldif:
|
||
if dn != base_dn: # new_cransldapobject ne donne pas de ldif formaté et utilise un ldif non formaté, donc on formate
|
||
self.attrs = attributs.AttrsDict(self.conn, ldif, Parent=self)
|
||
else:
|
||
self.attrs = ldif
|
||
self._modifs = attributs.AttrsDict(self.conn, ldif, Parent=self)
|
||
orig = ldif
|
||
|
||
elif dn != base_dn:
|
||
res = self.conn.search_s(dn, 0)
|
||
if not res:
|
||
raise ValueError ('objet inexistant: %s' % dn)
|
||
self.dn, res_attrs = res[0]
|
||
|
||
# L'objet sortant de la base ldap, on ne fait pas de vérifications sur
|
||
# l'état des données.
|
||
self.attrs = attributs.AttrsDict(self.conn, res_attrs, Parent=self)
|
||
|
||
# Pour test en cas de mode w ou rw
|
||
orig = res[0][1]
|
||
|
||
self._modifs = attributs.AttrsDict(self.conn, res[0][1], Parent=self)
|
||
|
||
if mode in ['w', 'rw']:
|
||
if not self.may_be(modified, self.conn.droits + self.conn._check_parent(dn) + self.conn._check_self(dn)):
|
||
raise EnvironmentError("Vous n'avez pas le droit de modifier cet objet.")
|
||
|
||
self.mode = mode
|
||
|
||
# Je m'interroge sur la pertinence de cette partie, je pense qu'elle n'est
|
||
# pas utile. -- PEB 27/01/2013
|
||
if 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 = orig
|
||
nldif = cldif_to_ldif(self.attrs)
|
||
|
||
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][v.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 _get_fields(self):
|
||
# """Renvoie la liste des champs LDAP de l'objet"""
|
||
# return self.attribs
|
||
# attribs = property(_get_fields)
|
||
|
||
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, str) or isinstance(login, unicode)
|
||
assert isinstance(chain, str)
|
||
|
||
new_line = "%s, %s : %s" % (time.strftime("%d/%m/%Y %H:%M"), login, chain)
|
||
# Attention, le __setitem__ est surchargé, mais pas .append sur l'historique
|
||
self["historique"] = self.get("historique", []) + [new_line]
|
||
|
||
def create(self):
|
||
"""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."""
|
||
objet = self.__class__.__name__
|
||
|
||
for attribut in self.attribs:
|
||
if not attribut.optional:
|
||
nom_attr = attribut.__name__
|
||
if len(self._modifs.get(nom_attr, [])) <= 0:
|
||
raise attributs.OptionalError("L'objet %s que vous créez doit posséder au moins un attribut %s" % (objet, nom_attr))
|
||
|
||
# Création de la requête LDAP
|
||
modlist = addModlist(cldif_to_ldif(self._modifs))
|
||
# Requête LDAP de création de l'objet
|
||
self.conn.add_s(self.dn, modlist)
|
||
|
||
def bury(self, comm, login):
|
||
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.attrs.keys():
|
||
for value in self.attrs[key]:
|
||
ldif+=u"%s: %s\n" % (key, value)
|
||
|
||
import datetime
|
||
file="%s %s" % (datetime.datetime.now(), self.dn)
|
||
f = open('/home/cimetiere_lc/%s/%s' % (self['objectClass'][0],file), 'w')
|
||
f.write(ldif.encode("UTF-8"))
|
||
f.close()
|
||
|
||
def delete(self, comm="", login=current_user):
|
||
if self.mode not in ['w', 'rw']:
|
||
raise EnvironmentError("Objet en lecture seule, réessayer en lecture/écriture")
|
||
if not self.may_be(deleted, self.conn.droits):
|
||
raise EnvironmentError("Vous n'avez pas le droit de supprimer %s." % self.dn)
|
||
self.bury(comm, login)
|
||
self.conn.delete_s(self.dn)
|
||
|
||
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")
|
||
|
||
objet = self.__class__.__name__
|
||
|
||
for attribut in self.attribs:
|
||
if not attribut.optional:
|
||
nom_attr = attribut.__name__
|
||
if len(self._modifs.get(nom_attr, [])) <= 0:
|
||
raise attributs.OptionalError("L'objet %s que vous créez doit posséder au moins un attribut %s" % (objet, nom_attr))
|
||
|
||
# On récupère la liste des modifications
|
||
modlist = self.get_modlist()
|
||
try:
|
||
self.conn.modify_s(self.dn, modlist)
|
||
except:
|
||
raise EnvironmentError("Impossible de modifier l'objet, peut-être n'existe-t-il pas ?")
|
||
|
||
# Vérification des modifications
|
||
self.attrs = attributs.AttrsDict(self.conn, self.conn.search_s(self.dn, 0)[0][1], 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([unicode(i) for i in self.attrs.get(attr,[])])
|
||
new_vals = set([unicode(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})
|
||
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):
|
||
"""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 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
|
||
ldif = cldif_to_ldif(self._modifs)
|
||
orig_ldif = cldif_to_ldif(self.attrs)
|
||
|
||
return modifyModlist(orig_ldif, ldif)
|
||
|
||
def get(self, attr, default):
|
||
"""Renvoie l'attribut demandé ou default si introuvable"""
|
||
try:
|
||
return self[attr]
|
||
except KeyError:
|
||
return default
|
||
|
||
def __getitem__(self, attr):
|
||
if self.mode in [ 'w', 'rw' ]:
|
||
return [ v for v in self._modifs[attr] ]
|
||
elif self.attrs.has_key(attr):
|
||
return [ v for v in self.attrs[attr] ]
|
||
elif self.has_key(attr):
|
||
return []
|
||
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 __setitem__(self, attr, values):
|
||
"""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 ]
|
||
if attr in self.attrs.keys():
|
||
for attribut in attrs_before_verif:
|
||
attribut.check_uniqueness([str(content) for content in self.attrs[attr]])
|
||
|
||
# On groupe les attributs précédents, et les nouveaux
|
||
mixed_attrs = attrs_before_verif + self.attrs[attr]
|
||
else:
|
||
mixed_attrs = attrs_before_verif
|
||
# 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)):
|
||
raise EnvironmentError("Vous ne pouvez pas toucher aux attributs de type %r." % (attr))
|
||
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(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] == deleted:
|
||
out.append(u"%s : [%s] Suppression" % (date, author))
|
||
elif attrs['reqType'][0] == 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
|
||
|
||
def blacklist_actif(self):
|
||
"""Renvoie la liste des blacklistes actives sur l'entité
|
||
Améliorations possibles:
|
||
- Proposer de filtrer les blacklistes avec un arg supplémentaire ?
|
||
- 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 self.__class__.__name__ == "adherent" and self.paiement_ok():
|
||
if not config.periode_transitoire and config.bl_carte_et_actif and not self.carte_ok() and not self.sursis_carte():
|
||
bl = attributs.blacklist(u'%s$%s$%s$%s' % ('-', '-', 'carte_etudiant', ''), {}, self.conn)
|
||
blacklist_liste.append(bl)
|
||
if self['chbre'][0].value == '????':
|
||
bl = attributs.blacklist(u'%s$%s$%s$%s' % ('-', '-', 'chambre_invalide', ''), {}, self.conn)
|
||
blacklist_liste.append(bl)
|
||
attrs = (self.attrs if self.mode not in ["w", "rw"] else self._modifs)
|
||
blacklist_liste.extend(filter((lambda bl: bl.is_actif()), attrs.get("blacklist",[])))
|
||
return blacklist_liste
|
||
|
||
def blacklist(self, sanction, commentaire, debut="now", fin = '-'):
|
||
u"""
|
||
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 proprio(CransLdapObject):
|
||
u""" Un propriétaire de machine (adhérent, club…) """
|
||
can_be_by = { created: [attributs.nounou, attributs.bureau, attributs.cableur],
|
||
modified: [attributs.nounou, attributs.bureau, attributs.soi, attributs.cableur],
|
||
deleted: [attributs.nounou, attributs.bureau],
|
||
}
|
||
|
||
attribs = [attributs.nom, attributs.chbre, attributs.paiement, attributs.info, attributs.blacklist, attributs.controle, attributs.historique]
|
||
|
||
def __init__(self, conn, dn, mode='ro', ldif = None, machines=[]):
|
||
super(proprio, self).__init__(conn, dn, mode, ldif)
|
||
self._machines = machines
|
||
|
||
def sursis_carte(self):
|
||
for h in self['historique'][::-1]:
|
||
x=re.match("(.*),.* : .*(paiement\+%s|inscription).*" % config.ann_scol,h.value)
|
||
if x != None:
|
||
return ((time.time()-time.mktime(time.strptime(x.group(1),'%d/%m/%Y %H:%M')))<=config.sursis_carte)
|
||
return False
|
||
|
||
def paiement_ok(self):
|
||
u"""Renvoie si le propriétaire a payé pour l'année en cours"""
|
||
if self.dn == base_dn:
|
||
return True
|
||
bool_paiement = False
|
||
try:
|
||
for paiement in self['paiement']:
|
||
if paiement.value == config.ann_scol:
|
||
bool_paiement = True
|
||
break
|
||
# Pour la période transitoire année précédente ok
|
||
if config.periode_transitoire and paiement.value == config.ann_scol -1:
|
||
bool_paiement = True
|
||
break
|
||
except KeyError:
|
||
pass
|
||
# Doit-on bloquer en cas de manque de la carte d'etudiant ?
|
||
# (si période transitoire on ne bloque dans aucun cas)
|
||
if config.bl_carte_et_definitif and not 'club' in map(lambda x:x.value,self["objectClass"]):
|
||
bool_carte = False
|
||
try:
|
||
for carte in self['carteEtudiant']:
|
||
if carte.value == config.ann_scol:
|
||
bool_carte = True
|
||
except KeyError:
|
||
pass
|
||
# Si inscrit depuis moins de config.sursis_carte, on laisse un sursis
|
||
if not bool_carte and self.sursis_carte():
|
||
bool_carte = True
|
||
return bool_carte and bool_paiement
|
||
return bool_paiement
|
||
|
||
def carte_ok(self):
|
||
u"""Renvoie si le propriétaire a donné sa carte pour l'année en cours"""
|
||
if not self.dn == base_dn and config.bl_carte_et_actif and not 'club' in map(lambda x:x.value,self["objectClass"]):
|
||
bool_carte = False
|
||
try:
|
||
for carte in self['carteEtudiant']:
|
||
if carte.value == config.ann_scol:
|
||
bool_carte = True
|
||
except KeyError:
|
||
pass
|
||
return bool_carte
|
||
return True
|
||
|
||
def update_solde(self, diff, comment=u"", login=None):
|
||
"""Modifie le solde du proprio. diff peut être négatif ou positif."""
|
||
login = login or current_user
|
||
assert isinstance(diff, int) or isinstance(diff, float)
|
||
assert isinstance(comment, unicode)
|
||
|
||
solde = float(self["solde"][0].value)
|
||
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 machines(self):
|
||
"""Renvoie la liste des machines"""
|
||
if not self._machines:
|
||
self._machines = self.conn.search('mid=*', dn = self.dn, scope = 1, mode=self.mode)
|
||
for m in self._machines:
|
||
m._proprio = self
|
||
return self._machines
|
||
|
||
def delete(self, comm="", login=current_user):
|
||
if self.mode not in ['w', 'rw']:
|
||
raise EnvironmentError("Objet en lecture seule, réessayer en lecture/écriture")
|
||
if not self.may_be(deleted, self.conn.droits):
|
||
raise EnvironmentError("Vous n'avez pas le droit de supprimer %s." % self.dn)
|
||
for machine in self.machines():
|
||
machine.delete(comm, login)
|
||
self.bury(comm, login)
|
||
self.conn.delete_s(self.dn)
|
||
|
||
class machine(CransLdapObject):
|
||
u""" Une machine """
|
||
can_be_by = { created: [attributs.nounou, attributs.bureau, attributs.cableur, attributs.parent],
|
||
modified: [attributs.nounou, attributs.bureau, attributs.cableur, attributs.parent],
|
||
deleted: [attributs.nounou, attributs.bureau, attributs.cableur, attributs.parent],
|
||
}
|
||
|
||
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 __init__(self, conn, dn, mode='ro', ldif = None):
|
||
super(machine, self).__init__(conn, dn, mode, ldif)
|
||
self._proprio = None
|
||
|
||
def proprio(self):
|
||
u"""Renvoie le propriétaire de la machine"""
|
||
parent_dn = self.dn.split(',', 1)[1]
|
||
if not self._proprio:
|
||
self._proprio = new_cransldapobject(self.conn, parent_dn, self.mode)
|
||
return self._proprio
|
||
|
||
def blacklist_actif(self):
|
||
u"""Renvoie la liste des blacklistes actives sur la machine et le proprio
|
||
Améliorations possibles:
|
||
- Proposer de filtrer les blacklistes avec un arg supplémentaire ?
|
||
- Vérifier les blacklistes des machines pour les adhérents ?"""
|
||
black=self.proprio().blacklist_actif()
|
||
attrs = (self.attrs if self.mode not in ["w", "rw"] else self._modifs)
|
||
black.extend(filter((lambda bl: bl.is_actif()), attrs.get("blacklist",[])))
|
||
return black
|
||
|
||
class AssociationCrans(proprio):
|
||
u""" Association crans (propriétaire particulier)."""
|
||
def delete(self, comm, login):
|
||
raise EnvironmentError("Détruire le Crans ? Hum…")
|
||
pass
|
||
|
||
class adherent(proprio):
|
||
u"""Adhérent crans."""
|
||
attribs = proprio.attribs + [attributs.aid, attributs.prenom, attributs.tel,
|
||
attributs.mail, attributs.mailInvalide, attributs.charteMA,
|
||
attributs.derniereConnexion, attributs.gpgFingerprint,
|
||
attributs.carteEtudiant, attributs.droits, attributs.etudes,
|
||
attributs.postalAddress, attributs.mailExt, attributs.compteWiki]
|
||
|
||
def __init__(self, conn, dn, mode='ro', ldif = None):
|
||
super(adherent, self).__init__(conn, dn, mode, ldif)
|
||
if u'cransAccount' in [ str(o) for o in self['objectClass']]:
|
||
self.attribs = self.attribs + [attributs.uid, attributs.canonicalAlias, attributs.solde,
|
||
attributs.contourneGreylist, attributs.derniereConnexion,
|
||
attributs.homepageAlias, attributs.mailAlias ]
|
||
|
||
|
||
|
||
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 uid
|
||
est précisé, le crée."""
|
||
|
||
if u'posixAccount' in self.attrs['objectClass']:
|
||
return self.attrs['uid'][0]
|
||
|
||
elif login:
|
||
fn = crans_utils.strip_accents(unicode(self.attrs['prenom'][0]).capitalize())
|
||
ln = crans_utils.strip_accents(unicode(self.attrs['nom'][0]).capitalize())
|
||
login = crans_utils.strip_accents(login).lower()
|
||
if jaro(ln.lower(), login) < 0.75 and jaro(fn.lower() + ' ' + ln.lower(), login) < 0.75:
|
||
raise ValueError("Le login est trop différent du nom",
|
||
login, self.attrs['nom'][0])
|
||
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' % login)
|
||
|
||
self._modifs['homeDirectory'] = [home]
|
||
self._modifs['mail'] = [login]
|
||
self._modifs['uid' ] = [login]
|
||
calias = crans_utils.strip_spaces(fn) + u'.' + crans_utils.strip_spaces(ln)
|
||
if crans_utils.mailexist(calias):
|
||
calias = login
|
||
self._modifs['canonicalAlias'] = [calias]
|
||
self._modifs['objectClass'] = [u'adherent', u'cransAccount', u'posixAccount', u'shadowAccount']
|
||
self._modifs['cn'] = [ fn + u' ' + ln ]
|
||
self._modifs['loginShell'] = [unicode(shell)]
|
||
self._modifs['userPassword'] = [unicode(hash_pass)]
|
||
|
||
if uidNumber:
|
||
if self.conn.search('(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('(uidNumber=%s)' % uidNumber):
|
||
break
|
||
if not len(pool_uid):
|
||
raise ValueError("Plus d'uid disponibles !")
|
||
|
||
## try:
|
||
## self.lock('uidNumber', str(uidNumber))
|
||
## except:
|
||
## # Quelqu'un nous a piqué l'uid que l'on venait de choisir !
|
||
## return self.compte(login, uidNumber, hash_pass, shell)
|
||
|
||
self._modifs['uidNumber'] = [unicode(uidNumber)]
|
||
self._modifs['gidNumber'] = [unicode(config.gid)]
|
||
self._modifs['gecos'] = [self._modifs['cn'][0] + u',,,']
|
||
|
||
self.save()
|
||
|
||
else:
|
||
raise EnvironmentError("L'adhérent n'a pas de compte crans")
|
||
|
||
|
||
class club(proprio):
|
||
u"""Club crans"""
|
||
attribs = proprio.attribs + [attributs.cid, attributs.responsable, attributs.imprimeurClub]
|
||
|
||
class machineFixe(machine):
|
||
u"""Machine fixe"""
|
||
pass
|
||
|
||
class machineWifi(machine):
|
||
u"""Machine wifi"""
|
||
attribs = machine.attribs + [attributs.ipsec]
|
||
|
||
def set_ipv4(self, login=None):
|
||
u"""Définie une ipv4 à la machine si elle n'est possède pas déjà une."""
|
||
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]))) ]
|
||
ip = self['ipHostNumber'] = [ unicode(crans_utils.ip4_of_rid(int(rid[0]))) ]
|
||
login = login or current_user
|
||
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]))
|
||
|
||
class machineCrans(machine):
|
||
can_be_by = { created: [attributs.nounou],
|
||
modified: [attributs.nounou],
|
||
deleted: [attributs.nounou],
|
||
}
|
||
attribs = machine.attribs + [attributs.prise, attributs.nombrePrises]
|
||
|
||
class borneWifi(machine):
|
||
can_be_by = { created: [attributs.nounou],
|
||
modified: [attributs.nounou],
|
||
deleted: [attributs.nounou],
|
||
}
|
||
attribs = machine.attribs + [attributs.canal, attributs.puissance, attributs.hotspot,
|
||
attributs.prise, attributs.positionBorne, attributs.nvram]
|
||
|
||
class facture(CransLdapObject):
|
||
can_be_by = { created: [attributs.nounou, attributs.bureau, attributs.cableur],
|
||
modified: [attributs.nounou, attributs.bureau, attributs.cableur],
|
||
deleted: [attributs.nounou, attributs.bureau, attributs.cableur],
|
||
}
|
||
attribs = [attributs.fid, attributs.modePaiement, attributs.recuPaiement]
|
||
|
||
class service(CransLdapObject):
|
||
pass
|
||
|
||
class lock(CransLdapObject):
|
||
pass
|