
il est inutile losque l'on construit les propriétaires des machines de réintérroger l'annuaire ldap pour les obtenir alors que l'on vient de dumper tout l'annuaire. Pour cela, on rajoute un paramètre optionel machines à la classe proprio, et on construit directement les propriétaires en appelant la classe adhérant au lieu de passer par la methode new_cransldapobject.
555 lines
23 KiB
Python
555 lines
23 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# LC_LDAP.PY-- LightWeight CransLdap
|
|
#
|
|
# Copyright (C) 2010 Cr@ns <roots@crans.org>
|
|
# Author: Antoine Durand-Gasselin <adg@crans.org>
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
# * Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
# * Neither the name of the Cr@ns nor the names of its contributors may
|
|
# be used to endorse or promote products derived from this software
|
|
# without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT
|
|
# HOLDER> BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
from __future__ import with_statement
|
|
import os, sys, ldap, re, netaddr, datetime, copy, time, random
|
|
from ldap.modlist import addModlist, modifyModlist
|
|
try:
|
|
from Levenshtein import jaro
|
|
except ImportError:
|
|
def jaro(a, b): return 0
|
|
sys.path.append('/usr/scripts/gestion')
|
|
|
|
import config, crans_utils
|
|
from attributs import attrify, blacklist
|
|
from ldap_locks import CransLock
|
|
|
|
uri = 'ldapi:///' #'ldap://ldap.adm.crans.org/'
|
|
base_dn = 'ou=data,dc=crans,dc=org'
|
|
log_dn = "cn=log"
|
|
|
|
# Champs à ignorer dans l'historique
|
|
HIST_IGNORE_FIELDS = ["modifiersName", "entryCSN", "modifyTimestamp"]
|
|
|
|
def ldif_to_uldif(ldif):
|
|
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, check_ctxt = True):
|
|
"""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] = [ attrify(val, attr, ldif, conn, check_ctxt) for val in vals]
|
|
return cldif
|
|
|
|
def cldif_to_ldif(cldif):
|
|
ldif = {}
|
|
for attr, vals in cldif.items():
|
|
ldif[attr] = [ str(val) for val in vals ]
|
|
return ldif
|
|
|
|
|
|
def lc_ldap_test():
|
|
"""Binding LDAP à la base de tests"""
|
|
return lc_ldap(dn='cn=admin,dc=crans,dc=org', cred='75bdb64f32')
|
|
|
|
class lc_ldap(ldap.ldapobject.LDAPObject):
|
|
def __init__(self, dn=None, user=None, cred=None, uri=uri):
|
|
"""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
|
|
"""
|
|
|
|
ldap.ldapobject.LDAPObject.__init__(self, uri)
|
|
|
|
if user and not re.match('[a-z_][a-z0-9_-]*', user):
|
|
raise ValueError('Invalid user name: %s' % user)
|
|
|
|
# Si un username, on récupère le dn associé
|
|
if user and not dn:
|
|
self.simple_bind_s(base_dn)
|
|
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]
|
|
|
|
# Si on a un dn, on se connecte avec à la base ldap sinon on s'y
|
|
# connecte en anonyme
|
|
if dn:
|
|
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', [])
|
|
else:
|
|
self.conn = self.simple_bind_s()
|
|
self.dn = None
|
|
self.droits = []
|
|
|
|
def search(self, filterstr='(objectClass=*)', mode='ro', dn= base_dn, scope= 2, sizelimit=400):
|
|
"""La fonction de recherche dans la base ldap qui renvoie un liste de
|
|
CransLdapObjects. On utilise la feature de sizelimit de python ldap"""
|
|
res = self.search_ext_s(dn, scope, filterstr, sizelimit=sizelimit)
|
|
return [ new_cransldapobject(self, r[0], mode=mode) for r in res ]
|
|
|
|
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."""
|
|
res = {}
|
|
machines = []
|
|
# On récupère tous les objets ldap et on les met dans un dico
|
|
# (proxying de la base ldap)
|
|
for dn, attrs in self.search_s(base_dn, scope=2): #on fait tout dans une seule boucle
|
|
#~ res[dn] = attrs
|
|
# On crée la liste des machines
|
|
#~ for dn, attrs in res.items():
|
|
if dn.startswith('mid='):
|
|
m = new_cransldapobject(self, dn, ldif = attrs)
|
|
parent_dn = dn.split(',', 1)[1]
|
|
if not parent.has_key(parent_dn):
|
|
parent[parent_dn]=adherent(self, dn,machines= [ new_cransldapobject(self, dn,ldif=attrs)]) # on utilise pas new_cransldapobject pour optimiser les appel ldap (on passe la liste des machines)
|
|
else:
|
|
parent[parent_dn]._machines.append(new_cransldapobject(self, dn,ldif=attrs))
|
|
#~ m._proprio = new_cransldapobject(self, parent_dn, res[parent_dn],opt=machine_proprio[parent_dn])
|
|
m._proprio = parent[parent_dn]
|
|
machines.append(m)
|
|
return machines
|
|
|
|
def newMachine(self, parent, realm, uldif):
|
|
"""Crée une nouvelle machine: realm peut être:
|
|
fil, fil-v6, wifi, wifi-v6, adm, gratuit, personnel-ens, special"""
|
|
#adm, serveurs, bornes, wifi, adherents, gratuit ou personnel-ens"""
|
|
owner = self.search('objectClass=*', dn=parent, scope=0)[0]
|
|
|
|
if realm in ["adm", "serveurs"]:
|
|
uldif['objectClass'] = [u'machineCrans']
|
|
assert isinstance(owner, AssociationCrans)
|
|
# XXX - Vérifier les droits
|
|
|
|
elif realm == "bornes":
|
|
uldif['objectClass'] = [u'borneWifi']
|
|
assert isinstance(owner, AssociationCrans)
|
|
# XXX - Vérifier les droits
|
|
|
|
elif realm in ["wifi", "wifi-v6"]:
|
|
uldif['objectClass'] = [u'machineWifi']
|
|
assert isinstance(owner, adherent) or isinstance(owner, club)
|
|
# XXX - Vérifier les droits (owner.type_connexion)
|
|
|
|
elif realm in ["fil", "fil-v6", "gratuit", "personnel-ens"]:
|
|
uldif['objectClass'] = [u'machineFixe']
|
|
assert isinstance(owner, adherent) or isinstance(owner, club)
|
|
# XXX - Vérifier les droits
|
|
|
|
else: raise ValueError("Realm inconnu: %s" % realm)
|
|
|
|
# On récupère la plage des mids
|
|
if realm == 'fil':
|
|
plage = xrange(256, 2047)
|
|
else:
|
|
plage = xrange( *(config.mid[realm]))
|
|
# On récupère le premier id libre dans la plages s'il n'est pas
|
|
# déjà précisé dans le ldiff
|
|
mid = uldif.setdefault('mid', [ unicode(self._find_id('mid', plage)) ])
|
|
uldif['ipHostNumber'] = [ unicode(crans_utils.ip_of_mid(int (mid[0]))) ]
|
|
return self._create_entity('mid=%s,%s' % (mid[0], parent), uldif)
|
|
|
|
def newAdherent(self, uldif):
|
|
"""Crée un nouvel adhérent"""
|
|
aid = uldif.setdefault('aid', [ unicode(self._find_id('aid')) ])
|
|
uldif['objectClass'] = [u'adherent']
|
|
return self._create_entity('aid=%s,%s' % (aid[0], base_dn), uldif)
|
|
|
|
def newClub(self, uldif):
|
|
"""Crée un nouveau club"""
|
|
cid = uldif.setdefault('cid', [ unicode(self._find_id('cid')) ])
|
|
uldif['objectClass'] = [u'club']
|
|
return self._create_entity('cid=%s,%s' % (cid[0], base_dn), uldif)
|
|
|
|
def newFacture(self, uldif):
|
|
"""Crée une nouvelle facture"""
|
|
raise NotImplementedError()
|
|
|
|
def _create_entity(self, dn, uldif):
|
|
'''Crée une nouvelle entité ldap en dn, avec attributs ldif:
|
|
uniquement en unicode'''
|
|
# Conversion en cldiff pour vérification des valeurs
|
|
cldif = ldif_to_cldif(uldif, self)
|
|
# Conversion en ascii
|
|
ldif = cldif_to_ldif(cldif)
|
|
# Création de la requête LDAP
|
|
modlist = addModlist(ldif)
|
|
# Requête LDAP de création de l'objet
|
|
self.add_s(dn, modlist)
|
|
# Renvoi du CransLdapObject
|
|
return new_cransldapobject(self, dn, mode='w')
|
|
|
|
|
|
def _find_id(self, attr, plage = xrange(1, 32000)):
|
|
'''Trouve un <attr>id libre dans plage'''
|
|
res = self.search_s(base_dn, 2, '%s=*' % attr, attrlist = [attr])
|
|
nonfree = [ int(r[1].get(attr)[0]) for r in res if r[1].get(attr) ]
|
|
nonfree.sort()
|
|
|
|
for i in plage:
|
|
if nonfree and nonfree[0] <= i:
|
|
while nonfree and nonfree[0] <= i:
|
|
nonfree = nonfree[1:]
|
|
else:
|
|
break
|
|
else:
|
|
raise EnvironmentError('Aucun %s libre dans la plage [%d, %d]' %
|
|
(attr, plage[0], i))
|
|
return i
|
|
|
|
|
|
def new_cransldapobject(conn, dn, mode='ro', ldif = None):
|
|
"""Crée un objet CransLdap en utilisant la classe correspondant à
|
|
l'objectClass du ldif"""
|
|
|
|
classe = None
|
|
|
|
if ldif:
|
|
classe = globals()[ldif['objectClass'][0]]
|
|
elif dn == base_dn:
|
|
classe = AssociationCrans
|
|
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 CransLdap"""
|
|
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.
|
|
'''
|
|
|
|
self.mode = mode
|
|
|
|
self.attrs = None # Contient un dico uldif qui doit représenter ce qui
|
|
# est dans la base
|
|
|
|
self._modifs = None # C'est là qu'on met les modifications
|
|
|
|
if not isinstance(conn, lc_ldap):
|
|
raise TypeError("conn doit être une instance de lc_ldap")
|
|
self.conn = conn
|
|
self.dn = dn
|
|
|
|
if ldif:
|
|
# Vous précisez un ldif, l'objet est 'ro'
|
|
self.mode = 'ro'
|
|
self.attrs = ldif
|
|
elif dn != base_dn:
|
|
res = conn.search_s(dn, 0)
|
|
if not res:
|
|
raise ValueError ('objet inexistant: %s' % dn)
|
|
self.dn, self.attrs = res[0]
|
|
|
|
self.attrs = ldif_to_uldif(self.attrs)
|
|
self.attrs = ldif_to_cldif(self.attrs, conn, check_ctxt = False)
|
|
if mode in ['w', 'rw']:
|
|
### Vérification que `λv. str(Attr(v))` est bien une projection
|
|
oldif = res[0][1]
|
|
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:", attr, nvals, vals)
|
|
|
|
self._modifs = ldif_to_cldif(ldif_to_uldif(res[0][1]), conn, check_ctxt = False)
|
|
|
|
def save(self):
|
|
"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")
|
|
|
|
# On récupère la liste des modifications
|
|
modlist = self.get_modlist()
|
|
self.conn.modify_s(self.dn, modlist)
|
|
|
|
# Vérification des modifications
|
|
self.attrs = ldif_to_uldif(self.conn.search_s(self.dn, 0)[0][1])
|
|
self.attrs = ldif_to_cldif(self.attrs, self.conn, check_ctxt=False)
|
|
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 get_modlist(self):
|
|
"""Renvoie le dico des modifs"""
|
|
# 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):
|
|
try:
|
|
return self[attr]
|
|
except KeyError:
|
|
return default
|
|
|
|
def __getitem__(self, attr):
|
|
if self.mode in [ 'w', 'rw' ]:
|
|
return [ v.__unicode__() for v in self._modifs[attr] ]
|
|
else:
|
|
return [ v.__unicode__() for v in self.attrs[attr] ]
|
|
|
|
def __setitem__(self, attr, values):
|
|
if self.mode not in ['w', 'rw']:
|
|
raise ValueError("Objet en lecture seule")
|
|
if not isinstance(values, list):
|
|
values = [ values ]
|
|
self._modifs[attr] = [ attrify(val, attr, self._modifs, self.conn) for val in values ]
|
|
|
|
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] == 'delete':
|
|
out.append(u"%s : [%s] Suppression" % (date, author))
|
|
elif attrs['reqType'][0] == 'modify':
|
|
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é"""
|
|
# XXX - Proposer de filtrer les blacklistes avec un arg supplémentaire ?
|
|
# XXX - Vérifier les blacklistes des machines pour les adhérents ?
|
|
attrs = (self.attrs if self.mode not in ["w", "rw"] else self._modifs)
|
|
return filter((lambda bl: bl.is_actif()), attrs.get("blacklist"))
|
|
|
|
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 = blacklist(u'%s$%s$%s$%s' % (debut, fin, sanction, commentaire), {}, self.conn, False)
|
|
|
|
self._modifs.setdefault('blacklist', []).append(bl)
|
|
|
|
|
|
class proprio(CransLdapObject):
|
|
ufields = [ 'nom', 'chbre' ]
|
|
mfields = [ 'paiement', 'info', 'blacklist', 'controle']
|
|
ofields = []
|
|
xfields = []
|
|
|
|
def __init__(self, conn, dn, mode='ro', ldif = None, machines=[]):
|
|
super(proprio, self).__init__(conn, dn, mode, ldif)
|
|
self._machines = machines
|
|
|
|
def machines(self):
|
|
if not self._machines:
|
|
self._machines = self.conn.search('mid=*', dn = self.dn, scope = 1)
|
|
for m in self._machines:
|
|
m._proprio = self
|
|
return self._machines
|
|
|
|
class machine(CransLdapObject):
|
|
ufields = ['mid', 'macAddress', 'host', 'midType']
|
|
ofields = []
|
|
mfields = ['info', 'blacklist', 'hostAlias', 'exempt',
|
|
'portTCPout', 'portTCPin', 'portUDPout', 'portUDPin']
|
|
xfields = ['ipHostNumber']
|
|
|
|
def __init__(self, conn, dn, mode='ro', ldif = None):
|
|
super(machine, self).__init__(conn, dn, mode, ldif)
|
|
self._proprio = None
|
|
|
|
def proprio(self):
|
|
parent_dn = self.dn.split(',', 1)[1]
|
|
if not self._proprio:
|
|
self._proprio = new_cransldapobject(self.conn, parent_dn, self.mode)
|
|
return self._proprio
|
|
|
|
|
|
class AssociationCrans(proprio): pass
|
|
|
|
class adherent(proprio):
|
|
ufields = proprio.ufields + ['aid', 'prenom', 'tel', 'mail', 'mailInvalide']
|
|
ofields = proprio.ofields + ['charteMA', 'adherentPayant', 'typeAdhesion',
|
|
'canonicalAlias', 'solde', 'contourneGreylist',
|
|
'rewriteMailHeaders', 'derniereConnexion',
|
|
'homepageAlias']
|
|
mfields = proprio.mfields + ['carteEtudiant', 'mailAlias', 'droits' ]
|
|
xfields = ['etudes', 'postalAddress']
|
|
|
|
def compte(self,login = None, uidNumber=0, hash_pass = '', shell=config.login_shell):
|
|
"""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):
|
|
ufields = ['cid', 'responsable']
|
|
mfields = ['imprimeurClub']
|
|
|
|
class machineFixe(machine): pass
|
|
|
|
class machineWifi(machine):
|
|
ufields = machine.ufields + ['ipsec']
|
|
|
|
class machineCrans(machine):
|
|
ufields = machine.ufields + ['prise']
|
|
ofields = machine.ofields + ['nombrePrises']
|
|
|
|
class borneWifi(machine):
|
|
ufields = machine.ufields + ['canal', 'puissane', 'hotspot',
|
|
'prise', 'positionBorne', 'nvram']
|
|
|
|
class facture(CransLdapObject):
|
|
ufields = ['fid', 'modePaiement', 'recuPaiement']
|
|
|
|
class service(CransLdapObject): pass
|
|
|
|
class lock(CransLdapObject): pass
|
|
|
|
|
|
MODIFIABLE_ATTRS = [ 'tel', 'chbre', 'mailAlias', 'loginShell' ]
|
|
|