Split de lc_ldap.py en lc_ldap.py et objets.py + création du wrapper shortcuts.py

Ça permet de faire les choses sales du genre os.getenv("SUDO_USER") ou import_secrets
dans le wrapper et pas dans le binding même.
This commit is contained in:
Vincent Le Gallic 2013-05-16 06:59:14 +02:00
parent a620f5b5e9
commit db7d41d967
8 changed files with 968 additions and 867 deletions

744
objets.py Normal file
View file

@ -0,0 +1,744 @@
#!/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
from ldap.modlist import addModlist, modifyModlist
## import de /usr/scripts/
if not "/usr/scripts/" in sys.path:
sys.path.append('/usr/scripts/')
import gestion.config as config
## import locaux
import lc_ldap
import crans_utils
import attributs
import ldap_locks
import services
import variables
#: Champs à ignorer dans l'historique
HIST_IGNORE_FIELDS = ["modifiersName", "entryCSN", "modifyTimestamp", "historique"]
def new_cransldapobject(conn, dn, mode='ro', ldif = 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 ldif:
classe = ObjectFactory.get(ldif['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])
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 = { variables.created: [attributs.nounou],
variables.modified: [attributs.nounou],
variables.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.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 ldif 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:
self.attrs = attributs.AttrsDict(self.conn, ldif, Parent=self)
self._modifs = attributs.AttrsDict(self.conn, ldif, Parent=self)
orig = ldif
elif dn != variables.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(variables.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 = self.attrs.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][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) or isinstance(chain, unicode)
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(self._modifs.to_ldif())
# Requête LDAP de création de l'objet
self.conn.add_s(self.dn, modlist)
services.services_to_restart(self.conn, {}, self._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.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.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, 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)
services.services_to_restart(self.conn, self.attrs, {})
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 ?")
# On programme le redémarrage des services
services.services_to_restart(self.conn, self.attrs, self._modifs)
# 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 = self._modifs.to_ldif()
orig_ldif = self.attrs.to_ldif()
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(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
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 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.
"""
_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
class proprio(CransLdapObject):
u""" Un propriétaire de machine (adhérent, club…) """
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],
}
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 == variables.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 == variables.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."""
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"][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=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, 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)
services.services_to_restart(self.conn, self.attrs, {})
class machine(CransLdapObject):
u""" Une machine """
can_be_by = { variables.created: [attributs.nounou, attributs.bureau, attributs.cableur, attributs.parent],
variables.modified: [attributs.nounou, attributs.bureau, attributs.cableur, attributs.parent],
variables.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 save(self):
raise EnvironmentError("AssociationCrans.save(): done.")
def ressuscite(self, comm, login):
raise EnvironmentError("Ressusciter le Crans ? Hum…")
def delete(self, comm, login):
raise EnvironmentError("Casser le Crans ? Hum…")
pass
class BaseInvites(proprio):
u"""Un artefact de la base ldap"""
def delete(self, comm, login):
raise EnvironmentError("Les pauvres invites")
pass
@crans_object
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]
ldap_name = "adherent"
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, attributs.loginShell ]
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 [str(o) for o 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 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")
@crans_object
class club(proprio):
u"""Club crans"""
attribs = proprio.attribs + [attributs.cid, attributs.responsable, attributs.imprimeurClub]
ldap_name = "club"
@crans_object
class machineFixe(machine):
u"""Machine fixe"""
ldap_name = "machineFixe"
@crans_object
class machineWifi(machine):
u"""Machine wifi"""
attribs = machine.attribs + [attributs.ipsec]
ldap_name = "machineWifi"
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]))) ]
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):
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):
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 facture(CransLdapObject):
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]
ldap_name = "facture"
@crans_object
class service(CransLdapObject):
ldap_name = "service"