scripts/gestion/gest_crans_lc.py
2014-11-23 16:27:17 +01:00

2977 lines
150 KiB
Python
Executable file

#!/bin/bash /usr/scripts/python.sh
# -*- coding: utf-8 -*-
u"""
Interface utilisateur du système de gestion des machines
et adhérents du crans
Copyright (C) Valentin Samir
Licence : GPLv3
"""
### sur le dialog (1.1) de zamok, il manque les fonctionnalité suivante présente dans la 1.2
### --default-button pour choisir le bouton sélectionner par defaut
### --not-tags pour masquer les tags quand il ne servent à rien pour l'utilisateur (mais sont utilisé comme index par le programme)
import os
import sys
import ssl
import time
import copy
import ldap
import signal
import inspect
import tempfile
import collections
if '/usr/scripts' not in sys.path:
sys.path.append('/usr/scripts')
from pythondialog import Dialog
from pythondialog import DialogError, DialogTerminatedBySignal
from OpenSSL import crypto, SSL
from gestion.cert_utils import createCertRequest
from gestion.affich_tools import get_screen_size, coul
from gestion.chgpass import check_password
import gestion.config as config
import gestion.config.factures
import lc_ldap.shortcuts
import lc_ldap.objets as objets
import lc_ldap.attributs as attributs
import lc_ldap.printing as printing
import lc_ldap.crans_utils as lc_utils
from lc_ldap.attributs import UniquenessError
import gestion.secrets_new as secrets
debugf=None
debug_enable = '--debug' in sys.argv[1:]
test_enabled = '--test' in sys.argv[1:]
if debug_enable:
import traceback
def mydebug(txt):
"""Petit fonction pour écrire des messages de débug dans /tmp/gest_crans_lc.log"""
global debugf, debug_enable
if debug_enable:
if debugf is None:
debugf = open('/tmp/gest_crans_lc.log', 'w')
if isinstance(txt, list):
for v in txt:
mydebug(' ' + str(v))
else:
debugf.write(str(txt)+'\n')
debugf.flush()
os.fsync(debugf)
# Implémentation "à la main" de la tail récursion en python
# voir http://kylem.net/programming/tailcall.html
# je trouve ça assez sioux
# En plus, ça nous permet de gérer plus facilement le
# code en CPS
class TailCaller(object) :
"""
Classe permetant, en décorant des fonctions avec, d'avoir de la tail récursion
faite "à la main" en python (voir http://kylem.net/programming/tailcall.html)
"""
other_callers = {}
def __init__(self, f) :
self.f = f
self.func_name = f.func_name
TailCaller.other_callers[id(self)]=f.func_name
def __del__(self):
del(TailCaller.other_callers[id(self)])
def __call__(self, *args, **kwargs) :
mydebug("***%s calling" % self.func_name)
stacklvl = len(inspect.stack())
try:
ret = self.f(*args, **kwargs)
except Continue as c:
ret = c.tailCall
while isinstance(ret, TailCall):
# Si la continuation a été créer par un TailCaller précédent, on propage
# Cela permet d'assurer qu'un TailCaller ne s'occupe que des TailCall
# Crée après lui.
if stacklvl>=ret.stacklvl:
mydebug("***%s terminate" % self.func_name)
raise Continue(ret)
mydebug("***%s doing %s" % (self.func_name, ret))
try:
ret = ret.handle()
except Continue as c:
ret = c.tailCall
mydebug("***%s returning %s" % (self.func_name, ret))
return ret
def tailcaller(f):
"""
Décorateur retardant la décoration par TailCaller d'une fonction
À utiliser sur les fonctions faisant de la tail récursion
et devant retourner une valeur. On l'utilise de toute
façon sur la fonction d'entrée dans l'application
Une fonction de devrait pas instancier de TailCall sans
être décoré par ce décorteur de façon générale
"""
f.tailCaller = True
return f
class Continue(Exception):
"""Exception pour envoyer des TailCall en les raisant"""
def __init__(self, tailCall):
self.tailCall = tailCall
class TailCall(object) :
"""
Appel tail récursif à une fonction et ses argument
On peut voir un TailCall comme le fait de retarder
l'appel à une fonction (avec ses listes d'arguements)
à un moment futur. La fonction sera appelée dans les
cas suivant :
* Le TailCall est retourné par une fonction qui est un TailCaller
* L'exception Continue(TailCall(...)) est levée et traverse
une fonction qui est un TailCaller
"""
def __init__(self, call, *args, **kwargs) :
self.stacklvl = len(inspect.stack())
if isinstance(call, TailCall):
call.kwargs.update(**kwargs)
kwargs = call.kwargs
args = call.args + args
call = call.call
self.call = call
self.args = args
self.kwargs = kwargs
def __str__(self):
return "TailCall<%s(%s%s%s)>" % (
self.call.func_name,
', '.join(repr(a) for a in self.args),
', ' if self.args and self.kwargs else '',
', '.join("%s=%s" % (repr(k),repr(v)) for (k,v) in self.kwargs.items())
)
def copy(self):
'''
Renvois une copie de l'objet courant
attention les elements et args ou kwargs sont juste linké
ça n'est pas gennant dans la mesure où ils ne sont normalement pas
éditer mais remplacé par d'autres éléments
'''
result = TailCall(self.call, *list(self.args), **dict(self.kwargs))
result.stacklvl = self.stacklvl
return result
def __call__(self, *args, **kwargs):
self.kwargs.update(kwargs)
self.args = self.args + args
return self
def handle(self) :
"""
Exécute la fonction call sur sa liste d'argument.
on déréférence les TailCaller le plus possible pour réduire
la taille de la stack
"""
caller = None
call = self.call
while isinstance(call, TailCaller) :
caller = call
call = self.call.f
return call(*self.args, **self.kwargs)
def unicode_of_Error(x):
"""Formatte des exception"""
return u"\n".join(unicode(i, 'utf-8') if type(i) == str
else repr(i) for i in x.args)
def handle_exit_code(d, code):
"""Gère les codes de retour dialog du menu principal"""
if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
if code == d.DIALOG_CANCEL:
#msg = "Vous avez choisi Annuler dans la dernière fenêtre de dialogue.\n\n" \
# "Voulez vous quitter le programme ?"
os.system('clear')
sys.exit(0)
else:
msg = "Vous avez appuyer sur ESC ou CTRL+C dans la dernière fenêtre de dialogue.\n\n" \
"Voulez vous quitter le programme ?"
if d.yesno(msg, width=60) == d.DIALOG_OK:
os.system('clear')
sys.exit(0)
return False
else:
return True # code est d.DIALOG_OK
def raiseKeyboardInterrupt(x, y):
"""fonction utilisée pour réactiver les Ctrl-C"""
raise KeyboardInterrupt()
class GestCrans(object):
"""Interface de gestion des machines et des adhérents du crans, version lc_ldap"""
def __getattribute__(self, attr):
ret = super(GestCrans, self).__getattribute__(attr)
# Petit hack pour que ça soit les methodes de l'objet instancié qui soient
# décorée par TailCaller et pas les méthodes statiques de la classe GestCrans
if getattr(ret, 'tailCaller', False) and not isinstance(ret, TailCaller):
ret = TailCaller(ret)
setattr(self, attr, ret)
return ret
def __init__(self):
signal.signal(signal.SIGINT, signal.SIG_IGN)
# On initialise le moteur de rendu en spécifiant qu'on va faire du dialog
printing.template(dialog=True)
self.check_ldap()
# On met un timeout à 10min d'innactivité sur dialog
self.timeout = 600
self.error_to_raise = (Continue, DialogError, ldap.SERVER_DOWN)
def check_ldap(self):
"""Se connecte à la base ldap et vérifie les droits de l'utilisateur courant"""
self.check_ldap_last = time.time()
# S'il y a --test dans les argument, on utilise la base de test
if test_enabled:
self.conn = lc_ldap.shortcuts.lc_ldap_test()
else:
# On ouvre une connexion lc_ldap
self.conn = lc_ldap.shortcuts.lc_ldap_admin()
# On vérifie que l'utilisateur système existe dans ldap (pour la gestion des droits)
luser=self.conn.search(u'(&(uid=%s)(objectClass=cransAccount))' % self.conn.current_login)
if not luser:
sys.stderr.write("L'utilisateur %s n'existe pas dans la base de donnée" % self.conn.current_login)
sys.exit(1)
self.conn.droits = [str(d) for d in luser[0]['droits']]
self.conn.dn = luser[0].dn
# Si un nom d'utilisateur est donné sur la ligne de commande
# et qu'on a les droits nounou, on l'utilise
if sys.argv[1:] and attributs.nounou in self.conn.droits:
for u in sys.argv[1:]:
luser=self.conn.search(u'(&(uid=%s)(objectClass=cransAccount))' % u)
if luser:
self.conn.current_login = u
self.conn.droits = [str(d) for d in luser[0]['droits']]
self.conn.dn = luser[0].dn
break
a = attributs
allowed_right = [a.cableur, a.tresorier, a.bureau, a.nounou, a.imprimeur]
for droit in allowed_right:
if droit in self.conn.droits:
break
else:
sys.stderr.write(
u"%s ne possède aucun des droits :\n * %s\nnécessaire à utiliser ce programme\n" % (
self.conn.current_login,
'\n * '.join(allowed_right)
)
)
sys.exit(1)
_dialog = None
@property
def dialog(self):
"""
Renvois l'objet dialog. De plus renouvelle régulièrement la connexion à la base ldap
"""
# Tous les self.timeout, on refraichie la connexion ldap
if time.time() - self.check_ldap_last > self.timeout:
self.check_ldap_last = time.time()
if self._dialog is None:
self._dialog = Dialog()
self.dialog_last_access = time.time()
return self._dialog
def nyan(self, cont, *args, **kwargs):
"""
Nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan
"""
(lines, cols) = get_screen_size()
print "\033[48;5;17m"
print " "*(lines * cols)
cols = int(min(cols/2, 65))
lines = int(lines) -1
cmd = "/usr/bin/nyancat -W %s -H %s" % (cols, lines)
os.system(cmd)
raise Continue(cont)
@tailcaller
def handle_dialog(self, cancel_cont, box, *args):
"""
Gère les fonctions appelant une fênetre dialog :
gestion de l'appuie sur Ctrl-C et rattrapage des exception / segfault de dialog.
cancel_cont représente l'endroit où retourner si Ctrl-C
"""
ctrlC=False
ret=None
try:
signal.signal(signal.SIGINT, raiseKeyboardInterrupt) # Ctrl-C
ret = box(*args)
signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C
except KeyboardInterrupt:
signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C
raise Continue(cancel_cont)
except DialogTerminatedBySignal as e:
signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C
if e[1] == 11:
self.dialog.msgbox(
"La fenêtre dialog à été fermée par une erreur de segmentation",
timeout=self.timeout, title="Erreur rencontrée", width=73, height=10
)
raise Continue(cancel_cont)
else:
raise
finally:
if ret:
return ret
else:
EnvironmentError("Pas de ret ?!? c'est pas possible ")
@tailcaller
def handle_dialog_result(self, code, output, cancel_cont, error_cont, codes_todo=[]):
"""
Gère les fonctions traitant les résultat d'appels à dialog.
s'occupe de gérer les exceptions, Ctrl-C, propagation de certaine exceptions, l'appuis sur annuler.
Le code à exécuté lui ai passé via la liste codes_todo, qui doit contenir une liste de triple :
(code de retour dialog, fonction à exécuter, liste des arguements de la fonction)
la fonction est appelée sur ses arguements si le code retourné par dialog correspond.
codes_todo = [(code, todo, todo_args)]
"""
# Si on a appuyé sur annulé ou ESC, on s'en va via la continuation donnée en argument
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cancel_cont)
# Sinon, c'est OK
else:
for (codes, todo, todo_args) in codes_todo:
if code in codes:
try:
signal.signal(signal.SIGINT, raiseKeyboardInterrupt) # Ctrl-C
# On effectue ce qu'il y a a faire dans todo
ret = todo(*todo_args)
signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C
return ret
# On propage les Continue
except self.error_to_raise:
signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C
raise
# En cas d'une autre erreur, on l'affiche et on retourne au menu d'édition
except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e:
signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C
self.dialog.msgbox(traceback.format_exc() if debug_enable else "%s" % unicode_of_Error(e), timeout=self.timeout,
title="Erreur rencontrée", width=73, height=10)
raise Continue(error_cont)
except KeyboardInterrupt:
signal.signal(signal.SIGINT, signal.SIG_IGN) # Pas de Ctrl-C
raise Continue(cancel_cont)
# En cas de code de retour dialog non attendu, on prévient et on retourne au menu d'édition
self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code,
timeout=self.timeout, title="Erreur rencontrée", width=73, height=10)
raise Continue(error_cont)
@tailcaller
def edit_blacklist_select(self, obj, title, cont):
"""
Permet de choisir une blackliste parmis celle de obj oubien une nouvelle blacklist.
Retourne (index, bl) où bl est un dictionnaire représentant la blackliste et index
l'index de la blackliste dans obj['blacklist'] ou new pour une nouvelle blacklist
"""
def box():
choices = [('new', 'Ajouter une nouvelle blackliste')]
index = 0
for bl in obj['blacklist']:
choices.append(
(str(index),
coul("%s [%s]" % (bl['type'], bl['comm']), 'rouge' if bl['actif'] else None,
dialog=True)
)
)
index+=1
return self.dialog.menu(
"Éditer une blacklist ou en ajouter une nouvelle ?\n(les blacklistes actives apparaissent en rouge)",
width=0,
timeout=self.timeout,
height=0,
menu_height=0,
item_help=0,
title=title,
scrollbar=True,
colors=True,
cancel_label="Retour",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=choices)
def todo(tag):
if tag == 'new':
return tag, {'debut':0, 'fin':0, 'type':'', 'comm':''}
else:
bl = {}
bl.update(obj['blacklist'][int(tag)].value)
return tag, bl
(code, tag) = self.handle_dialog(cont, box)
retry_cont = TailCall(self.edit_blacklist_select, obj=obj, title=title, cont=cont)
return self.handle_dialog_result(
code=code,
output=tag,
cancel_cont=cont,
error_cont=retry_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [tag])]
)
@tailcaller
def edit_blacklist_type(self, title, cont):
"""Permet de choisir un type de blackliste pour les nouvelles blacklistes"""
retry_cont = TailCall(self.edit_blacklist_type, title=title, cont=cont)
def box():
return self.dialog.menu(
"Choisissez un type de blacklist",
width=0,
height=0,
menu_height=0,
item_help=0,
timeout=self.timeout,
title=title,
scrollbar=True,
colors=True,
cancel_label="Retour",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=[(k,v) for (k,v) in config.blacklist_items.items()])
def todo(tag, retry_cont):
if tag in config.blacklist_items:
return tag
else:
self.dialog.msgbox("%s n'est pas une blacklist" % tag, timeout=self.timeout,
title="Erreur rencontrée", width=73, height=10)
raise Continue(retry_cont)
(code, tag) = self.handle_dialog(cont, box)
return self.handle_dialog_result(
code=code,
output=tag,
cancel_cont=cont(bl=None),
error_cont=retry_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, retry_cont])]
)
@tailcaller
def get_timestamp(self, title, text, cont, hour=-1, minute=-1, second=-1, day=0, month=0, year=0):
"""Fait choisir une date et une heure et retourne le tuple (year, month, day, hour, minute, second)"""
retry_cont = TailCall(self.get_timestamp, title=title, text=text, cont=cont, hour=hour,
minute=minute, second=second, day=day, month=month, year=year)
def get_date(day, month, year):
(code, output) = self.dialog.calendar(text, day=day, month=month, year=year,
timeout=self.timeout, title=title)
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont)
elif output:
(day, month, year) = output
return (year, month, day)
else:
raise EnvironmentError("Pourquoi je n'ai pas de date ?")
def get_time(hour, minute, second, day, month, year):
(code, output) = self.dialog.timebox(text, timeout=self.timeout, hour=hour,
minute=minute, second=second)
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(retry_cont(day=day, month=month, year=year))
elif output:
(hour, minute, second) = output
return (hour, minute, second)
else:
raise EnvironmentError("Pourquoi je n'ai pas d'horaire ?")
(year, month, day) = get_date(day, month, year)
(hour, minute, second) = get_time(hour, minute, second, day, month, year)
return (year, month, day) + (hour, minute, second)
def edit_blacklist(self, obj, title, update_obj, cont, bl=None, tag=None, bl_type=None,
debut=None, fin=None, comm=None):
"""Pour éditer les blacklistes d'un objet lc_ldap"""
self_cont = TailCall(self.edit_blacklist, obj=obj, title=title, update_obj=update_obj,
cont=cont, bl=bl, tag=tag, bl_type=bl_type, debut=debut, fin=fin, comm=comm)
# Si bl ou tag ne sont pas définit on les demande
if bl is None or tag is None:
bl_type = None
debut = None
fin = None
comm = None
tag, bl = self.edit_blacklist_select(obj, title, cont(**{update_obj:obj}))
if bl_type is None and tag == 'new':
bl['type'] = self.edit_blacklist_type(title, self_cont(obj=obj))
elif tag == 'new':
bl['type'] = bl_type
# Cas de l'ajout d'un blacklist
if tag == 'new':
# Si debut n'est pas encore spécifié, on le demande
if debut is None:
debut_tuple = self.get_timestamp(title=title, text="Choisir le début de la blacklist",
cont=self_cont(bl=bl, tag=tag, bl_type=None, debut=None, fin=None, comm=None))
debut = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1))))
# Idem pour fin
if fin is None:
if self.dialog.yesno("Mettre une date de fin ?", title=title,
timeout=self.timeout) == self.dialog.DIALOG_OK:
fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :",
cont=self_cont(bl=bl, tag=tag, bl_type=bl_type,
debut=None, fin=None, comm=None))
fin = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1))))
else:
fin = '-'
bl['debut']=debut
bl['fin']=fin
bl['comm']=self.get_comment(title=title, text="Commentaire ?",
cont=self_cont(bl=bl, tag=tag, bl_type=bl['type'], debut=debut, fin=None, comm=None))
if self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn),
title="Ajouter la blacklist ?"):
try:
with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj:
obj['blacklist'].append(bl)
obj.save()
# On s'en va en mettant à jour dans la continuation la valeur de obj
raise Continue(self_cont(bl=None, obj=obj))
# On propage les Continue
except self.error_to_raise:
raise
# En cas d'une autre erreur, on l'affiche et on retourne
except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e:
self.dialog.msgbox(traceback.format_exc() if debug_enable else ("%s" % unicode_of_Error(e)), timeout=self.timeout,
title="Erreur rencontrée", width=73)
raise Continue(self_cont(obj=obj))
else:
raise Continue(self_cont(bl=None, obj=obj))
# Cas de l'édition d'une blacklist
else:
if debut is None:
# Mettre un warning pour éditer (seulement quand debut vaut None pour ne pas le répéter à chaque fois que l'on revient en arrière par la suite
if not self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), title="Éditer la blackliste ?"):
raise Continue(self_cont(bl=None, obj=obj))
debut = time.localtime(bl['debut'])
debut_tuple = self.get_timestamp(title=title, text="Choisir le début de la blacklist", cont=self_cont(bl=bl, tag=tag, debut=None, fin=None, comm=None),
day=debut.tm_mday,
month=debut.tm_mon,
year=debut.tm_year,
hour=debut.tm_hour,
minute=debut.tm_min,
second=debut.tm_sec
)
debut = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1))))
bl['debut'] = debut
if fin is None:
if self.dialog.yesno("Mettre une date de fin ?", timeout=self.timeout, title=title) == self.dialog.DIALOG_OK:
if bl['fin'] == '-':
fin = time.localtime()
else:
fin = time.localtime(bl['fin'])
fin_tuple = self.get_timestamp(title=title, text="Choisir la date de fin :", cont=self_cont(bl=bl, tag=tag, debut=debut, fin=None, comm=None),
day=fin.tm_mday,
month=fin.tm_mon,
year=fin.tm_year,
hour=fin.tm_hour,
minute=fin.tm_min,
second=fin.tm_sec
)
fin = int(time.mktime(time.struct_time(debut_tuple + (0, 0, -1))))
else:
fin = '-'
bl['fin'] = fin
bl['comm']=self.get_comment(title=title, text="Commentaire ?", init=bl['comm'], cont=self_cont(bl=bl, tag=tag, bl_type=bl['type'], debut=debut, fin=None, comm=None))
if self.confirm_item(item=attributs.attrify(bl, 'blacklist', self.conn), title="Modifier la blacklist ?"):
try:
with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj:
obj['blacklist'][int(tag)]=bl
obj.save()
# On s'en va en mettant à jour dans la continuation la valeur de obj
raise Continue(self_cont(bl=None, obj=obj))
# On propage les Continue
except self.error_to_raise:
raise
# En cas d'une autre erreur, on l'affiche et on retourne au menu d'édition
except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e:
self.dialog.msgbox(traceback.format_exc() if debug_enable else "%s" % unicode_of_Error(e), timeout=self.timeout, title="Erreur rencontrée", width=73)
raise Continue(self_cont)
else:
raise Continue(self_cont(bl=None, obj=obj))
def edit_boolean_attributs(self, obj, attribs, title, update_obj, cont, values={}):
"""
Permet d'éditer des attributs booléen de l'objet obj listé dans attribs.
update_obj est le nom du paramètre à mettre à jour dans cont pour passer l'objet modifié
"""
# Dictionnaire décrivant quelle est la valeur booléenne à donner à l'absence de l'attribut
missing = {
'default' : False, # par défaut, on dit que c'est False
attributs.dnsIpv6 : True # pour dnsIpv6, c'est True
}
choices = [(a.ldap_name, a.legend, 1 if values.get(a.ldap_name, obj[a.ldap_name][0] if obj[a.ldap_name] else missing.get(a, missing['default'])) else 0) for a in attribs]
(code, output) = self.dialog.checklist("Activier ou désactiver les propriétés suivantes",
height=0,
width=0,
timeout=self.timeout,
list_height=7,
choices=choices,
title=title)
def todo(values, obj, attribs, cont):
# On met à jour chaque attribut si sa valeur à changé
with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj:
for a in attribs:
if obj[a.ldap_name] and obj[a.ldap_name][0] != values[a.ldap_name]:
obj[a.ldap_name]=values[a.ldap_name]
elif not obj[a.ldap_name] and missing.get(a, missing['default']) != values[a.ldap_name]:
obj[a.ldap_name]=values[a.ldap_name]
obj.save()
# On s'en va en mettant à jour dans la continuation la valeur de obj
raise Continue(cont(**{update_obj:obj}))
# On transforme la liste des cases dialog cochée en dictionnnaire
values = dict((a.ldap_name, a.ldap_name in output) for a in attribs)
# Une continuation que l'on suivra si quelque chose se passe mal
retry_cont = TailCall(self.edit_boolean_attributs, obj=obj, update_obj=update_obj, attribs=attribs, title=title, cont=cont, values=values)
return self.handle_dialog_result(
code=code,
output=output,
cancel_cont=cont,
error_cont=retry_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [values, obj, attribs, cont])]
)
def edit_attributs(self, obj, attr, title, update_obj, cont, tag=None, values=None):
"""
Permet d'éditer la liste d'attribut attr de l'objet obj.
update_obj est le nom du paramètre à mettre à jour dans cont pour passer l'objet modifié
"""
# Il n'y a pas inputmenu dans la lib dialog, du coup, on traite les arguments à la main.
# Ça reste relativement acceptable comme on utilise la fonction de la lib pour appeler dialog
def box(values, default_tag):
cmd = ['--inputmenu', "Édition de l'attribut %s :" % attr, "0", "0", "20"]
index=0
for value in values:
cmd.extend([str(index), str(value)])
index+=1
cmd.extend(['new', ''])
(code, output) = self.dialog._perform(*(cmd,), timeout=self.timeout, title=title, default_item=str(default_tag))
if code == self.dialog.DIALOG_EXTRA:
output = output.split(' ', 2)[1:]
else:
output = ''
return (code, output)
def todo_extra(output, values, retry_cont):
tag, value = output
if tag == 'new':
if value:
values.append(value)
elif value == '':
values.pop(int(tag))
else:
values[int(tag)] = value
raise Continue(retry_cont(values=values, tag=tag))
def todo(obj, values, cont):
with self.conn.search(dn=obj.dn, scope=0, mode='rw')[0] as obj:
obj[attr] = [unicode(value, 'utf-8') for value in values]
obj.save()
raise Continue(cont(**{update_obj:obj}))
if values is None:
values = [str(a) for a in obj[attr]]
retry_cont = TailCall(self.edit_attributs, obj=obj, attr=attr, title=title, update_obj=update_obj, cont=cont, values=values)
(code, output) = self.handle_dialog(cont, box, values, tag)
return self.handle_dialog_result(
code=code,
output=output,
cancel_cont=cont,
error_cont=retry_cont,
codes_todo=[
([self.dialog.DIALOG_OK], todo, [obj, values, cont]),
([self.dialog.DIALOG_EXTRA], todo_extra, [output, values, retry_cont]),
]
)
def search(self, objectClassS, title, values={}, cont=None, disable_field=[]):
"""
Rechercher des adhérents ou des machines dans la base ldap
retourne le tuple (code de retour dialog, valeurs entrée par l'utilisateur, liste d'objets trouvés)
La fonction est découpé en trois partie :
* affichage dialog et récupération du résultat
* construction de filtres de recherche ldap et recherches ldap
* filtre sur les résultats des recherches ldap
"""
select_dict = {
#label attribut ldap search for substring param dialog: line col valeur input-line icol len max-chars
'Nom' : {'ldap':'nom', 'sub':True, 'params' : [ 1, 1, values.get('nom', ""), 1, 13, 20, 20]},
'Prénom' : {'ldap':'prenom', 'sub':True, 'params' : [ 2, 1, values.get('prenom', ""), 2, 13, 20, 20]},
'Téléphone' : {'ldap':'tel', 'sub':True, 'params' : [ 3, 1, values.get('tel', ""), 3, 13, 10, 00]},
'Chambre' : {'ldap':'chbre','sub':True, 'params' : [ 4, 1, values.get('chbre',""), 4, 13, 05, 00]},
'aid' : {'ldap' : 'aid', 'sub':False, 'params' : [ 5, 1, values.get('aid',""), 5, 13, 05, 05]},
'mail' : {'ldap' : 'mail', 'sub':True, 'params' : [ 6, 1, values.get('mail',""), 6, 13, 40, 00]},
# seconde colone
'Machine' : {'ldap' : '*', 'sub':True, 'params' : [1, 35, "", 1, 43, 0, 0]},
'Host' : {'ldap' : 'host', 'sub':True, 'params' : [2, 37, values.get('host',""), 2, 43, 17, 17]},
'Mac' : {'ldap' : 'macAddress', 'sub':False, 'params' : [3, 37, values.get('macAddress',""), 3, 43, 17, 17]},
'IP' : {'ldap' : 'ipHostNumber', 'sub':False,'params' : [4, 37, values.get('ipHostNumber',""), 4, 43, 15, 15]},
'mid' : {'ldap' : 'mid', 'sub':False, 'params' : [5, 37, values.get('mid',""), 5, 43, 5, 5]},
}
# On a besoin de l'ordre pour récupérer les valeurs ensuite
select_adherent = ['Nom', 'Prénom', 'Téléphone', 'Chambre', 'aid', 'mail']
select_machine = ['Host', 'Mac', 'IP', 'mid']
if 'club' in objectClassS and not 'adherent' in objectClassS:
select_dict['cid']=select_dict['aid']
select_dict['cid']['ldap']='cid'
select_dict['cid']['params'][2]=values.get('cid', "")
select_adherent[select_adherent.index('aid')]='cid'
def box():
# On met les argument à dialog à la main ici, sinon, c'est difficile de choisir comment mettre une seconde colone
cmd = ["--mixedform", "Entrez vos paramètres de recherche", '0', '0', '0']
for key in select_adherent:
cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']] + ['2' if key in disable_field else '0'])
cmd.extend(['Machine :'] + [str(e) for e in select_dict['Machine']['params']] + ['2'])
for key in select_machine:
cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']] + ['2' if key in disable_field else '0'])
cmd.extend(["Les champs vides sont ignorés.", '7', '1', "", '0', '0', '0', '0', '2' ])
# On utilise quand même la fonction de la bibliothèques pour passer les arguments
(code, output) = self.dialog._perform(*(cmd,), timeout=self.timeout, title=title, backtitle="Entrez vos paramètres de recherche")
if output:
return (code, output.split('\n')[:-1])
else: # empty selection
return (code, [])
(code, dialog_values) = self.handle_dialog(cont, box)
# Si il a appuyé sur annuler ou sur escape, on saute sur la continuation
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont)
else:
# Transformation de la liste des valeures entrée en dictionnnaire
dialog_values = dict(zip(select_adherent + select_machine, dialog_values))
ldap_values = dict([(select_dict[key]['ldap'], value) for key, value in dialog_values.items()])
# Construction des filtres ldap pour les adhérents et les machines
filter_adherent = []
filter_machine = []
for (key, value) in dialog_values.items():
if value:
if key in select_adherent:
filter_adherent.append((u"(%s=*%s*)" if select_dict[key]['sub'] and not '*' in value else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8')))
elif key in select_machine:
filter_machine.append((u"(%s=*%s*)" if select_dict[key]['sub'] and not '*' in value else u"(%s=%s)") % (select_dict[key]['ldap'], unicode(value, 'utf-8')))
if filter_adherent:
filter_adherent=u"(&%s)" % "".join(filter_adherent)
if filter_machine:
filter_machine=u"(&%s)" % "".join(filter_machine)
# Récupération des adhérents et des machines
adherents=self.conn.search(filter_adherent) if filter_adherent else []
machines=self.conn.search(filter_machine) if filter_machine else []
# Filtrage des machines en fonction des adhérents
if filter_adherent:
if filter_machine:
# Si on filtre sur des adhérent et des machines, on calcule l'intersection
adherents_dn = set([a.dn for a in adherents])
machines_f = [m for m in machines if m.parent_dn in adherents_dn]
else:
# Sinon on filtre seulement sur les adhérents, récupère les machines des adhérents trouvés
machines_f = [m for a in adherents for m in a.machines()]
else:
# Sinon si on filtre seulement sur des machines
machines_f = machines
# Filtrage des adhérents en fonction des machines
if filter_machine:
if filter_adherent:
# Si on filtre sur des adhérents et des machines, on calcule l'intersection
machines_dn = set([m.parent_dn for m in machines])
adherents_f = [a for a in adherents if a.dn in machines_dn]
else:
# Sinon on récupères les proprios des machines trouvées
adherents_f = [m.proprio() for m in machines]
else:
# Sinon si on filtre seulement sur des adhérents
adherents_f = adherents
# On filtre sur les objectClassS
return ldap_values, [ o for objectClass in objectClassS for o in machines_f+adherents_f if objectClass in o['objectClass'] ]
@tailcaller
def select_one(self, items, title, text="Que souhaitez vous faire ?", default_item=None, cont=None):
"""Fait selectionner un item parmis une liste d'items à l'utisateur"""
def box(items, default_item):
choices=[]
olist={}
count = 0
# On sépare les item d'items en fonction de leur type
for o in items:
olist[o.__class__] = olist.get(o.__class__, []) + [o]
classes = olist.keys()
classes.sort()
default_tag = items.index(default_item) if default_item in items else default_item
# On se débrouille pour faire corresponde l'ordre d'affichache des objets
# et leur ordre dans la liste items. On donne la largeur de l'affichage à la main
# pour prendre en compte la largeur du widget dialog
items=[]
items_id = {}
(line, col) = get_screen_size()
for c in classes:
items.extend(olist[c])
items_s = printing.sprint_list(olist[c], col-20).encode('utf-8').split('\n')
choices.append(("", str(items_s[0])))
next=1
if items_s[next:]:
choices.append(("", str(items_s[next])))
next+=1
for i in items_s[next:]:
choices.append((str(count), str(i)))
count+=1
# On laisse une ligne vide pour séparer les listes d'objets de type différent
choices.append(("", ""))
# On supprime la dernière ligne qui est vide
del(choices[-1])
return self.dialog.menu(
text,
width=0,
height=0,
menu_height=0,
timeout=self.timeout,
item_help=0,
default_item=str(default_tag),
title=title,
scrollbar=True,
choices=choices,
colors=True)
def todo(tag, items, title, cont, retry_cont):
# Si l'utilisateur n'a pas choisis une ligne correspondant à quelque chose
if not tag:
self.dialog.msgbox("Merci de choisir l'un des item de la liste ou d'annuler", timeout=self.timeout, title="Sélection", width=0, height=0)
raise Continue(retry_cont)
# Sinon on retourne l'item choisis
elif self.confirm_item(items[int(tag)], title):
return items[int(tag)]
else:
raise Continue(cont)
(code, tag) = self.handle_dialog(cont, box, items, default_item)
retry_cont = TailCall(self.select_one, items=items, title=title, default_item=tag, cont=cont)
return self.handle_dialog_result(
code=code,
output=tag,
cancel_cont=cont,
error_cont=retry_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, items, title, cont, retry_cont])]
)
@tailcaller
def get_comment(self, title, text, cont, init='', force=False):
"""
Fait entrer à l'utilisateur un commentaire et le retourne.
Si force est à True, on oblige le commentaire à être non vide
"""
(code, output) = self.dialog.inputbox(text=text, title=title, timeout=self.timeout, init=init)
retry_cont = TailCall(self.get_comment, title=title, text=text, cont=cont, force=force)
def todo(output, force, title, retry_cont):
if force and not output:
self.dialog.msgbox("Entrée vide, merci d'indiquer quelque chose", timeout=self.timeout, title=title)
raise Continue(retry_cont)
else:
return unicode(output, 'utf-8')
return self.handle_dialog_result(
code=code,
output=output,
cancel_cont=cont,
error_cont=retry_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [output, force, title, retry_cont])]
)
def confirm_item(self, item, title, defaultno=False, text='', text_bottom="", **params):
"""Affiche un item et demande si c'est bien celui là que l'on veux (supprimer, éditer, créer,...)"""
return self.dialog.yesno(
text + printing.sprint(item, **params) + text_bottom,
no_collapse=True,
colors=True,
no_mouse=True,
timeout=self.timeout,
title=title,
defaultno=defaultno,
width=0, height=0,
backtitle="Appuyez sur MAJ pour selectionner du texte"
) == self.dialog.DIALOG_OK
def display_item(self, item, title, **params):
"""Affiche un item"""
return self.dialog.msgbox(
printing.sprint(item, **params),
no_collapse=True,
colors=True,
timeout=self.timeout,
title=title,
width=0, height=0,
backtitle="Appuyez sur MAJ pour selectionner du texte"
)
# On a besoin du décorateur ici car select va retourner un item après avoir
# possblement traiter plusieurs tailcall
@tailcaller
def select(self, objectClassS, title, values={}, cont=None, disable_field=[]):
"""Permet de choisir un objet adhérent ou machine dans la base ldap"""
try:
# On fait effectuer une recherche à l'utilisateur
values, items = self.search(objectClassS, title, values, cont=cont, disable_field=disable_field)
# S'il n'y a pas de résultas, on recommence
if not items:
self.dialog.msgbox("Aucun Résultat", timeout=self.timeout, title="Recherche", width=0, height=0)
raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, disable_field=disable_field, cont=cont))
# S'il y a plusieurs résultats
elif len(items)>1:
# On en fait choisir un, si c'est une continuation qui est renvoyé, elle est gérée par select
return self.select_one(items, title, cont=TailCall(self.select, objectClassS=objectClassS, title=title, values=values, disable_field=disable_field, cont=cont))
# S'il y a exactement 1 résultat à la recherche, on fait confirmer son choix à l'utilisateur
elif len(items) == 1:
item=items[0]
# On fait confirmer son choix à l'utilisateur
if self.confirm_item(item, title):
return item
else:
raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, disable_field=disable_field, cont=cont))
except self.error_to_raise:
raise
except Exception as e:
self.dialog.msgbox(traceback.format_exc() if debug_enable else "%r" % e, timeout=self.timeout, title="Erreur rencontrée", width=0, height=0)
raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, disable_field=disable_field, cont=cont))
def machine_information(self, cont, machine=None, objectClass=None, proprio=None, realm=None, fields_values=None):
"""
Permet de modifier une machine si elle est fournit par le paramètre machine
sinon, crée une machine à partir de proprio, objectClass et realm.
Si on ne fait qu'éditer une machine, proprio, objectClass et realm sont ignoré
D'une machinère générale, il faudrait mettre ici tous les attributs single value
et les multivalué que l'on peut simplement représenter de façon textuelle avec
un séparateur.
Pour le moment il y a :
* host
* macAddress
* ipHostNumber
* port(TCP|UDP)(in|out)
"""
a = attributs
# Quel sont les attributs ldap dont on veut afficher et la taille du champs d'édition correspondant
to_display = [(a.host, 30), (a.macAddress, 17), (a.ipHostNumber, 15),
(a.portTCPout, 50), (a.portTCPin, 50), (a.portUDPout, 50),
(a.portUDPin, 50)
]
# Quel séparateur on utilise pour les champs multivalué
separateur = ' '
def box():
if machine:
attrs = dict((k,[str(a) for a in at]) for k,at in machine.items())
else:
attrs = {}
fields = [("%s :" % a.legend, separateur.join(attrs.get(a.ldap_name, [a.default] if a.default else [])), l+1, l) for a,l in to_display]
return self.dialog.form(
text="",
timeout=self.timeout,
height=0, width=0, form_height=0,
fields=fields_values if fields_values else fields,
title="Paramètres machine",
backtitle="Gestion des machines du Crans")
def check_host(host, objectClass):
# Si c'est une machine wifi, host doit finir par wifi.crans.org
if "machineWifi" == objectClass or 'borneWifi' == objectClass:
hostend = ".wifi.crans.org"
# Si c'est une machine wifi, host doit finir par crans.org
elif "machineFixe" == objectClass:
hostend = ".crans.org"
# Si l'object class est machineCrans, pas de vérification
elif "machineCrans" == objectClass:
return host
# Sinon, libre à chachun d'ajouter d'autres objectClass ou de filtrer
# plus finement fonction des droits de self.conn.droits
else:
raise ValueError("La machine n'est ni une machine fixe, ni une machine wifi mais %s ?!?" % objectClass)
if not host.endswith(hostend) and not '.' in host:
host = "%s.wifi.crans.org" % host
elif host.endswith(hostend) and '.' in host[:-len(hostend)]:
raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend)
elif not host.endswith(hostend) and '.' in host:
raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend)
return host
def modif_machine(machine, attrs):
with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine:
for (key, values) in attrs.items():
machine[key]=values
machine.validate_changes()
machine.save()
return machine
def create_machine(proprio, realm, attrs):
# Dans ce cas, on a besoin d'un proprio et d'un realm pour déterminer le rid
if proprio is None or realm is None:
raise EnvironmentError("On essaye de créer une machine mais proprio ou realm vaut None")
ldif = {
'macAddress': ['%s' % attrs['macAddress']],
'host': ['%s' % attrs['host']]
}
with self.conn.newMachine(proprio.dn, realm, ldif) as machine:
for (key, values) in attrs.items():
machine[key]=values
if attributs.ipsec in machine.attribs:
machine[attributs.ipsec.ldap_name]=attributs.ipsec.default
machine.validate_changes()
if self.confirm_item(machine, "Voulez vous vraiement créer cette machine ?"):
machine.create()
self.display_item(machine, "La machine à bien été créée", ipsec=True)
return machine
else:
raise Continue(cont)
def todo(to_display, tags, objectClass, machine, proprio, realm, separateur, cont):
attrs = {}
# On traite les valeurs reçues
for ((a,l),values) in zip(to_display, tags):
values = unicode(values, 'utf-8')
# Si le champs n'est pas single value, on utilise separateur pour découper
# et on ne garde que les valeurs non vides
if not a.singlevalue:
values = [v for v in values.split(separateur) if v]
# Pour host, on fait quelques vérification de syntaxe
if a.ldap_name == 'host':
attrs[a.ldap_name]=check_host(values, objectClass)
else:
attrs[a.ldap_name]=values
# Soit on édite une machine existante
if machine:
machine = modif_machine(machine, attrs)
# Soit on crée une nouvelle machine
else:
machine = create_machine(proprio, realm, attrs)
raise Continue(cont(machine=machine))
if machine:
objectClass = machine["objectClass"][0]
(code, tags) = self.handle_dialog(cont, box)
# On prépare les fiels à afficher à l'utilisateur si une erreure à lieu
# pendant le traitement des donnée (on n'éfface pas ce qui a déjà été entré
# c'est au cableur de corriger ou d'annuler
fields_values = [("%s :" % a.legend, values, l) for ((a,l),values) in zip(to_display, tags)]
retry_cont = TailCall(self.machine_information, machine=machine, cont=cont, objectClass=objectClass, proprio=proprio, realm=realm, fields_values=fields_values)
return self.handle_dialog_result(
code=code,
output=tags,
cancel_cont=cont,
error_cont=retry_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [to_display, tags, objectClass, machine, proprio, realm, separateur, cont])]
)
def modif_machine_blacklist(self, machine, cont):
"""Raccourci vers edit_blacklist spécifique aux machines"""
return self.edit_blacklist(obj=machine, title="Éditions des blacklist de la machine %s" % machine['host'][0], update_obj='machine', cont=cont)
def certificat_tlsa(self, certificat, cont, values=None):
"""Menu d'éditions des paramètres TLSA d'un certificat"""
separateur = ' '
form = {
'Type de certificat' : {'ldap_name' : 'certificatUsage', 'text':"".join(str(s) for s in certificat.get('certificatUsage', [])), 'len':1},
'Type de correspondance' : {'ldap_name' : 'matchingType', 'text':"".join(str(s) for s in certificat.get('matchingType', [])), 'len':1},
'Ports TCP' : {'ldap_name' : 'portTCPin', 'text':separateur.join(str(p) for p in certificat.get('portTCPin', [])), 'len':30},
'Ports UDP' : {'ldap_name' : 'portUDPin', 'text':separateur.join(str(p) for p in certificat.get('portUDPin', [])), 'len':30},
}
form_order = ['Type de certificat', 'Type de correspondance', 'Ports TCP', 'Ports UDP']
def box(fields_values=None):
fields = [("%s : " % k, form[k]['text'], form[k]['len'] + 1, form[k]['len']) for k in form_order]
return self.dialog.form(
text="""Type de certificat : Type de correspondance :
* 0 - CA pinning * 0 - certificat entier
* 1 - Cert pinning * 1 - sha256
* 2 - CA auto signé * 2 - sha512
* 3 - Cert autosigné""",
no_collapse=True,
height=0, width=0, form_height=0,
timeout=self.timeout,
fields=fields_values if fields_values else fields,
title="Paramètres TLS d'un certificat de la machine %s" % certificat.machine()['host'][0],
backtitle="Gestion des certificats des machines du Crans")
def todo(form, values, certificat, cont):
if not values['certificatUsage'] in ['0', '1', '2', '3']:
raise ValueError("""Type de certificat invalide :
les valeurs valident sont :
* 0 pour du CA pinning
(le certificat doit être une autorité de certification valide)
* 1 pour du certificat pinning
(le certificat doit déjà être validé par les navigateur)
* 2 pour ajouter un CA
(pour les autorité de certification autosigné)
* 3 pour les certificats autosigné"""
)
if not values['matchingType'] in ['0', '1', '2']:
raise ValueError("""Type de correspondance invalide :
les valeurs valident sont :
* 0 le certificat sera mis entièrement dans le dns
* 1 le sha256 du certificat sera mis dans le dns
* 2 le sha512 du certificat sera mis dans le dns"""
)
with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat:
if "TLSACert" in certificat['objectClass']:
certificat['certificatUsage'] = unicode(values['certificatUsage'])
certificat['matchingType'] = unicode(values['matchingType'])
else:
certificat.tlsa(values['certificatUsage'], values['matchingType'])
certificat['portTCPin'] = [unicode(s, 'utf-8') for s in values['portTCPin'].split(separateur) if s]
certificat['portUDPin'] = [unicode(s, 'utf-8') for s in values['portUDPin'].split(separateur) if s]
certificat.save()
raise Continue(cont(certificat=certificat))
(code, output) = self.handle_dialog(cont, box, values)
values = dict(zip([form[k]['ldap_name'] for k in form_order], output))
fields_values = [("%s : " % k, values.get(form[k]['ldap_name'], ""), form[k]['len'] + 1, form[k]['len']) for k in form_order]
self_cont=TailCall(self.certificat_tlsa, certificat=certificat, cont=cont, values=fields_values)
return self.handle_dialog_result(
code=code,
output=output,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [form, values, certificat, cont])]
)
def create_certificat(self, cont, machine=None, certificat=None):
"""Permet d'ajouter un certificat à une machine à partir du PEM du certificat"""
if machine is None and certificat is None:
raise EnvironmentError("Il faut fournir au moins une machine ou un certificat")
# input multiline en utilisant un editbox
def box():
fp, path = tempfile.mkstemp()
os.close(fp)
cmd = ['--editbox', path, "0", "0"]
(code, output) = self.dialog._perform(*(cmd,),
no_mouse=True, # On désactive la sourie sinon dialog segfault si on clic
backtitle="Appuyez sur CTRL+MAJ+V pour coller",
timeout=self.timeout,
title="Création d'un certificat, entrez le PEM du certificat")
os.remove(path)
if code == self.dialog.DIALOG_OK:
return code, output
else:
return code, None
def todo(machine, certificat, cont):
if certificat:
with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat:
certificat['certificat'] = unicode(pem.strip(), 'utf-8')
certificat.save()
else:
with self.conn.newCertificat(machine.dn, {}) as certificat:
certificat['certificat'] = unicode(pem.strip(), 'utf-8')
certificat.create()
raise Continue(cont(certificat=certificat, machine=certificat.machine()))
(code, pem) = self.handle_dialog(cont, box)
self_cont = TailCall(self.create_certificat, machine=machine, certificat=certificat, cont=cont)
return self.handle_dialog_result(
code=code,
output=pem,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [machine, certificat, cont])]
)
@tailcaller
def get_password(self, cont, confirm=True, title="Choix d'un mot de passe", **kwargs):
"""
Affiche une série d'inpuxbox pour faire entrer un mot de passe puis le retourne,
si confirm=True, il y a une confirmation du mot de passe de demandée
"""
def todo(self_cont, cont):
(code, pass1) = self.dialog.passwordbox("Entrez un mot de passe", title=title, timeout=self.timeout, **kwargs)
if code != self.dialog.DIALOG_OK:
raise Continue(cont)
elif not pass1:
raise ValueError("Mot de pass vide !")
if confirm:
(code, pass2) = self.dialog.passwordbox("Comfirmer le mot de passe", timeout=self.timeout, title=title, **kwargs)
if code != self.dialog.DIALOG_OK:
raise Continue(self_cont)
if pass1 != pass2:
raise ValueError("Les deux mots de passe ne concordent pas")
return pass1
self_cont = TailCall(self.get_password, cont=cont)
return self.handle_dialog_result(
code=self.dialog.DIALOG_OK,
output="",
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [self_cont, cont])]
)
def create_privatekey(self, cont, machine=None, certificat=None, imp=False, size=4096):
"""Permet de générer ou importer une clef privée à une machine"""
if machine is None and certificat is None:
raise EnvironmentError("Il faut fournir au moins une machine ou un certificat")
# input multiline en utilisant un editbox
def box():
fp, path = tempfile.mkstemp()
os.close(fp)
cmd = ['--editbox', path, "0", "0"]
(code, output) = self.dialog._perform(*(cmd,),
no_mouse=True, # On désactive la sourie sinon dialog segfault si on clic
backtitle="Appuyez sur CTRL+MAJ+V pour coller",
timeout=self.timeout,
title="Création d'un certificat, entrez le PEM du certificat")
os.remove(path)
if code == self.dialog.DIALOG_OK:
return code, output
else:
return code, None
def todo(machine, certificat, pem, imp, size, cont):
if not imp:
if not machine:
machine=certificat.machine()
if "machineCrans" in machine['objectClass']:
passphrase = secrets.get('privatekey_passphrase')
else:
self.dialog.msgbox("Vous aller être inviter à entrez un mot de passe. Ce mot de passe est utilisé pour chiffrer la clef privée qui va être générée dans la base de donnée du crans.\n\nCe mot de passe n'est pas conservé, sous quelque forme que se soit par le crans.\nAussi, en cas de perte, la clef privée deviendrait inutilisable.\n Pensez à le sauvegarder quelque part",
title="Génération d'une clée privée",
width=70, timeout=self.timeout,
height=12)
passphrase = self.get_password(cont)
self.dialog.infobox("Génération d'une clef privée RSA de taille %s en cours.\nMerci de patienter" % size)
pem = crypto.PKey()
pem.generate_key(crypto.TYPE_RSA, size)
pem = crypto.dump_privatekey(crypto.FILETYPE_PEM, pem, "des3", passphrase)
elif not pem.startswith("-----BEGIN ENCRYPTED PRIVATE KEY-----"):
raise ValueError("On n'accepte que des clef chiffrée PKCS#8 en PEM. Donc la clef doit commencer par -----BEGIN ENCRYPTED PRIVATE KEY-----")
if certificat:
if "privatekey" in certificat:
raise ValueError("Il y a déjà une clef privée, merci d'annuler")
with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat:
certificat.private(pem, encrypted=True)
certificat.save()
self.dialog.msgbox("Clef privée bien ajouté", timeout=self.timeout, title="Ajout d'une clef privée")
raise Continue(cont(certificat=certificat, machine=certificat.machine()))
else:
with self.conn.newCertificat(machine.dn, {}) as certificat:
certificat['hostCert']=unicode(machine['host'][0])
certificat.private(pem, encrypted=True)
certificat.create()
self.dialog.msgbox("Clef privée créée avec succès", timeout=self.timeout, title="Création d'une clef privée")
raise Continue(cont(certificat=certificat, machine=certificat.machine()))
if imp:
(code, pem) = self.handle_dialog(cont, box)
else:
(code, pem) = (self.dialog.DIALOG_OK, "")
self_cont = TailCall(self.create_privatekey, machine=machine, certificat=certificat, cont=cont, imp=imp, size=size)
return self.handle_dialog_result(
code=code,
output=pem,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [machine, certificat, pem, imp, size, cont])]
)
def delete_certificat(self, certificat, cont):
"""Supprime un certificat"""
def todo(certificat, cont):
if self.confirm_item(item=certificat, title="Voulez vous vraiement supprimer le certificat ?"):
with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat:
certificat.delete()
self.dialog.msgbox("Le certificat a bien été supprimé", timeout=self.timeout, title="Suppression d'un certificat")
raise Continue(cont(certificat=None, machine=certificat.machine(refresh=True)))
else:
raise Continue(cont(certificat=certificat))
return self.handle_dialog_result(
code=self.dialog.DIALOG_OK,
output="",
cancel_cont=cont,
error_cont=TailCall(self.delete_certificat, certificat=certificat, cont=cont),
codes_todo=[([self.dialog.DIALOG_OK], todo, [certificat, cont])]
)
def gen_csr(self, certificat, cont):
"""Permet de générer un csr à partir de la clef privée du certificat"""
def todo(certificat, self_cont, cont):
if certificat['encrypted']:
if "machineCrans" in certificat.machine()["objectClass"]:
passphrase = secrets.get('privatekey_passphrase')
else:
self.dialog.msgbox("Mercie de fournir le mot de passe chiffrant la clef privée.\nIl a été choisis lors de la création de la clef.",
title="Génération d'un CSR",
width=70,
height=10, timeout=self.timeout)
passphrase = self.get_password(cont, confirm=False)
else:
passphrase = None
try:
if passphrase:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, str(certificat['privatekey'][0]), passphrase)
else:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, str(certificat['privatekey'][0]))
except crypto.Error as e:
if len(e.message) > 2 and len(e.message[2]) > 2 and e.message[2][2] == 'bad password read':
self.dialog.msgbox("Mauvais mot de passe", timeout=self.timeout)
raise Continue(self_cont)
else:
raise
req = createCertRequest(pkey,
digest="sha1",
subjectAltName=[str(host) for host in certificat['hostCert'][1:]],
C=u"FR",
ST=u"Ile de France",
L=u"Cachan",
O=u"Association Cachan Réseaux A Normal SUP (C.R.A.N.S)",
OU=u"Crans",
CN=unicode(certificat['hostCert'][0]),
)
csr = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat:
certificat['csr']=unicode(csr)
certificat.save()
self.handle_dialog(cont, box, csr)
if self.dialog.yesno("Remplacer le certificat actuel ?", timeout=self.timeout) == self.dialog.DIALOG_OK:
return self.create_certificat(certificat=certificat, cont=cont(certificat=certificat))
else:
raise Continue(cont(certificat=certificat))
self_cont = TailCall(self.gen_csr, certificat=certificat, cont=cont)
return self.handle_dialog_result(
code=self.dialog.DIALOG_OK,
output="",
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [certificat, self_cont, cont])]
)
def get_certificat(self, certificat, cont, privatekey=False, csr=False):
"""Permet d'afficher le certificat courant"""
def box(text):
fp, path = tempfile.mkstemp()
os.write(fp, text)
os.close(fp)
self.dialog.textbox(filename=path, height=0, width=0,
backtitle="Appuyez sur CTRL+MAJ+V pour coller",
title="Récupération d'un certificat",
no_mouse=True, timeout=self.timeout,)
os.remove(path)
return
if privatekey:
self.handle_dialog(cont, box, unicode(certificat['privatekey'][0]))
elif csr:
self.handle_dialog(cont, box, unicode(certificat['csr'][0]))
else:
self.handle_dialog(cont, box, unicode(ssl.DER_cert_to_PEM_cert(str(certificat['certificat'][0]))))
raise Continue(cont)
def create_csr(self, cont, machine=None, certificat=None):
"""Permet d'ajouter un csr à une machine à partir du PEM du csr"""
if machine is None and certificat is None:
raise EnvironmentError("Il faut fournir au moins une machine ou un certificat")
# input multiline en utilisant un editbox
def box():
fp, path = tempfile.mkstemp()
os.close(fp)
cmd = ['--editbox', path, "0", "0"]
(code, output) = self.dialog._perform(*(cmd,),
no_mouse=True, # On désactive la sourie sinon dialog segfault si on clic
backtitle="Appuyez sur CTRL+MAJ+V pour coller",
timeout=self.timeout,
title="Création d'un certificat, entrez le PEM du certificat")
os.remove(path)
if code == self.dialog.DIALOG_OK:
return code, output
else:
return code, None
def todo(machine, certificat, pem, cont):
if certificat:
with self.conn.search(dn=certificat.dn, scope=0, mode='rw')[0] as certificat:
certificat['csr'] = unicode(pem.strip(), 'utf-8')
certificat.save()
else:
with self.conn.newCertificat(machine.dn, {}) as certificat:
certificat['hostCert']=unicode(machine['host'][0])
certificat['csr'] = unicode(pem.strip(), 'utf-8')
certificat.create()
raise Continue(cont(certificat=certificat, machine=certificat.machine()))
(code, pem) = self.handle_dialog(cont, box)
self_cont = TailCall(self.create_csr, machine=machine, certificat=certificat, cont=cont)
return self.handle_dialog_result(
code=code,
output=pem,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [machine, certificat, pem, cont])]
)
def modif_machine_certificat(self, machine, cont, tag=None, certificat=None):
"""Permet l'édition d'un certificat d'une machine"""
self_cont = TailCall(self.modif_machine_certificat, machine=machine, cont=cont, certificat=certificat)
if certificat is None:
certificat_index = self.edit_certificat_select(machine=machine, title="Modification des certificats de %s" % machine['host'][0], cont=cont)
if certificat_index == 'new':
raise Continue(TailCall(self.create_certificat, machine=machine, cont=self_cont))
elif certificat_index == 'priv':
raise Continue(TailCall(self.create_privatekey, machine=machine, cont=self_cont))
elif certificat_index == 'csr':
raise Continue(TailCall(self.create_csr, machine=machine, cont=self_cont))
certificat = machine.certificats()[certificat_index]
a = attributs
menu_droits = {
'Hostname':[a.parent, a.nounou],
'AddPrivateKey':[a.parent, a.nounou],
'AddCertificate':[a.parent, a.nounou],
'SetCertificate':[a.parent, a.nounou],
'TLSA':[a.parent, a.nounou],
'Autre':[a.nounou],
'GetPriv':[a.parent, a.nounou],
'GetCert':[a.parent, a.nounou, a.cableur],
'GetCSR':[a.parent, a.nounou, a.cableur],
'GenCSR':[a.parent, a.nounou],
'Remarque':[a.parent, a.nounou, a.cableur],
'Supprimer':[a.parent, a.nounou],
}
menu = {
'Hostname' : {'text':"Noms d'hôte utilisant le certificat", "help":'Il doivent être inclus dans les host et hostAlias de la machine parente', "attribut":attributs.hostCert},
'AddPrivateKey' : {'text': 'Ajouter la clef privée', 'help':'La clef doit être obligatoirement chiffrée avant envoi', "callback":TailCall(self.create_privatekey, imp=True)},
'AddCertificate' : {'text': 'Ajouter un certificat X509', 'help':'', "callback":self.create_certificat},
'SetCertificate' : {'text': 'Remplacer le certificat X509', 'help':'', "callback":self.create_certificat},
'TLSA' : {'text':"Paramètres pour les champs dns TLSA", 'help':'Permet de configurer DANE pour le certificat X509', "callback":self.certificat_tlsa},
'Autre': {'text' : "Modifier les attribut booléen comme revocked", 'help':'', "callback":self.modif_certificat_boolean},
'GetPriv' : {'text' : 'Récupérer la clef privée', 'help':"Affiche la clef privée telle qu'elle est dans la base de donnée, c'est à dire chiffrée", 'callback':TailCall(self.get_certificat, privatekey=True)},
'GetCert' : {'text' : 'Récupérer le certificat', 'help':"Affiche le certificat au format PEM", 'callback':self.get_certificat},
'GetCSR' : {'text' : 'Récupérer la requête de signature de certificat', 'help':"Affiche le CSR au format PEM", 'callback':TailCall(self.get_certificat, csr=True)},
'GenCSR' : {'text' : 'Générer un CSR, puis remplacer le certificat', 'help':'Généré à partir de la clef privée. Les noms (CN et subjectAltName) sont pris à partir de Hostname (attribut hostCert)', "callback":self.gen_csr},
'Remarque' : {'text': 'Mettre des remarques', 'help':'La première apparait dans la liste des certificats', 'attribut':attributs.info},
'Supprimer' : {'text' : "Supprimer le certificat", 'help':'', "callback":self.delete_certificat},
}
if "privateKey" in certificat["objectClass"]:
menu
menu_order = ['Hostname']
if not "privateKey" in certificat['objectClass']:
menu_order.extend(['AddPrivateKey', 'SetCertificate'])
if not "x509Cert" in certificat['objectClass']:
menu_order.extend([ 'AddCertificate'])
if "x509Cert" in certificat['objectClass']:
menu_order.extend(['TLSA', 'Autre', 'GetCert'])
if certificat['csr']:
menu_order.extend(['GetCSR'])
if "privateKey" in certificat['objectClass']:
if attributs.nounou in self.conn.droits or machine.dn.startswith(self.conn.dn):
menu_order.extend(['GetPriv'])
menu_order.extend(['GenCSR'])
menu_order.extend(['Remarque', 'Supprimer'])
def box(default_item=None):
text="Certificat de %s, xid=%s :\n" % (certificat['hostCert'][0], certificat['xid'][0])
if "x509Cert" in certificat['objectClass']:
text += " * Certificat N°0x%X émis par %s, valable du %s au %s\n" % (
int(str(certificat['serialNumber'][0])),
certificat['issuerCN'][0],
time.strftime("%d/%m/%Y", time.localtime(int(certificat['start'][0]))),
time.strftime("%d/%m/%Y", time.localtime(int(certificat['end'][0])))
)
if "privateKey" in certificat['objectClass']:
text += " * Clef privée\n"
if certificat['csr']:
text += " * Requête de signature de certificat\n"
if certificat['info']:
text += str(certificat['info'][0])
return self.dialog.menu(
text,
width=0,
height=0,
menu_height=0,
item_help=1,
timeout=self.timeout,
default_item=str(default_item),
title="Modification des certificats de %s" % certificat.machine()['host'][0],
scrollbar=True,
cancel_label="Retour",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=[(key, menu[key]['text'], menu[key]['help']) for key in menu_order if self.has_right(menu_droits[key], certificat)])
def todo(tag, menu, certificat, self_cont):
if not tag in menu_order:
raise Continue(self_cont(certificat=certificat))
else:
if 'callback' in menu[tag]:
raise Continue(TailCall(menu[tag]['callback'], certificat=certificat, cont=self_cont(certificat=certificat, tag=tag)))
elif 'attribut' in menu[tag]:
raise Continue(TailCall(self.modif_certificat_attributs, certificat=certificat, cont=self_cont(certificat=certificat, tag=tag), attr=menu[tag]['attribut'].ldap_name))
else:
raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag)
cancel_cont = cont(machine=machine) if certificat is None else self_cont(machine=certificat.machine(), certificat=None, tag=tag)
(code, tag) = self.handle_dialog(cancel_cont, box, tag)
return self.handle_dialog_result(
code=code,
output=tag,
cancel_cont=cancel_cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, certificat, self_cont])]
)
@tailcaller
def edit_certificat_select(self, machine, title, cont):
"""Permet de choisir un certificat existant ou nouveau d'une machine"""
a = attributs
menu_droits = {
'new':[a.parent, a.nounou],
'priv':[a.parent, a.nounou],
'csr':[a.parent, a.nounou],
}
menu = {
'new':'Ajouter un nouveau certificat',
'priv':'Générer une nouvelle clef privée',
'csr':'Ajouter une nouvelle requête de certificat',
}
menu_order = ['new', 'priv', 'csr']
menu_special = ['new', 'priv', 'csr']
def box(default_item=None):
index=0
choices = []
for key in menu_order:
if self.has_right(menu_droits[key], machine):
choices.append((key, menu[key]))
for cert in machine.certificats():
if cert['info']:
item = str(cert['info'][0])
elif "x509Cert" in cert['objectClass']:
item = "Emit par %s pour %s du %s au %s" % (cert['issuerCN'][0], ', '.join(str(cn) for cn in cert['hostCert']), time.strftime("%d/%m/%Y", time.localtime(int(cert['start'][0]))), time.strftime("%d/%m/%Y", time.localtime(int(cert['end'][0]))))
elif "privateKey" in cert['objectClass']:
item = "Clef privée de %s, xid=%s" % (cert['hostCert'][0], cert['xid'][0])
elif cert['csr']:
item = "Requête de signature de certificat pour %s, xid=%s" % (cert['hostCert'][0], cert['xid'][0])
choices.append((str(index), item))
index+=1
return self.dialog.menu(
"Modifier ou ajouter un certificat ?",
width=0,
height=0,
timeout=self.timeout,
menu_height=0,
item_help=0,
title="Modification des certificats de %s" % machine['host'][0],
scrollbar=True,
default_item=str(default_item),
cancel_label="Retour",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=choices)
def todo(tag):
if tag in ['new', 'priv', 'csr']:
return tag
else:
return int(tag)
(code, tag) = self.handle_dialog(cont, box)
retry_cont = TailCall(self.edit_certificat_select, machine=machine, title=title, cont=cont)
return self.handle_dialog_result(
code=code,
output=tag,
cancel_cont=cont(machine=machine),
error_cont=retry_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [tag])]
)
def modif_machine_attributs(self, machine, attr, cont):
"""Juste un raccourci vers edit_attributs spécifique aux machines"""
return self.edit_attributs(obj=machine, update_obj='machine', attr=attr, title="Modification de la machine %s" % machine['host'][0], cont=cont)
def modif_adherent_attributs(self, adherent, attr, cont):
"""Juste un raccourci vers edit_attributs spécifique aux adherents"""
return self.edit_attributs(obj=adherent, update_obj='adherent', attr=attr, title="Modification de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), cont=cont)
def modif_proprio_attributs(self, proprio, attr, cont):
"""Juste un raccourci vers edit_attributs spécifique aux proprios"""
return self.edit_attributs(obj=proprio, update_obj='proprio', attr=attr, title="Modification de %s %s" % (proprio.get('prenom', [''])[0], proprio['nom'][0]), cont=cont)
def modif_certificat_attributs(self, certificat, attr, cont):
"""Juste un raccourci vers edit_attributs spécifique aux certificats"""
return self.edit_attributs(obj=certificat, update_obj='certificat', attr=attr, title="Modification d'un certificat de la machine %s" % certificat.machine()['host'][0], cont=cont)
def modif_machine_boolean(self, machine, cont):
"""Juste un raccourci vers edit_boolean_attributs spécifique aux machines"""
a = attributs
attribs = [a.dnsIpv6]
return self.edit_boolean_attributs(
obj=machine,
attribs=attribs,
title="Édition des attributs booléen de la machine %s" % machine['host'][0],
update_obj='machine',
cont=cont)
def modif_certificat_boolean(self, certificat, cont):
"""Juste un raccourci vers edit_boolean_attributs spécifique aux certificats"""
a = attributs
attribs = [a.revocked]
return self.edit_boolean_attributs(
obj=certificat,
attribs=attribs,
title="Édition des attributs booléen d'un certificat de la machine %s" % certificat.machine()['host'][0],
update_obj='certificat',
cont=cont)
def modif_machine(self, cont, machine=None, tag=None):
"""
Permet d'éditer une machine. Si fournie en paramètre on éditer en place,
sinon, on en cherche une dans la base ldap
"""
if machine is None:
machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour modification", cont=cont)
a = attributs
menu_droits = {
'Information' : [a.parent, a.cableur, a.nounou],
'Autre': [a.parent, a.cableur, a.nounou],
'Blackliste':[a.cableur, a.nounou],
'Certificat': [a.parent, a.cableur, a.nounou],
'Exemption' : [a.nounou],
'Alias' : [a.parent, a.cableur, a.nounou],
'Remarques' : [a.cableur, a.nounou],
'SshKey' : [a.parent, a.nounou],
'Supprimer' : [a.parent, a.cableur, a.nounou],
}
menu = {
'Information' : {'text' : "Modifier le nom de machine, l'IP, adresse MAC", "callback":self.machine_information},
'Autre' : {'text' : "Modifier les attribut booléen comme dsnIpv6", "callback":self.modif_machine_boolean},
'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_machine_blacklist},
'Certificat' : {'text': 'Modifier les certificats de la machine', 'callback':self.modif_machine_certificat},
'Exemption' : {'text':"Modifier la liste d'exemption d'upload de la machine", 'attribut':attributs.exempt},
'Alias' : {'text': 'Créer ou supprimer un alias de la machine', 'attribut':attributs.hostAlias},
'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'attribut':attributs.info},
'SshKey' : {'text':'Ajouter ou supprimer une clef ssh pour la machine', 'attribut':attributs.sshFingerprint},
'Supprimer' : {'text':'Supprimer la machine', 'callback':self.delete_machine},
}
menu_order = ['Information', 'Blackliste', 'Certificat', 'Alias', 'Exemption', 'SshKey', 'Autre', 'Remarques', 'Supprimer']
def box(default_item=None):
return self.dialog.menu(
"Que souhaitez vous modifier ?",
width=0,
height=0,
menu_height=0,
timeout=self.timeout,
item_help=0,
default_item=str(default_item),
title="Modification de %s" % machine['host'][0],
scrollbar=True,
cancel_label="Retour",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=[(key, menu[key]['text']) for key in menu_order if self.has_right(menu_droits[key], machine)])
def todo(tag, menu, machine, cont_ret):
if not tag in menu_order:
raise Continue(cont_ret)
else:
if 'callback' in menu[tag]:
raise Continue(TailCall(menu[tag]['callback'], machine=machine, cont=cont_ret))
elif 'attribut' in menu[tag]:
raise Continue(TailCall(self.modif_machine_attributs, machine=machine, cont=cont_ret, attr=menu[tag]['attribut'].ldap_name))
else:
raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag)
(code, tag) = self.handle_dialog(cont, box, tag)
cont_ret = TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag)
return self.handle_dialog_result(
code=code,
output=tag,
cancel_cont=cont(machine=machine),
error_cont=cont_ret,
codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, machine, cont_ret])]
)
def create_machine_proprio(self, cont, proprio, tag=None):
"""Permet d'ajouter une machine à un proprio (adherent, club ou AssociationCrans)"""
a = attributs
menu_droits = {
'Fixe' : [a.soi, a.cableur, a.nounou],
'Wifi' : [a.soi, a.cableur, a.nounou],
}
menu = {
'Fixe' : {'text' : "Machine filaire", 'objectClass':'machineFixe', 'realm':'adherents'},
'Wifi' : {'text': 'Machine sans fil', 'objectClass':'machineWifi', 'realm':'wifi-adh'},
}
menu_order = ['Fixe', 'Wifi']
if isinstance(proprio, objets.AssociationCrans):
menu_droits.update({
'Fixe' : [a.nounou],
'Wifi' : [a.nounou],
'Adm' : [a.nounou],
})
menu.update({
'Fixe' : {'text' : "Ajouter un serveur sur le vlan adherent", 'objectClass':'machineCrans', 'realm':'serveurs'},
'Wifi' : {'text': 'Ajouter une borne WiFi sur le vlan wifi', 'objectClass':'borneWifi', 'realm':'bornes'},
'Adm' : {'text' : "Ajouter un serveur sur le vlan adm", "objectClass":"machineCrans", 'realm':'adm'},
})
menu_order.append('Adm')
def box(default_item=None):
return self.dialog.menu(
"Type de Machine ?",
width=0,
height=0,
menu_height=0,
item_help=0,
timeout=self.timeout,
default_item=str(default_item),
title="Création de machines",
scrollbar=True,
cancel_label="Retour",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=[(key, menu[key]['text']) for key in menu_order if self.has_right(menu_droits[key], proprio)])
def todo(tag, menu, proprio, self_cont, cont):
if not tag in menu_order:
raise Continue(self_cont)
else:
return self.machine_information(
cont=cont,
machine=None,
objectClass=menu[tag]['objectClass'],
proprio=proprio,
realm=menu[tag]['realm']
)
(code, tag) = self.handle_dialog(cont, box, tag)
cont = cont(proprio=None) if isinstance(proprio, objets.AssociationCrans) else cont(proprio=proprio)
self_cont = TailCall(self.create_machine_proprio, cont=cont, proprio=proprio, tag=tag)
return self.handle_dialog_result(
code=code,
output=tag,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, proprio, self_cont, cont])]
)
def create_machine_adherent(self, cont, adherent=None):
"""
Permet d'ajouter une machine à un adhérent.
On affiche un menu pour choisir le type de machine (juste filaire et wifi pour le moment)
"""
if adherent is None:
adherent = self.select(["adherent"], "Recherche d'un adhérent pour lui ajouter une machine", cont=cont)
return self.create_machine_proprio(cont=cont, proprio=adherent)
def delete_machine(self, cont, machine=None):
"""Permet la suppression d'une machine de la base ldap"""
if machine is None:
machine = self.select(["machineFixe", "machineWifi", "machineCrans", "borneWifi"], "Recherche d'une machine pour supression", cont=cont)
def todo(machine):
if self.confirm_item(item=machine, title="Voulez vous vraiement supprimer la machine ?", defaultno=True):
with self.conn.search(dn=machine.dn, scope=0, mode='rw')[0] as machine:
machine.delete()
self.dialog.msgbox("La machine a bien été supprimée", timeout=self.timeout, title="Suppression d'une machine")
raise Continue(cont(machine=None))
else:
raise Continue(cont)
return self.handle_dialog_result(
code=self.dialog.DIALOG_OK,
output="",
cancel_cont=cont,
error_cont=cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [machine])]
)
def modif_adherent_blacklist(self, adherent, cont):
"""Raccourci vers edit_blacklist spécifique aux adherent"""
return self.edit_blacklist(obj=adherent, title="Éditions des blacklist de %s %s" % (adherent['prenom'][0], adherent['nom'][0]), update_obj='adherent', cont=cont)
def modif_adherent(self, cont, adherent=None, proprio=None, tag=None):
"""Menu d'édition d'un adhérent"""
if adherent is None:
adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont)
a = attributs
menu_droits = {
'Administratif' : [a.cableur, a.nounou],
'Personnel':[a.cableur, a.nounou, a.soi],
'Études':[a.nounou, a.soi, a.cableur],
'Chambre':[a.cableur, a.nounou],
'Compte':[a.cableur, a.nounou],
'GPGFingerprint' : [a.nounou, a.soi],
'Remarques' : [a.cableur, a.nounou],
'Droits':[a.nounou, a.bureau],
'Blackliste':[a.cableur, a.nounou],
'Vente':[a.cableur, a.nounou],
'Supprimer':[a.nounou, a.bureau],
}
menu = {
'Administratif' : {'text' : "Adhésion, carte étudiant, chartes", "callback":self.adherent_administratif},
'Personnel' : {'text' : "Nom, prénom, téléphone... (ajouter l'age ?)", 'callback':self.adherent_personnel},
'Études' : {'text' : "Étude en cours (perso, je pense que c'est à supprimer)", "callback":self.adherent_etudes},
'Chambre' : {'text' : 'Déménagement', "callback":self.adherent_chambre},
'Compte' : {'text' : "Gestion du compte crans", "adherent":"proprio", "callback":self.proprio_compte, 'help':"Création/Suppression/Activation/Désactivation du compte, gestion des alias mails crans du compte"},
'GPGFingerprint' : {'text':'Ajouter ou supprimer une empeinte GPG', 'attribut':attributs.gpgFingerprint},
'Remarques' : {'text':'Ajouter ou supprimer une remarque de la machine', 'attribut':attributs.info},
'Droits' : {'text':"Modifier les droits alloués à cet adhérent", "callback":self.adherent_droits},
'Blackliste' : {'text': 'Modifier les blacklist de la machine', 'callback':self.modif_adherent_blacklist},
'Vente' : {'text':"Chargement solde crans, vente de cable ou adaptateur ethernet ou autre", "adherent":"proprio", "callback":self.proprio_vente},
'Supprimer' : {'text':"Supprimer l'adhérent de la base de donnée", 'callback':self.delete_adherent},
}
menu_order = ['Administratif', 'Personnel', 'Études', 'Chambre', 'Compte']
menu_compte_crans = ['Droits']
medu_end = ['GPGFingerprint', 'Remarques', 'Blackliste', 'Vente', 'Supprimer']
if "cransAccount" in adherent['objectClass']:
menu_order.extend(menu_compte_crans)
menu_order.extend(medu_end)
def box(default_item=None):
choices = []
for key in menu_order:
if self.has_right(menu_droits[key], adherent):
choices.append((key, menu[key]['text'], menu[key].get('help', "")))
return self.dialog.menu(
"Que souhaitez vous modifier ?",
width=0,
height=0,
menu_height=0,
timeout=self.timeout,
item_help=1,
default_item=str(default_item),
title="Modification de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
scrollbar=True,
cancel_label="Retour",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=choices)
def todo(tag, menu, adherent, cont_ret):
if not tag in menu_order:
raise Continue(cont_ret)
else:
if 'callback' in menu[tag]:
raise Continue(TailCall(menu[tag]['callback'], cont=cont_ret, **{menu[tag].get('adherent', 'adherent'):adherent}))
elif 'attribut' in menu[tag]:
raise Continue(TailCall(self.modif_adherent_attributs, adherent=adherent, cont=cont_ret, attr=menu[tag]['attribut'].ldap_name))
else:
raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag)
(code, tag) = self.handle_dialog(cont, box, tag)
cont_ret = TailCall(self.modif_adherent, cont=cont, adherent=adherent, tag=tag)
return self.handle_dialog_result(
code=code,
output=tag,
cancel_cont=cont(proprio=adherent),
error_cont=cont_ret,
codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, cont_ret])]
)
def adherent_administratif(self, cont, adherent):
self.dialog.msgbox("todo", width=0, height=0)
return cont
def adherent_personnel(self, cont, adherent=None, fields_attrs={}, make_compte_crans=None, force_create=False):
"""
Permet d'éditer les nom, prénom et téléphone d'un adhérent, ou de créer un adhérent.
Il faut encore trouver un moyen de récupérer des valeurs pour les attributs mail et chbre
"""
a = attributs
# Quel sont les attributs ldap dont on veut afficher et
# la taille du champs d'édition correspondant
to_display = [(a.nom, 30), (a.prenom, 30), (a.tel, 30), (a.mail, 30)]
non_empty = [a.nom, a.prenom, a.tel]
input_type = {'default':0}
# Quel séparateur on utilise pour les champs multivalué
separateur = ' '
def box(make_compte_crans):
if force_create and adherent is None and fields_attrs and make_compte_crans is not None:
return (self.dialog.DIALOG_OK, [fields_attrs[a] for a,l in to_display], make_compte_crans)
if adherent:
attrs = dict((k,[str(a) for a in at]) for k,at in adherent.items())
if 'cransAccount' in adherent['objectClass']:
input_type[attributs.mail] = 2
to_display.append((attributs.mailExt, 30))
else:
attrs = {}
if make_compte_crans is None:
if self.dialog.yesno("Crééra-t-on un compte crans à l'utilisateur ?", timeout=self.timeout, title="Création d'un adhérent", width=50) == self.dialog.DIALOG_OK:
input_type[attributs.mail] = 2
make_compte_crans = True
to_display.append((attributs.mailExt, 30))
else:
make_compte_crans = False
fields = [(
"%s %s:" % (a.legend, '(optionnel) ' if a.optional else ''),
fields_attrs.get(a, separateur.join(attrs.get(a.ldap_name, [a.default] if a.default else []))),
l+1, l,
input_type.get(a, input_type['default'])
) for a,l in to_display]
(code, tags) = self.dialog.form(
text="",
timeout=self.timeout,
height=0, width=0, form_height=0,
fields=fields,
title="Création d'un adhérent" if adherent is None else "Édition des informations de %s %s" % (adherent['prenom'][0], adherent['nom'][0]),
backtitle="Gestion des adhérents du Crans")
return (code, tags, make_compte_crans)
def modif_adherent(adherent, attrs):
with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
for (key, values) in attrs.items():
adherent[key]=values
adherent.validate_changes()
adherent.save()
return adherent
def create_adherent(attrs, make_compte_crans, force_create, self_cont, cont):
if not force_create:
items = self.conn.search("(&(prenom=%s)(nom=%s))" % (attrs['prenom'], attrs['nom']))
if items:
newadherent = self.select_one(items, title="Choisir un adhérant existant", text="Des adhérent avec les même noms et prénoms existent déjà, en utiliser un ?\n(Annuler pour continuer la création)", cont=self_cont(make_compte_crans=make_compte_crans, force_create=True))
raise Continue(cont(adherent=newadherent))
with self.conn.newAdherent({}) as adherent:
delay={}
for (key, values) in attrs.items():
try:
adherent[key]=values
# En cas d'erreur, on a peut être besoin du compte crans
except ValueError:
delay[key]=values
print delay
# on récupère la chambre
adherent = self.adherent_chambre_campus(success_cont=None, cont=self_cont(make_compte_crans=make_compte_crans), adherent=adherent, create=True)
# Si c'est EXT, on demande une adresse complète
if 'EXT' in adherent['chbre']:
adherent = self.adherent_chambre_ext(keep_machine=True, keep_compte=True, success_cont=None, cont=self_cont(make_compte_crans=make_compte_crans), adherent=adherent, create=True)
# Si compte crans à créer, on le crée.
# On le met en dernier pour éviter de faire entrez plusieurs fois son mdp à l'adhérent
# en cas d'erreur de la part du cableur
if make_compte_crans:
adherent = self.proprio_compte_create(proprio=adherent, cont=self_cont(make_compte_crans=None, force_create=False, adherent=None), update_obj='adherent', return_obj=True)
# On réeaffecte les attributs de tout à l'heure
for (key, values) in delay.items():
adherent[key]=values
# On confirme la création
if self.confirm_item(adherent, title="Créer l'adhérent suivant ?"):
adherent.validate_changes()
adherent.create()
else:
adherent = None
return adherent
def todo(to_display, non_empty, tags, adherent, separateur, make_compte_crans, force_create, self_cont, cont):
attrs = {}
# On traite les valeurs reçues
for ((a,l),values) in zip(to_display, tags):
if not values and a in non_empty:
raise ValueError("%s ne devrait pas être vide" % a.legend)
values = unicode(values, 'utf-8')
# Si le champs n'est pas single value, on utilise separateur pour découper
# et on ne garde que les valeurs non vides
if not a.singlevalue:
values = [v for v in values.split(separateur) if v]
attrs[a.ldap_name]=values
if adherent:
adherent = modif_adherent(adherent, attrs)
else:
adherent = create_adherent(attrs, make_compte_crans, force_create, self_cont, cont)
raise Continue(cont(adherent=adherent))
(code, tags, make_compte_crans) = self.handle_dialog(cont, box, make_compte_crans)
# On prépare les fiels à afficher à l'utilisateur si une erreure à lieu
# pendant le traitement des donnée (on n'éfface pas ce qui a déjà été entré
# c'est au cableur de corriger ou d'annuler
fields_attrs = dict((a, values) for ((a,l),values) in zip(to_display, tags))
retry_cont = TailCall(self.adherent_personnel, adherent=adherent, cont=cont, fields_attrs=fields_attrs)
return self.handle_dialog_result(
code=code,
output=tags,
cancel_cont=cont,
error_cont=retry_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [to_display, non_empty, tags, adherent, separateur, make_compte_crans, force_create, retry_cont, cont])]
)
def adherent_etudes(self, adherent, cont):
"""Gestion des études de l'adhérent"""
self.dialog.msgbox("todo", width=0, height=0)
return cont
@tailcaller
def adherent_chambre_campus(self, success_cont, cont, adherent, create=False):
"""Permet de faire déménager d'adhérent sur le campus"""
def box():
return self.dialog.inputbox(
"chambre ?",
title="%s de %s %s" % ("Création" if create else "Déménagement", adherent['prenom'][0], adherent["nom"][0]),
cancel_label="Retour",
width=50, timeout=self.timeout,
)
def expulse_squatteur(adherent, chbre):
with self.conn.search(u"chbre=%s" % chbre, mode='rw')[0] as squatteur:
if self.confirm_item(
item=squatteur,
title="Chambre occupée",
defaultno=True,
text=u"L'adhérent ci-dessous occupé déjà la chambre %s :\n" % output,
text_bottom=u"\nPasser la chambre de cet adhérent en chambre inconnue ?"
):
squatteur['chbre']=u'????'
squatteur.save()
return True
else:
return False
def set_chambre(adherent, chbre):
try:
adherent['postalAddress']=[]
adherent['chbre']=unicode(output, 'utf-8')
except UniquenessError:
if expulse_squatteur(adherent, chbre):
# La chambre est maintenant normalement libre
adherent['chbre']=unicode(output, 'utf-8')
else:
raise Continue(self_cont)
return adherent
def todo(chbre, adherent, self_cont, success_cont):
if not output:
raise Continue(self_cont)
if output == "????":
raise ValueError("Chambre ???? invalide")
if create:
return set_chambre(adherent, chbre)
else:
with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
adherent = set_chambre(adherent, chbre)
adherent.save()
self.display_item(item=adherent, title="Adhérent déménagé dans la chambre %s" % output)
raise Continue(success_cont(adherent=adherent))
(code, output) = self.handle_dialog(cont, box)
self_cont = TailCall(self.adherent_chambre_campus, adherent=adherent, success_cont=success_cont, cont=cont)
return self.handle_dialog_result(
code=code,
output=output,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [output, adherent, self_cont, success_cont])]
)
@tailcaller
def adherent_chambre_ext(self, keep_machine, keep_compte, success_cont, cont, adherent, create=False):
"""Permet de faire déménager l'adhérent hors campus"""
if keep_machine and not keep_compte:
raise EnvironmentError("On ne devrait pas supprimer un compte crans et y laisser des machines")
elif keep_machine and keep_compte:
def box(values={}):
form = [("Adresse", 40), ("Compl. adr.", 40), ("Code postal", 7), ("Ville", 16)]
fields = [("%s :" % k, values.get(k, ""), l, 50) for k,l in form]
return self.dialog.form(
text="",
timeout=self.timeout,
height=0, width=0, form_height=0,
fields=fields,
title="Paramètres machine",
backtitle="Gestion des machines du Crans")
def todo(output, adherent, success_cont, cont):
if not create:
if self.dialog.yesno("changer l'adresse de l'adhérent pour %s ?" % ", ".join([o for o in output if o]),
title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
defaultno=True, timeout=self.timeout) == self.dialog.DIALOG_OK:
with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
adherent['postalAddress']=[unicode(pa, 'utf-8') for pa in output]
adherent['chbre']=u'EXT'
adherent.save()
self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines conservées")
raise Continue(success_cont(adherent=adherent))
else:
raise Continue(cont)
else:
adherent['postalAddress']=[unicode(pa, 'utf-8') for pa in output]
adherent['chbre']=u'EXT'
return adherent
elif not keep_machine and keep_compte:
if create:
raise EnvironmentError("On ne crée pas un adhérent en lui supprimant des machines")
def box(values={}):
return (self.dialog.DIALOG_OK, "")
def todo(output, adherent, success_cont, cont):
if self.confirm_item(
item=adherent,
text=u"Supprimer toutes les machines de l'adhérent ci-dessous ?\n\n",
text_bottom=u"\nLa chambre de l'adhérent passera de plus en EXT.",
title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
defaultno=True):
with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
for machine in adherent.machines():
with machine:
machine.delete()
adherent['chbre']=u'EXT'
adherent.save()
self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines supprimées")
raise Continue(success_cont(adherent=adherent))
else:
raise Continue(cont)
elif not keep_machine and not keep_compte:
if create:
raise EnvironmentError("On ne crée pas un adhérent en lui supprimant des machines et un compte")
def box(values={}):
return (self.dialog.DIALOG_OK, "")
def todo(output, adherent, success_cont, cont):
if adherent.get('solde', [0])[0] > 0:
self.dialog.msgbox("Solde de l'adhérent %s€ strictement positif, impossible de supprimer le compte\nRepasser le solde à 0€ pour supprimer le compte." % adherent.get('solde', [0])[0],
title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
width=50, timeout=self.timeout)
raise Continue(cont)
elif self.confirm_item(
item=adherent,
text=u"Supprimer toutes les machines et du compte crans de l'adhérent ci-dessous ?\n\n",
text_bottom=u"\nLa chambre de l'adhérent passera de plus en EXT.",
title=u"Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
defaultno=True):
with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
for machine in adherent.machines():
with machine:
machine.delete()
adherent['chbre']=u'EXT'
adherent.save()
# On supprime le compte crans (on essaye)
def post_deletion(proprio, cont):
if not "cransAccount" in proprio['objectClass']:
self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines et compte crans supprimées")
else:
self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines supprimées et compte crans concervé")
raise Continue(cont(adherent=proprio))
self.proprio_compte_delete(proprio=adherent, cont=TailCall(post_deletion, proprio=adherent, cont=success_cont(adherent=adherent)), force=True)
else:
raise Continue(cont)
else:
raise EnvironmentError("Impossible, on a fait tous les cas, python est buggué")
(code, output) = self.handle_dialog(cont, box)
self_cont = TailCall(self.adherent_chambre_ext, adherent=adherent, keep_machine=keep_machine, keep_compte=keep_compte, success_cont=success_cont, cont=cont)
return self.handle_dialog_result(
code=code,
output=output,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [output, adherent, success_cont, cont])]
)
def adherent_chambre(self, adherent, cont, default_item=None):
"""Permet de faire déménager d'adhérent"""
a = attributs
menu_droits = {
'0' : [a.cableur, a.nounou],
'1' : [a.cableur, a.nounou],
'2' : [a.cableur, a.nounou],
'3' : [a.cableur, a.nounou],
}
menu = {
"0": {'text':"Déménagement sur le campus", 'callback':self.adherent_chambre_campus, 'help': "Déménagement vers une chambre sur le campus, on ne demande que le bâtiment et la chambre"},
"1": {'text':"Déménagement à l'extérieur en conservant les machines", "callback": TailCall(self.adherent_chambre_ext, keep_machine=True, keep_compte=True), "help": "Pour concerver ses machines, il faut donner un adresse postale complète"},
"2": {'text':"Départ du campus en conservant son compte", "callback":TailCall(self.adherent_chambre_ext, keep_machine=False, keep_compte=True), "help":"Supprime les machines mais concerve le compte crans, l'adresse passe en EXT sans plus d'information"},
"3": {'text':"Départ du campus en supprimant son compte", "callback":TailCall(self.adherent_chambre_ext, keep_machine=False, keep_compte=False), "help":"Supprime les machines et le compte crans, l'adhérent reste dans la base de donnée, il est possible de mettre une redirection du mail crans vers une autre adresse dans bcfg2"},
}
menu_order = [str(i) for i in range(4)]
def box(default_item=None):
return self.dialog.menu(
"Quel est le type du déménagement ?",
width=0,
height=0,
menu_height=0,
timeout=self.timeout,
item_help=1,
default_item=str(default_item),
title="Déménagement de %s %s" % (adherent['prenom'][0], adherent["nom"][0]),
scrollbar=True,
cancel_label="Retour",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], adherent)])
def todo(tag, menu, adherent, self_cont, cont):
if not tag in menu_order:
raise Continue(self_cont)
else:
raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, success_cont=cont, adherent=adherent))
(code, tag) = self.handle_dialog(cont, box, default_item)
self_cont = TailCall(self.adherent_chambre, adherent=adherent, cont=cont, default_item=tag)
return self.handle_dialog_result(
code=code,
output=tag,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, adherent, self_cont, cont])]
)
@tailcaller
def proprio_compte_create(self, proprio, cont, warning=True, guess_login=True, guess_pass=0, return_obj=False, update_obj='proprio'):
"""Permet de créer un compte crans à un proprio (club ou adhérent)"""
def box_warning(warning, proprio, cont):
# Affiche-t-on le warning sur la consutation de l'adresse crans
if warning:
if self.dialog.yesno(
text="\Zr\Z1AVERTISSEMENT :\Zn \nL'adhérent devra impérativement consulter l'adresse mail associée\n\n\n\ZnContinuer ?",
title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]),
defaultno=True,
width=70,
colors=True, timeout=self.timeout) != self.dialog.DIALOG_OK:
raise Continue(cont)
def get_login(guess_login, guess_pass, proprio, self_cont, cont):
# Essaye-t-on de deviner le login à utiliser
if not guess_login:
(code, login) = self.dialog.inputbox(
text="Le login doit faire au maximum %s caractères\nIl ne doit pas être un pseudo ou prénom mais doit être relié au nom de famille\nSeuls les caractères alphabétiques et le trait d'union sont autorisés" % config.maxlen_login,
title="Choix du login pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]),
init=str(proprio['nom'][0]).lower(),
width=60,
height=10, timeout=self.timeout)
if code != self.dialog.DIALOG_OK:
raise Continue(cont)
else:
# Si oui, de quelle manière
if guess_pass == 0:
login = str(proprio['nom'][0])
elif guess_pass == 1 and proprio.get('prenom', [''])[0]:
login = "%s%s" % (str(proprio['prenom'][0])[0], proprio['nom'][0])
# Si toutes les manières ont échoués, la prochaine fois, ça on n'essaye pas de deviner
else:
raise Continue(self_cont(warning=False, guess_login=False, guess_pass=2))
return login
def create_compte(proprio, login, guess_login, self_cont, cont):
try:
proprio.compte(login=unicode(login, 'utf-8'))
except ValueError:
# Il y a eu une erreur, si on essaye de deviner, on essaye la manière suivante
if guess_login:
raise Continue(self_cont(warning=False, guess_login=True, guess_pass=guess_pass+1))
# Sinon on propage l'erreur pour l'afficher à l'utilisateur
else:
raise
self.dialog.msgbox(
text="Le compte ne sera créé que lors de l'enregistrement des données\n\nL'adresse mail de l'adhérent est : %s\nL'adhérent possède également l'alias :\n%s\n" % (proprio['mail'][0], proprio['canonicalAlias'][0]),
title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]),
width=75,
height=12, timeout=self.timeout,
)
return proprio
@tailcaller
def set_password(proprio, update_obj, cont):
if self.dialog.yesno("Attribuer un mot de passe maintenant ?",
title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]),
timeout=self.timeout
) == self.dialog.DIALOG_OK:
#return self.proprio_compte_password(proprio=proprio, return_obj=return_obj, cont=cont(**{update_obj:proprio}))
proprio = self.proprio_compte_password(proprio=proprio, return_obj=True, cont=TailCall(set_password, proprio, update_obj, cont))
if return_obj:
return proprio
else:
raise Continue(cont(**{update_obj:proprio}))
elif return_obj:
return proprio
else:
raise Continue(cont(**{update_obj:proprio}))
def todo(proprio, warning, guess_login, guess_pass, return_obj, self_cont, cont):
box_warning(warning, proprio, cont)
login = get_login(guess_login, guess_pass, proprio, self_cont, cont)
if return_obj:
proprio = create_compte(proprio, login, guess_login, self_cont, cont)
return set_password(proprio, update_obj, cont)
else:
with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio:
proprio = create_compte(proprio, login, guess_login, self_cont, cont)
if not self.confirm_item(item=proprio, title="Création du compte crans pour l'adhérent ?"):
raise Continue(cont)
else:
proprio.save()
self.dialog.msgbox(
text="Compte créé avec succès.",
title="Création du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]),
timeout=self.timeout
)
return set_password(proprio, update_obj, cont)
self_cont = TailCall(self.proprio_compte_create, proprio=proprio, cont=cont, warning=warning, guess_login=guess_login, guess_pass=guess_pass)
return self.handle_dialog_result(
code=self.dialog.DIALOG_OK,
output="",
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [proprio, warning, guess_login, guess_pass, return_obj, self_cont, cont])]
)
@tailcaller
def proprio_compte_password(self, proprio, cont, return_obj=False):
"""Permet de changer le mot de passe d'un compte crans"""
def test_password(password, self_cont):
(good, msg) = check_password(password, dialog=True)
if not good:
self.dialog.msgbox(
msg,
title="Erreur dans le mot de passe de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]),
colors=True,
width=70, timeout=self.timeout)
raise Continue(self_cont)
else:
return True
def todo(passwords, proprio, return_obj, self_cont, cont):
password = self.get_password(cont=cont,
title="Choix du mot de passe pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]),
backtitle="Le mot de passe doit être assez difficile")
if test_password(password, self_cont):
if return_obj:
proprio['userPassword']=unicode(lc_utils.hash_password(password))
return proprio
else:
with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio:
proprio['userPassword']=unicode(lc_utils.hash_password(password))
proprio.save()
self.dialog.msgbox(
"Mot de passe changé avec succès",
title="Choix du mot de passe pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]),
width=70, timeout=self.timeout
)
raise Continue(cont(proprio=proprio))
#(code, passwords) = self.handle_dialog(cont, box)
(code, passwords) = (self.dialog.DIALOG_OK, "")
self_cont = TailCall(self.proprio_compte_password, proprio=proprio, cont=cont)
return self.handle_dialog_result(
code=code,
output=passwords,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [passwords, proprio, return_obj, self_cont, cont])]
)
@tailcaller
def proprio_compte_delete(self, proprio, cont, force=False):
"""Permet la suppression du compte crans d'un proprio"""
def todo(proprio, self_cont, cont):
if force or self.confirm_item(item=proprio, title="Voulez vous vraiement supprimer le compte de %s %s ?" % (proprio.get('prenom', [''])[0], proprio["nom"][0]), defaultno=True):
(code, mail) = self.dialog.inputbox(
text="Il faut choisir une nouvelle adresse de contact.\n(On regarde s'il y a une adresse optionnel)",
title="Choix d'une adresse de contact pour %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]),
init=str(proprio.get("mailExt", [""])[0]),
width=50, timeout=self.timeout)
if not code == self.dialog.DIALOG_OK:
raise Continue(cont)
elif not mail:
raise ValueError("Il faut entrer une adresse mail")
with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio:
proprio.delete_compte(unicode(mail, 'utf-8'))
proprio.save()
self.dialog.msgbox("Le compte a bien été supprimée", timeout=self.timeout, title="Suppression du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]))
raise Continue(cont(proprio=proprio))
else:
raise Continue(cont)
self_cont = TailCall(self.proprio_compte_delete, proprio=proprio, cont=cont, force=force)
return self.handle_dialog_result(
code=self.dialog.DIALOG_OK,
output="",
cancel_cont=cont(proprio=proprio),
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [proprio, self_cont, cont])]
)
def proprio_compte_etat(self, proprio, disable, cont):
"""Permet de d'éastiver ou activer un compte crans avec l'attribut shadowExpire"""
with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio:
if disable:
proprio["shadowExpire"]=0
else:
proprio["shadowExpire"]=[]
proprio.save()
raise Continue(cont(proprio=proprio))
def proprio_compte_shell(self, proprio, cont, choices_values=None):
"""Permet de modifier le shell d'un compte crans"""
a = attributs
# Seul les nounous peuvent changer les shells restrictifs
shells_droits = {
'default' : [a.soi, a.nounou, a.cableur],
'rbash' : [a.nounou],
'rssh' : [a.nounou],
'badPassSh' : [a.nounou],
'disconnect_shell':[a.nounou],
}
shell = os.path.basename(str(proprio['loginShell'][0])).lower()
shells = config.shells_gest_crans
shells_order = config.shells_gest_crans_order
def box():
# liste des shell éditables par l'utilisateur
editable_sheels = [s for s in shells_order if self.has_right(shells_droits.get(s, shells_droits['default']), proprio)]
# Si l'utilisateur de gest_crans peut éditer le shell courant
# il peut le changer pour un autre shell qu'il peut éditer
if shell in editable_sheels:
choices=[(s, shells[s]['desc'], 1 if s == shell else 0) for s in editable_sheels]
return self.dialog.radiolist(
text="",
height=0, width=0, list_height=0,
choices=choices_values if choices_values else choices,
title="Shell de %s %s" % (proprio.get('prenom', [""])[0], proprio['nom'][0]),
timeout=self.timeout
)
# Sinon, on affiche un message d'erreur et on annule
else:
self.dialog.msgbox("Vous ne pouvez pas changer le shell de cet utilisateur",
title="Édition du shell impossible", timeout=self.timeout, width=0, height=0)
return (self.dialog.DIALOG_CANCEL, None)
def todo(output, shell, shells, proprio, self_cont, cont):
loginShell = shells[output]['path']
if shell and shell != output:
with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio:
proprio['loginShell']=unicode(loginShell)
proprio.save()
self.dialog.msgbox("Shell modifié avec succès.\nLa modification peut prendre une quainzaine de minute avant d'être effective.",
title="Shell de %s %s" % (proprio.get('prenom', [""])[0], proprio['nom'][0]),
width=50, timeout=self.timeout,
)
raise Continue(cont(proprio=proprio))
(code, output) = self.handle_dialog(cont, box)
self_cont = TailCall(self.proprio_compte_shell, proprio=proprio, cont=cont, choices_values=[(s, shells[s]['desc'], 1 if s == output else 0) for s in shells_order])
return self.handle_dialog_result(
code=code,
output=output,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [output, shell, shells, proprio, self_cont, cont])]
)
def proprio_compte(self, proprio, cont, default_item=None):
"""Menu de gestion du compte crans d'un proprio"""
has_compte = 'cransAccount' in proprio['objectClass']
disabled_compte = has_compte and 0 in proprio['shadowExpire']
a = attributs
menu_droits = {
"Password": [a.cableur, a.nounou],
'MailAlias': [a.cableur, a.nounou],
"Activer" : [a.nounou],
"Désactiver" : [a.nounou],
"Créer" : [a.cableur, a.nounou],
"Supprimer" : [a.cableur, a.nounou],
"Shell" : [a.nounou, a.soi, a.cableur],
}
menu = {
"Password" : {"text":"Changer le mot de passe du compte", "help":"", "callback":self.proprio_compte_password},
'MailAlias' : {'text': 'Créer ou supprimer des alias mail', "help":"", 'attribut':attributs.mailAlias},
"Shell" : {"text" : "Changer le shell de cet utilisateur", "help":'', "callback":self.proprio_compte_shell},
"Activer" : {"text" : "Activer le compte pour la connexion mail/serveur", "help":"Permet d'autoriser les connexions smtp, imap, ssh, etc… avec le compte", "callback":TailCall(self.proprio_compte_etat, disable=False)},
"Désactiver" : {"text" : "Désactiver le compte pour la connexion mail/serveur", "help":"Permet d'interdire les connexions smtp, imap, ssh, etc… avec le compte", "callback":TailCall(self.proprio_compte_etat, disable=True)},
"Créer" : {"text": "Créer un compte", "help":'', "callback":self.proprio_compte_create},
"Supprimer" : {"text": "Supprimer le compte", "help":"Le home sera archivé dans le cimetière", "callback":self.proprio_compte_delete},
}
menu_order = []
tag_translate = {
"Créer":"Password",
"Password":"Password",
"Supprimer":"Créer",
"Activer":"Désactiver",
"Désactiver":"Activer",
"Shell":"Shell",
'MailAlias':'MailAlias',
'':'',
}
if has_compte:
if disabled_compte:
menu_order.append("Activer")
else:
menu_order.append("Désactiver")
menu_order.extend(['MailAlias', "Shell", "Password", "Supprimer"])
else:
menu_order.append("Créer")
def box(default_item=None):
return self.dialog.menu(
"Quel action effectuer sur le compte ?",
width=0,
height=0,
menu_height=0,
timeout=self.timeout,
item_help=1,
default_item=str(default_item),
title="Gestion du compte de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]),
scrollbar=True,
cancel_label="Retour",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], proprio)])
def todo(tag, menu, proprio, self_cont):
if not tag in menu_order:
raise Continue(self_cont)
elif 'callback' in menu[tag]:
raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, proprio=proprio))
elif 'attribut' in menu[tag]:
raise Continue(TailCall(self.modif_proprio_attributs, proprio=proprio, cont=self_cont, attr=menu[tag]['attribut'].ldap_name))
else:
raise EnvironmentError("Il n'y a ni champ 'attribut' ni 'callback' pour le tag %s" % tag)
(code, tag) = self.handle_dialog(cont, box, default_item)
self_cont = TailCall(self.proprio_compte, proprio=proprio, cont=cont, default_item=tag_translate.get(tag, tag))
return self.handle_dialog_result(
code=code,
output=tag,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [tag, menu, proprio, self_cont])]
)
def adherent_droits(self, adherent, cont, choices_values=None):
"""Gestion des droits d'un adhérent"""
def box():
return self.dialog.checklist(
text="",
height=0, width=0, list_height=0,
choices=choices_values if choices_values else [(droit, "", 1 if droit in adherent["droits"] else 0) for droit in attributs.TOUS_DROITS],
title="Droits de %s %s" % (adherent['prenom'][0], adherent['nom'][0]),
timeout=self.timeout
)
def todo(droits, adherent, self_cont, cont):
# Les vérifications de sécurité sont faites dans lc_ldap
with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
adherent['droits']=[unicode(d) for d in droits]
adherent.save()
if adherent["uid"] and adherent["uid"][0] == self.conn.current_login:
self.check_ldap()
raise Continue(cont(adherent=adherent))
(code, droits) = self.handle_dialog(cont, box)
self_cont = TailCall(self.adherent_droits, adherent=adherent, cont=cont, choices_values=[(d, "", 1 if d in droits else 0) for d in attributs.TOUS_DROITS])
return self.handle_dialog_result(
code=code,
output=droits,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [droits, adherent, self_cont, cont])]
)
@tailcaller
def proprio_vente_set(self, article, cont):
"""Permet de définir la quantité de l'article à vendre"""
def box():
if article['pu'] == '*':
return self.dialog.inputbox(title="Montant pour %s ?" % article['designation'],
text="", init=str(article.get('nombre','')), timeout=self.timeout, width=70)
else:
return self.dialog.inputbox(title="Nombre de %s ?" % article['designation'],
text="", timeout=self.timeout, init=str(article.get('nombre','1')), width=70)
def todo(article, output, cont):
article['nombre']=output
# Il faut entrer quelque chose
if not output:
raise ValueError("Merci d'entrer une valeur")
# Vérification des centimes
if article['pu'] == '*' and '.' in output:
if len(output.split('.', 1)[1])>2:
raise ValueError("Les centimes, c'est seulement deux chiffres après la virgule")
typ = float if article['pu'] == '*' else int
# Vérification du type de l'entré
try:
output=typ(output)
except ValueError:
raise ValueError("Merci d'entrez seulement des nombres")
# On définis le nombre d'entrée. Pour pu=* il y aura du trairement à faire
# avant de générer la facture : mettre pu à nombre et nombre à 1
# on le met comme ça pour pouvoir naviger aisément entre les écrans dialog
article['nombre'] = output
return article
(code, output) = self.handle_dialog(cont, box)
self_cont = TailCall(self.proprio_vente_set, article=article, cont=cont)
return self.handle_dialog_result(
code=code,
output=output,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], todo, [article, output, cont])]
)
@tailcaller
def proprio_vente(self, proprio, cont, tags=[], tag_paiment=None, to_set=[], have_set=[]):
"""Menu de vente du crans. Permet également de recharger le solde crans"""
box_paiement = {
"liquide" : "Espèces",
"cheque" : "Chèque",
"solde" : "Solde Crans (actuel : %s€)",
}
def box_choose_item(tags):
choices = []
for code, article in gestion.config.factures.items.items():
choices.append((code, u"%s%s" % (article['designation'], (u' (%s€)' % article['pu']) if article['pu'] != '*' else ""), 1 if code in tags else 0))
return self.dialog.checklist(
text="",
title="Vente de truc à %s %s" % (proprio.get("prenom", [''])[0], proprio["nom"][0]),
choices=choices,
timeout=self.timeout)
def box_choose_paiment(tag, articles):
box_paiement_order = ["liquide", "cheque"]
if "cransAccount" in proprio['objectClass']:
if not "SOLDE" in [art['code'] for art in articles]:
box_paiement_order.append("solde")
box_paiement["solde"] = box_paiement["solde"] % proprio["solde"][0]
choices = []
for key in box_paiement_order:
choices.append((key, box_paiement[key], 1 if key == tag else 0))
return self.dialog.radiolist(
text="",
title="Choix d'un mode de paiement pour %s %s" % (proprio.get("prenom", [''])[0], proprio["nom"][0]),
choices=choices,
timeout=self.timeout)
def choose_item(proprio, tags, articles, self_cont):
to_set=[]
for tag in tags:
articles[tag]['code']=tag
to_set.append(articles[tag])
raise Continue(self_cont(to_set=to_set, have_set=[]))
def number_of_items(to_set, have_set, self_cont):
# Où faut-il aller si l'utilisateur appuis sur annuler
if not have_set:
lcont = self_cont(to_set=[])
else:
lcont = self_cont(to_set=[have_set[-1]] + to_set, have_set=have_set[:-1])
art = self.proprio_vente_set(to_set[0], cont=lcont)
if not to_set[1:]:
total = 0
line=1
text=u"Résumé :\n"
for article in have_set + [art]:
if article['pu'] == '*':
total += article['nombre']
text+=u" * %s pour %s\n" % (article['designation'], article['nombre'])
else:
total += article['nombre']*article['pu']
text+=u" * %dx %s à %s\n" % (article['nombre'], article['designation'], article['pu'])
line+=1
text+=u"Total à payer : %s" % total
self.dialog.msgbox(text=text,
title="Résumé de la facture à payer",
width=70, height=5+line, timeout=self.timeout)
return self_cont(to_set=to_set[1:], have_set=have_set + [art])
def choose_paiment(have_set, tag, proprio, lcont, self_cont, cont):
if not tag:
raise ValueError("Il faut choisir un moyen de paiement")
code, comment = self.dialog.inputbox(text="Détail pour les espèce, nom de la note ou banque du chèque", title="Commentaire", width=70, timeout=self.timeout)
if code != self.dialog.DIALOG_OK:
raise Continue(self_cont)
if not comment:
raise ValueError("Commentaire nécessaire")
articles = copy.deepcopy(have_set)
for article in articles:
if article['pu'] == '*':
article['pu'] = article['nombre']
article['nombre'] = 1
with self.conn.newFacture(proprio.dn, {}) as facture:
facture['modePaiement']=unicode(tag, 'utf-8')
facture['article']=articles
facture['info']=unicode(comment, 'utf-8')
if self.confirm_item(item=facture,
text=u"Le paiement de %s€ a-t-il bien été reçu (mode : %s) ?\n" % (facture.total(), tag),
title=u"Validation du paiement",
timeout=self.timeout):
# Appeler créditer va créditer ou débiter le solde, sauver le proprio et créer la facture
facture.crediter()
arts = ["%s %s" % (art['nombre'], art['designation']) for art in facture['article'] if art['code'] != 'SOLDE']
if arts:
self.dialog.msgbox(
text=u"Vous pouvez remettre à l'adherent les articles (si se sont des articles) suivant :\n * %s" % '\n * '.join(arts),
title=u"Vente terminée",
width=0, height=0, timeout=self.timeout)
if tag == "solde":
self.dialog.msgbox(text=u"Le solde de l'adhérent à bien été débité", title="Solde débité", width=0, height=0, timeout=self.timeout)
if [a for a in facture['article'] if art['code'] == 'SOLDE']:
self.dialog.msgbox(text=u"Le solde de l'adhérent à bien été crédité", title="Solde crédité", width=0, height=0, timeout=self.timeout)
else:
self.dialog.msgbox(text=u"Le paiement n'ayant pas été reçue\nla vente est annulée", title="Annulation de la vente", width=0, height=0, timeout=self.timeout)
raise Continue(cont)
self_cont=TailCall(self.proprio_vente, proprio=proprio, cont=cont, tags=tags, tag_paiment=tag_paiment, to_set=to_set, have_set=have_set)
# S'il y a des article dont il faut définir la quantité
if to_set:
return self.handle_dialog_result(
code=self.dialog.DIALOG_OK,
output=None,
cancel_cont=None,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], number_of_items, [to_set, have_set, self_cont])]
)
# Sinon, si tous les quantités de tous les articles sont définis
elif have_set:
lcont = self_cont.copy()
lcont(to_set=[have_set[-1]] + to_set, have_set=have_set[:-1])
(code, tag) = self.handle_dialog(lcont, box_choose_paiment, tag_paiment, have_set)
self_cont=self_cont(tag_paiment=tag)
lcont(tag_paiment=tag)
return self.handle_dialog_result(
code=code,
output=tag,
cancel_cont=lcont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], choose_paiment, [have_set, tag, proprio, lcont, self_cont, cont])]
)
# Sinon, on propose des articles à chosir
else:
(code, tags) = self.handle_dialog(cont, box_choose_item, tags)
self_cont=self_cont(tags=tags, have_set=[], to_set=[], tag_paiment=None)
return self.handle_dialog_result(
code=code,
output=tags,
cancel_cont=cont,
error_cont=self_cont,
codes_todo=[([self.dialog.DIALOG_OK], choose_item, [proprio, tags, copy.deepcopy(gestion.config.factures.items), self_cont])]
)
def create_adherent(self, cont):
"""Crée un adhérent et potentiellement son compte crans avec lui"""
def mycont(adherent=None, **kwargs):
if adherent:
raise Continue(TailCall(self.modif_adherent, cont=cont, adherent=adherent))
else:
raise Continue(cont)
return self.adherent_personnel(cont=TailCall(mycont))
def delete_adherent(self, cont, adherent=None):
"""Permet la suppression d'un adhérent de la base ldap"""
if adherent is None:
adherent = self.select(["adherent"], "Recherche d'un adhérent pour supression", cont=cont)
def todo(adherent):
if self.confirm_item(item=adherent, title="Voulez vous vraiement supprimer l'adhérent ?", defaultno=True):
with self.conn.search(dn=adherent.dn, scope=0, mode='rw')[0] as adherent:
adherent.delete()
self.dialog.msgbox("L'adherent a bien été supprimée", timeout=self.timeout, title="Suppression d'un adherent")
raise Continue(cont(proprio=None))
else:
raise Continue(cont)
return self.handle_dialog_result(
code=self.dialog.DIALOG_OK,
output="",
cancel_cont=cont(proprio=adherent),
error_cont=cont(proprio=adherent),
codes_todo=[([self.dialog.DIALOG_OK], todo, [adherent])]
)
def create_club(self, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
def delete_club(self, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
def modif_club(self, cont, club=None):
if club is None:
club = self.select(["club"], "Recherche d'un club pour modification", disable_field=["Prénom", "Téléphone"], cont=cont)
self.dialog.msgbox("todo", width=0, height=0)
return cont(proprio=club)
def create_machine_club(self, cont, club=None):
"""
Permet d'ajouter une machine à un club.
On affiche un menu pour choisir le type de machine (juste filaire et wifi pour le moment)
"""
if club is None:
club = self.select(["club"], "Recherche d'un club pour lui ajouter une machine", cont=cont)
return self.create_machine_proprio(cont=cont, proprio=club)
def create_machine_crans(self, cont):
"""Permet l'ajout d'une machine à l'association"""
associationCrans = self.conn.search(dn="ou=data,dc=crans,dc=org", scope=0)[0]
return self.create_machine_proprio(cont=cont, proprio=associationCrans)
def has_right(self, list, obj=None):
"""Vérifie que l'un des droits de l'utilisateur courant est inclus dans list"""
if obj:
droits = obj.rights()
else:
droits = self.conn.droits
for d in list:
if d in droits:
return True
return False
@tailcaller
def menu_principal(self, tag=None, machine=None, proprio=None):
"""Menu principal de l'application affiché au lancement"""
a = attributs
menu_droits = {
'default' : [a.cableur, a.nounou],
'aKM' : [a.nounou],
}
menu = {
'aA' : {'text':"Inscrire un nouvel adhérent", 'callback': self.create_adherent,},
'mA' : {'text':"Modifier l'inscription d'un adhérent", 'callback': self.modif_adherent, 'help':"Changer la chambre, la remarque, la section, la carte d'étudiant ou précâbler."},
'aMA': {'text':"Ajouter une machine à un adhérent", 'callback': self.create_machine_adherent},
#'dA' : {'text':"Détruire un adhérent", 'callback': self.delete_adherent, 'help':"Suppression de l'adhérent ainsi que de ses machines"},
'mM' : {'text':"Modifier une machine existante", 'callback': self.modif_machine, 'help':"Changer le nom ou la MAC d'une machine."},
#'dM' : {'text':"Détruire une machine", 'callback': self.delete_machine},
'aC' : {'text':"Inscrire un nouveau club", 'callback': self.create_club},
'mC' : {'text':"Modifier un club", 'callback': self.modif_club},
'aMC': {'text':"Ajouter une machine à un club", 'callback': self.create_machine_club},
#'dC' : {'text':"Détruire un club", 'callback': self.delete_club},
'aKM': {'text':"Ajouter une machine à l'association", 'callback': self.create_machine_crans},
'' : {'text':"---------------------------------------",'callback': None},
}
### Les clef qui n'existe pas sont toute renvoyé sur la clef ''
#menu_order = ["aA", "mA", "aMA", "dA", "", "mM", "dM", " ", "aC", "mC", "aMC", "dC", " ", "aKM"]
#menu_order = ["aA", "mA", "aMA", "", "mM", " ", "aC", "mC", "aMC", " ", "aKM"]
menu_order = ["aA", "mA", "aMA", "", "mM", "", "aC", "mC", "aMC", "", "aKM"]
if machine and not proprio:
proprio = machine.proprio()
if isinstance(proprio, objets.AssociationCrans):
proprio = None
if machine or proprio:
menu_order = [''] + menu_order
if machine:
menu_machine = {
'mMc' : {
'text':"Modifier la machine %s" % machine['host'][0],
'callback': TailCall(self.modif_machine, machine=machine),
'help':"Changer le nom ou la MAC d'une machine."
},
}
menu_machine_order = ['mMc']
menu.update(menu_machine)
menu_order = menu_machine_order + menu_order
if proprio:
menu_adherent = {
'mAc' : {
'text':"Modifier l'inscription de %s" % proprio.get("cn", proprio["nom"])[0],
'callback': TailCall(self.modif_adherent, adherent=proprio)
},
'aMc' : {
'text':"Ajouter une machine à %s" % proprio.get("cn", proprio["nom"])[0],
'callback': TailCall(self.create_machine_adherent, adherent=proprio)
},
}
menu_club = {
'mCc' : {
'text':"Modifier l'inscription de %s" % proprio.get("cn", proprio["nom"])[0],
'callback': TailCall(self.modif_club, club=proprio)
},
'aMc' : {
'text':"Ajouter une machine à %s" % proprio.get("cn", proprio["nom"])[0],
'callback': TailCall(self.create_machine_club, club=proprio)
},
}
if 'adherent' in proprio['objectClass']:
menu_proprio = menu_adherent
menu_proprio_order = ['mAc', 'aMc']
elif 'club' in proprio['objectClass']:
menu_proprio = menu_club
menu_proprio_order = ['mCc', 'aMc']
else:
raise EnvironmentError("Je ne connais que des adherents et des club comme proprio")
menu.update(menu_proprio)
menu_order = menu_proprio_order + menu_order
def box(default_item=None):
choices = []
for key in menu_order:
if self.has_right(menu_droits.get(key, menu_droits['default'])):
choices.append((key, menu[key]['text'], menu[key].get('help', "")))
while choices[-1][0] == '':
choices=choices[:-1]
while choices[0][0] == '':
choices=choices[1:]
return self.dialog.menu(
"Que souhaitez vous faire ?",
width=0,
height=0,
menu_height=0,
item_help=1,
default_item=str(default_item),
title="Menu principal",
scrollbar=True,
timeout=self.timeout,
cancel_label="Quitter",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=choices)
(code, tag) = self.handle_dialog(TailCall(handle_exit_code, self.dialog, self.dialog.DIALOG_ESC), box, tag)
self_cont = TailCall(self.menu_principal, tag=tag, proprio=proprio, machine=machine)
callback = menu.get(tag, menu[''])['callback']
if handle_exit_code(self.dialog, code) and callback:
return TailCall(callback, cont=TailCall(self.menu_principal, tag=tag, machine=machine, proprio=proprio))
else:
return self_cont
@TailCaller
def main(gc, cont=None):
"""
Fonction principale gérant l'appel au menu principal,
le timeout des écrans dialog et les reconnexion a ldap en cas de perte de la connexion
"""
while True:
try:
# tant que le timeout est atteint on revient au menu principal
gc.menu_principal()
except DialogError as e:
# Si l'erreur n'est pas due à un timeout, on la propage
if time.time() - gc.dialog_last_access < gc.timeout:
raise
# Si on perd la connexion à ldap, on en ouvre une nouvelle
except ldap.SERVER_DOWN:
if gc.dialog.pause(title="Erreur", duration=10, height=9, width=50, text="La connection au serveur ldap à été perdue.\nTentative de reconnexion en cours…") == gc.dialog.DIALOG_OK:
gc.check_ldap()
else:
return
if __name__ == '__main__':
main(GestCrans())
os.system('clear')