scripts/gestion/gest_crans_lc.py
2014-03-13 09:54:03 +01:00

1289 lines
65 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 time
import ldap
import signal
import inspect
import tempfile
import collections
sys.path.append('/usr/scripts/')
from pythondialog import Dialog
from gestion.affich_tools import get_screen_size, coul
import gestion.config as config
import lc_ldap.shortcuts
import lc_ldap.objets
import lc_ldap.attributs as attributs
import lc_ldap.printing as printing
debugf=None
debug_enable = False
def mydebug(txt):
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 __call__(self, *args, **kwargs):
self.kwargs.update(kwargs)
self.args = self.args + args
return self
def handle(self) :
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 l'exception de type ValueError"""
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 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
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):
if not debug_enable:
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)
# 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
self.dialog = Dialog()
@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,
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)
(code, tag) = box()
retry_cont = TailCall(self.edit_blacklist_select, obj=obj, title=title, cont=cont)
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont)
elif code in [self.dialog.DIALOG_OK]:
if tag == 'new':
return tag, {'debut':0, 'fin':0, 'type':'', 'comm':''}
else:
try:
bl = {}
bl.update(obj['blacklist'][int(tag)].value)
return tag, bl
except Exception as e:
self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10)
raise Continue(retry_cont(values=values))
else:
self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10)
raise Continue(retry_cont)
@tailcaller
def edit_blacklist_type(self, title, cont):
"""Permet de choisir un type de blackliste pour les nouvelles blacklistes"""
def box():
return self.dialog.menu(
"Choisissez un type de blacklist",
width=0,
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=[(k,v) for (k,v) in config.blacklist_items.items()])
(code, tag) = box()
retry_cont = TailCall(self.edit_blacklist_type, title=title, cont=cont)
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont)
elif code in [self.dialog.DIALOG_OK]:
if tag in config.blacklist_items:
return tag
else:
self.dialog.msgbox("%s n'est pas une blacklist" % tag, title="Erreur rencontrée", width=73, height=10)
raise Continue(retry_cont(values=values))
else:
self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10)
raise Continue(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, 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, 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)
if bl_type is None and tag == 'new':
bl['type'] = self.edit_blacklist_type(title, self_cont)
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) == 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(cont(**{update_obj:obj}))
# On propage les Continue
except Continue:
raise
# En cas d'une autre erreur, on l'affiche et on retourne
except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e:
self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73)
raise Continue(self_cont)
else:
raise Continue(self_cont(bl=None))
# 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))
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 ?", 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(cont(**{update_obj:obj}))
# On propage les Continue
except Continue:
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("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73)
raise Continue(self_cont)
else:
raise Continue(self_cont(bl=None))
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,
list_height=7,
choices=choices,
title=title)
# 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)
# 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(cont)
# Sinon, c'est OK
elif code in [self.dialog.DIALOG_OK]:
# 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)
try:
# 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 propage les Continue
except Continue:
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("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10)
raise Continue(retry_cont(values=values))
else:
self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10)
raise Continue(retry_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,), title=title, default_item=str(default_tag))
if code == self.dialog.DIALOG_EXTRA:
output = output.split(' ', 2)[1:]
else:
output = ''
return (code, output)
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) = box(values, tag)
# Si on a fait annulé ou appuyé sur ESC
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont)
# Si on a éditer ou ajouté une valeur de/dans la liste de l'attribut
elif code in [self.dialog.DIALOG_EXTRA]:
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))
# Si on valide les changement affectué, on sauvegarde les nouvelles valeurs dans l'objet
elif code in [self.dialog.DIALOG_OK]:
try:
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}))
except Continue:
raise
except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e:
self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10)
raise Continue(retry_cont)
# Si dialog retourne un code inconnu, on le signale
else:
self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10)
raise Continue(retry_cont)
def search(self, objectClassS, title, values={}, cont=None):
"""
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]},
'Prenom' : {'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, 20, 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', 'Prenom', 'Téléphone', 'Chambre', 'aid', 'mail']
select_machine = ['Host', 'Mac', 'IP', 'mid']
def box():
# On met les argument à dialog à la main ici, sinon, c'est difficile de choisir comment mettre une seconde colone
cmd = ["--form", "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']])
cmd.extend(['Machine :'] + [str(e) for e in select_dict['Machine']['params']])
for key in select_machine:
cmd.extend(['%s :' % key] + [str(e) for e in select_dict[key]['params']])
cmd.extend(["Les champs vides sont ignorés.", '7', '1', "", '0', '0', '0', '0'])
# On utilise quand même la fonction de la bibliothèques pour passer les arguments
(code, output) = self.dialog._perform(*(cmd,), 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) = 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'] 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'] 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'] ]
def select_one(self, items, title, default_item=None, cont=None):
"""Fait selectionner un item parmis une liste d'items à l'utisateur"""
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]
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 olist.keys():
items.extend(olist[c])
items_s = printing.sprint_list(olist[c], col-20).encode('utf-8').split('\n')
choices.append(("", str(items_s[0])))
choices.append(("", str(items_s[1])))
for i in items_s[2:]:
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])
(code, tag) = self.dialog.menu(
"Que souhaitez vous faire ?",
width=0,
height=0,
menu_height=0,
item_help=0,
default_item=str(default_tag),
title=title,
scrollbar=True,
choices=choices,
colors=True)
# Si l'utilisateur à annulé, on coninue avec la continuation
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont)
# Si l'utilisateur n'a pas choisis une ligne correspondant à quelque chose
elif not tag:
self.dialog.msgbox("Merci de choisir l'un des item de la liste ou d'annuler", title="Sélection", width=0, height=0)
raise Continue(TailCall(self.select_one, items=items, title=title, default_item=default_item, cont=cont))
# Sinon on retourne l'item choisis
elif self.confirm_item(items[int(tag)], title):
return items[int(tag)]
else:
raise Continue(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, init=init)
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont)
elif force and not output:
self.dialog.msgbox("Entrée vide, merci d'indiquer quelque chose")
raise Continue(TailCall(self.get_comment, title=title, text=text, cont=cont, force=force))
else:
return unicode(output, 'utf-8')
def confirm_item(self, item, title, defaultno=False, **params):
"""Affiche un item et demande si c'est bien celui là que l'on veux (supprimer, éditer, créer,...)"""
return self.dialog.yesno(
printing.sprint(item, **params),
no_collapse=True,
colors=True,
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,
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):
"""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)
# S'il n'y a pas de résultas, on recommence
if not items:
self.dialog.msgbox("Aucun Résultat", title="Recherche", width=0, height=0)
raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, 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, 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, cont=cont))
except Continue:
raise
except Exception as e:
self.dialog.msgbox("%r" % e, title="Erreur rencontrée", width=0, height=0)
raise Continue(TailCall(self.select, objectClassS=objectClassS, title=title, values=values, cont=cont))
def machine_information(self, cont, machine=None, objectClass=None, parent=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 parent, objectClass et realm.
Si on ne fait qu'éditer une machine, parent, 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 = ' '
if machine:
objectClass = machine["objectClass"][0]
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]
(code, tags) = self.dialog.form(
text="",
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")
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont)
# 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 = []
for ((a,l),values) in zip(to_display, tags):
fields_values.append(("%s :" % a.legend, values, l))
try:
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 quelqes vérification de syntaxe
if a.ldap_name == 'host':
# Si c'est une machine wifi, host doit finir par wifi.crans.org
if "machineWifi" == objectClass:
hostend = ".wifi.crans.org"
# Si c'est une machine wifi, host doit finir par crans.org
elif "machineFixe" == objectClass:
hostend = ".crans.org"
# 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 values.endswith(hostend) and not '.' in values:
values = "%s.wifi.crans.org" % values
elif values.endswith(hostend) and '.' in values[:-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 values.endswith(hostend) and '.' in values:
raise ValueError("Nom d'hôte invalide, devrait finir par %s et être sans point dans la première partie" % hostend)
attrs[a.ldap_name]=values
if machine:
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()
else:
if parent is None or realm is None:
raise EnvironmentError("On essaye de créer une machine mais parent ou realm vaut None")
ldif = {
'macAddress': ['%s' % attrs['macAddress'].encode('utf-8')],
'host': ['%s' % attrs['host'].encode('utf-8')]
}
with self.conn.newMachine(parent.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)
else:
raise Continue(cont)
return cont(machine=machine)
except (Exception, ldap.OBJECT_CLASS_VIOLATION) as e:
self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10)
raise Continue(TailCall(self.machine_information, machine=machine, cont=cont, objectClass=objectClass, parent=parent, realm=realm, fields_values=fields_values))
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="",
height=0, width=0, form_height=0,
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")
(code, output) = box(values)
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont)
else:
values = dict(zip([form[k]['ldap_name'] for k in form_order], output))
fields_values = [("%s : " % k, values[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)
try:
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))
except Continue:
raise
except Exception as e:
self.dialog.msgbox("%s" % (unicode_of_Error(e)), title="Erreur rencontrée", width=73, height=10)
raise Continue(self_cont)
def create_certificat(self, machine, cont):
"""Permet d'ajouter un certificat à une machine à partir du PEM du 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,
backtitle="Appuyez sur CTRL+MAJ+V pour coller",
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
(code, pem) = box()
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont)
else:
try:
with self.conn.newCertificat(machine.dn, {}) as certificat:
certificat['certificat'] = unicode(pem, 'utf-8')
certificat.create()
raise Continue(cont(certificat=certificat, machine=certificat.machine()))
except Continue:
raise
except Exception as e:
self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10)
self_cont = TailCall(self.create_certificat, machine=machine, cont=cont)
raise Continue(self_cont)
def delete_certificat(self, certificat, cont):
"""Supprime un certificat"""
if self.confirm_item(item=certificat, title="Voulez vous vraiement supprimer le certificat ?"):
try:
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é", title="Suppression d'un certificat")
raise Continue(cont(certificat=None, machine=certificat.machine(refresh=True)))
except Continue:
raise
except Exception as e:
self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10)
raise Continue(cont)
else:
raise Continue(cont(certificat=certificat))
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))
certificat = machine.certificats()[certificat_index]
menu = {
'Hostname' : {'text':"Noms d'hôte utilisant le certificat", "attribut":attributs.hostCert},
'TLSA' : {'text':"Paramètres pour les champs dns TLSA", "callback":self.certificat_tlsa},
'Autre': {'text' : "Modifier les attribut booléen comme revocked", "callback":self.modif_certificat_boolean},
'Supprimer' : {'text' : "Supprimer le certificat", "callback":self.delete_certificat},
}
menu_order = ['Hostname', 'TLSA', 'Autre', 'Supprimer']
def box(default_item=None):
return self.dialog.menu(
"Paramètres pour le certificat N°0x%X émis par %s, valable du %s au %s" % (
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])))
),
width=0,
height=0,
menu_height=0,
item_help=0,
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']) for key in menu_order])
(code, tag) = box(tag)
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
if certificat is None:
raise Continue(cont(machine=machine))
else:
raise Continue(self_cont(certificat=None))
elif 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)
@tailcaller
def edit_certificat_select(self, machine, title, cont):
"""Permet de choisir un certificat existant ou nouveau d'une machine"""
def box(default_item=None):
index=0
choices = [('new', 'Ajouter un nouveau certificat')]
for cert in machine.certificats():
choices.append((str(index), "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]))))))
index+=1
return self.dialog.menu(
"Modifier ou ajouter un certificat ?",
width=0,
height=0,
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)
(code, tag) = box()
retry_cont = TailCall(self.edit_certificat_select, machine=machine, title=title, cont=cont)
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont)
elif code in [self.dialog.DIALOG_OK]:
if tag == 'new':
return tag
else:
return int(tag)
else:
self.dialog.msgbox("Le code de retour dialog est %s, c'est étrange" % code, title="Erreur rencontrée", width=73, height=10)
raise Continue(retry_cont)
return cont
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_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 = {
'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},
}
menu_order = ['Information', 'Blackliste', 'Certificat', 'Alias', 'Exemption', 'SshKey', 'Autre', 'Remarques']
def box(default_item=None):
return self.dialog.menu(
"Que souhaitez vous modifier ?",
width=0,
height=0,
menu_height=0,
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])
(code, tag) = box(tag)
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont(machine=machine))
elif not tag in menu_order:
raise Continue(TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag))
else:
cont_ret = TailCall(self.modif_machine, cont=cont, machine=machine, tag=tag)
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)
def create_machine_adherent(self, cont, adherent=None, tag=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)
menu = {
'Fixe' : {'text' : "Machine filaire", 'objectClass':'machineFixe', 'realm':'adherents'},
'Wifi' : {'text': 'Machine sans fil', 'objectClass':'machineWifi', 'realm':'wifi-adh'},
}
menu_order = ['Fixe', 'Wifi']
def box(default_item=None):
return self.dialog.menu(
"Type de Machine ?",
width=0,
height=0,
menu_height=0,
item_help=0,
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])
(code, tag) = box(tag)
if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cont(proprio=adherent))
elif not tag in menu_order:
raise Continue(TailCall(self.create_machine_adherent, cont=cont, adherent=adherent, tag=tag))
else:
return self.machine_information(
cont=cont(proprio=adherent),
machine=None,
objectClass=menu[tag]['objectClass'],
parent=adherent,
realm=menu[tag]['realm']
)
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)
if self.confirm_item(item=machine, title="Voulez vous vraiement supprimer la machine ?", defaultno=True):
try:
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", title="Suppression d'une machine")
raise Continue(cont(machine=None))
except Continue:
raise
except Exception as e:
self.dialog.msgbox("%s" % unicode_of_Error(e), title="Erreur rencontrée", width=73, height=10)
raise Continue(cont)
else:
raise Continue(cont)
return cont
def modif_adherent(self, cont, adherent=None):
if adherent is None:
adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont)
self.dialog.msgbox("todo", width=0, height=0)
return cont(proprio=adherent)
def create_adherent(self, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
def delete_adherent(self, cont):
adherent = self.select(["adherent"], "Recherche d'un adhérent pour supression", cont=cont)
self.dialog.msgbox("todo", width=0, height=0)
return cont
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):
self.dialog.msgbox("todo", width=0, height=0)
return cont
def create_machine_club(self, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
def create_machine_crans(self, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
def create_borne(self, cont):
self.dialog.msgbox("todo", width=0, height=0)
return cont
@tailcaller
def menu_principal(self, tag=None, machine=None, proprio=None):
"""Menu principal de l'application affiché au lancement"""
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},
'aKB': {'text':"Ajouter une borne wifi", 'callback': self.create_borne},
'' : {'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", "aKB"]
if machine and not proprio:
proprio = machine.proprio()
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_proprio = {
'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_proprio_order = ['mAc', 'aMc']
menu.update(menu_proprio)
menu_order = menu_proprio_order + menu_order
def box(default_item=None):
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,
cancel_label="Quitter",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=[(key, menu.get(key, menu[''])['text'], menu.get(key, menu['']).get('help', "")) for key in menu_order])
(code, tag) = box(tag)
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 TailCall(self.menu_principal, tag=tag, proprio=proprio, machine=machine)
if __name__ == '__main__':
GestCrans().menu_principal()
os.system('clear')