2444 lines
125 KiB
Python
Executable file
2444 lines
125 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 ldap
|
|
import signal
|
|
import inspect
|
|
import tempfile
|
|
import collections
|
|
sys.path.append('/usr/scripts/')
|
|
from pythondialog import Dialog
|
|
from pythondialog import DialogError
|
|
from OpenSSL import crypto, SSL
|
|
|
|
from gestion.cert_utils import createCertRequest
|
|
from gestion.affich_tools import get_screen_size, coul
|
|
from gestion.chgpass import checkpass
|
|
import gestion.config as config
|
|
|
|
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 = 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 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):
|
|
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):
|
|
self.check_ldap_last = time.time()
|
|
# On ouvre une connexion lc_ldap
|
|
self.conn = lc_ldap.shortcuts.lc_ldap_admin()
|
|
#self.conn.current_login = 'totocrans'
|
|
# 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
|
|
|
|
_dialog = None
|
|
@property
|
|
def dialog(self):
|
|
# 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
|
|
|
|
@tailcaller
|
|
def handle_dialog(self, cancel_cont, box, *args):
|
|
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
|
|
ctrlC = True
|
|
finally:
|
|
if ctrlC:
|
|
raise Continue(cancel_cont)
|
|
elif ret:
|
|
return ret
|
|
else:
|
|
EnvironmentError("Ni Ctrl+C ni ret ?!? c'est pas possible ")
|
|
|
|
@tailcaller
|
|
def handle_dialog_result(self, code, output, cancel_cont, error_cont, codes_todo=[]):
|
|
""" 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("%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)
|
|
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, 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(cont(**{update_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("%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))
|
|
|
|
# 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 ?", 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(cont(**{update_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("%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))
|
|
|
|
|
|
|
|
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,
|
|
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("%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="",
|
|
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, 'utf-8')
|
|
certificat.save()
|
|
else:
|
|
with self.conn.newCertificat(machine.dn, {}) as certificat:
|
|
certificat['certificat'] = unicode(pem, '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):
|
|
def todo(self_cont, cont):
|
|
(code, pass1) = self.dialog.passwordbox("Entrez un mot de passe", title="Choix d'un mot de passe")
|
|
if code != self.dialog.DIALOG_OK:
|
|
raise Continue(cont)
|
|
if confirm:
|
|
(code, pass2) = self.dialog.passwordbox("Comfirmer le mot de passe", title="Choix d'un mot de passe")
|
|
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,
|
|
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é", 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", 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):
|
|
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,)
|
|
os.remove(path)
|
|
return
|
|
|
|
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)
|
|
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")
|
|
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 ?") == 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):
|
|
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,)
|
|
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, 'utf-8')
|
|
certificat.save()
|
|
else:
|
|
with self.conn.newCertificat(machine.dn, {}) as certificat:
|
|
certificat['hostCert']=unicode(machine['host'][0])
|
|
certificat['csr'] = unicode(pem, '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]
|
|
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.append('AddPrivateKey')
|
|
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])
|
|
|
|
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)
|
|
(code, tag) = self.handle_dialog(cont, box, tag)
|
|
cancel_cont = cont(machine=machine) if certificat is None else self_cont(machine=certificat.machine(), certificat=None)
|
|
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"""
|
|
def box(default_item=None):
|
|
index=0
|
|
choices = [('new', 'Ajouter un nouveau certificat')]
|
|
choices.append(('priv', 'Générer une nouvelle clef privée'))
|
|
choices.append(('csr', 'Ajouter une nouvelle requête de certificat'))
|
|
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_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,
|
|
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])
|
|
|
|
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):
|
|
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.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])
|
|
|
|
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(self, cont, adherent=None, tag=None):
|
|
if adherent is None:
|
|
adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont)
|
|
a = attributs
|
|
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"},
|
|
#'Mail' :{'text' : "adresse mail de contact (alternative si compte crans)", "adherent":"proprio", "callback":self.proprio_mail},
|
|
#'Alias' : {'text': 'Créer ou supprimer un alias de la machine', 'attribut':attributs.mailAlias},
|
|
'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', "adherent":"proprio", 'callback':self.modif_proprio_blacklist},
|
|
'Vente' : {'text':"Chargement solde crans, vente de cable ou adaptateur ethernet ou autre", "adherent":"proprio", "callback":self.proprio_vente},
|
|
}
|
|
menu_order = ['Administratif', 'Personnel', 'Études', 'Chambre', 'Compte', 'GPGFingerprint', 'Remarques', 'Blackliste', 'Vente']
|
|
menu_compte_crans = ['Droits']
|
|
|
|
if "cransAccount" in adherent['objectClass']:
|
|
menu_order.extend(menu_compte_crans)
|
|
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=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=[(key, menu[key]['text'], menu[key].get('help', "")) for key in menu_order])
|
|
|
|
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, uldif={}, fields_values=None, 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)]
|
|
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_values and make_compte_crans is not None:
|
|
return (self.dialog.DIALOG_OK, [t[1] for t in fields_values], 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 ?", 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 :" % a.legend, 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_values if fields_values else 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, uldif, 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(uldif) as adherent:
|
|
for (key, values) in attrs.items():
|
|
adherent[key]=values
|
|
# Si compte crans à créer, on le crée
|
|
if make_compte_crans:
|
|
self.dialog.msgbox("todo", width=0, height=0)
|
|
return None
|
|
# Sinon, 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)
|
|
# 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, tags, adherent, uldif, separateur, make_compte_crans, force_create, cont, self_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]
|
|
attrs[a.ldap_name]=values
|
|
if adherent:
|
|
adherent = modif_adherent(adherent, attrs)
|
|
else:
|
|
adherent = create_adherent(attrs, make_compte_crans, force_create, uldif, 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_values = [("%s :" % a.legend, values, l) for ((a,l),values) in zip(to_display, tags)]
|
|
retry_cont = TailCall(self.adherent_personnel, adherent=adherent, cont=cont, uldif=uldif, 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, adherent, uldif, separateur, make_compte_crans, force_create, cont, retry_cont])]
|
|
)
|
|
|
|
def adherent_etudes(self, adherent, cont):
|
|
self.dialog.msgbox("todo", width=0, height=0)
|
|
return cont
|
|
|
|
@tailcaller
|
|
def adherent_chambre_campus(self, success_cont, cont, adherent, create=False):
|
|
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,
|
|
)
|
|
|
|
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):
|
|
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) == 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)
|
|
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()
|
|
### TODO SUpression du compte crans
|
|
self.dialog.msgbox("todo Supression du compte crans, lc_ldap ne sais pas encore faire", width=0, height=0)
|
|
self.display_item(item=adherent, title="Adhérent déménégé hors campus, machines et compte crans supprimées")
|
|
raise Continue(success_cont(adherent=adherent))
|
|
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):
|
|
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])
|
|
|
|
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])]
|
|
)
|
|
|
|
def proprio_compte_create(self, proprio, cont, warning=True, guess_login=True, guess_pass=0):
|
|
def todo(proprio, warning, guess_login, guess_pass, self_cont, 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,
|
|
colors=True) != self.dialog.DIALOG_OK:
|
|
raise Continue(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)
|
|
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))
|
|
with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio:
|
|
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,
|
|
)
|
|
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]),
|
|
)
|
|
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]),
|
|
) == self.dialog.DIALOG_OK:
|
|
return self.proprio_compte_password(proprio=proprio, cont=cont(proprio=proprio))
|
|
else:
|
|
raise Continue(cont(proprio=proprio))
|
|
|
|
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, self_cont, cont])]
|
|
)
|
|
|
|
def proprio_compte_password(self, proprio, cont):
|
|
def box():
|
|
return self.dialog.passwordform(text="Remplacer le mot de passe",
|
|
height=15, width=54, form_height=7,
|
|
fields=[("Mot de passe", "", 10, 20),
|
|
("Confirmation", "", 10, 20)],
|
|
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")
|
|
|
|
def todo(passwords, proprio, self_cont, cont):
|
|
(good, msg) = checkpass(passwords[0], 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)
|
|
raise Continue(self_cont)
|
|
elif passwords[0] != passwords[1]:
|
|
self.dialog.msgbox(
|
|
"Les deux mots de passes ne sont pas identiques",
|
|
title="Erreur dans le mot de passe de %s %s" % (proprio.get('prenom', [''])[0], proprio["nom"][0]),
|
|
colors=True)
|
|
raise Continue(self_cont)
|
|
else:
|
|
with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio:
|
|
proprio['userPassword']=unicode(lc_utils.hash_password(passwords[0]))
|
|
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]),
|
|
)
|
|
raise Continue(cont(proprio=proprio))
|
|
(code, passwords) = self.handle_dialog(cont, box)
|
|
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, self_cont, cont])]
|
|
)
|
|
|
|
|
|
def proprio_compte_delete(self, proprio, cont):
|
|
"""Permet la suppression du compte crans d'un proprio"""
|
|
def todo(proprio, self_cont, cont):
|
|
if 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)
|
|
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)
|
|
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):
|
|
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):
|
|
self.dialog.msgbox("todo", width=0, height=0)
|
|
return cont
|
|
|
|
def proprio_compte(self, proprio, cont, default_item=None):
|
|
has_compte = 'cransAccount' in proprio['objectClass']
|
|
disabled_compte = has_compte and 0 in proprio['shadowExpire']
|
|
menu = {}
|
|
menu_order = []
|
|
tag_translate = {
|
|
"Créer":"Password",
|
|
"Password":"Password",
|
|
"Supprimer":"Créer",
|
|
"Activer":"Désactiver",
|
|
"Désactiver":"Activer",
|
|
"Shell":"Shell",
|
|
'':'',
|
|
}
|
|
if has_compte:
|
|
menu["Password"] = {"text":"Changer le mot de passe du compte", "help":"", "callback":self.proprio_compte_password}
|
|
menu["Shell"] = {"text" : "Changer le shell de cet utilisateur", "help":'', "callback":self.proprio_compte_shell}
|
|
menu["Supprimer"] = {"text": "Supprimer le compte", "help":"Le home sera archivé dans le cimetière", "callback":self.proprio_compte_delete}
|
|
menu_order.extend(["Password", "Shell", "Supprimer"])
|
|
if disabled_compte:
|
|
menu["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)}
|
|
menu_order.insert(1, "Activer")
|
|
else:
|
|
menu["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)}
|
|
menu_order.insert(1, "Désactiver")
|
|
else:
|
|
menu["Créer"] = {"text": "Créer un compte", "help":'', "callback":self.proprio_compte_create}
|
|
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])
|
|
|
|
def todo(tag, menu, proprio, self_cont):
|
|
if not tag in menu_order:
|
|
raise Continue(self_cont)
|
|
else:
|
|
raise Continue(TailCall(menu[tag]['callback'], cont=self_cont, proprio=proprio))
|
|
|
|
(code, tag) = self.handle_dialog(cont, box, default_item)
|
|
self_cont = TailCall(self.proprio_compte, proprio=proprio, cont=cont, default_item=tag_translate[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 proprio_mail(self, proprio, cont):
|
|
self.dialog.msgbox("todo", width=0, height=0)
|
|
return cont
|
|
|
|
def adherent_droits(self, adherent, cont, choices_values=None):
|
|
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]),
|
|
)
|
|
def todo(droits, adherent, self_cont, cont):
|
|
"""if attributs.nounou in self.conn.droits:
|
|
droits_modifiable = attributs.DROITS_SUPERVISEUR[attributs.nounou]
|
|
elif attributs.bureau in self.conn.droits:
|
|
droits_modifiable = attributs.DROITS_SUPERVISEUR[attributs.bureau]
|
|
else:
|
|
droits_modifiable = []
|
|
new_droits = [str(d) for d in droits if d not in adherent['droits']]
|
|
del_droits = [str(d) for d in adherent['droits'] if d not in droits]
|
|
modif_droit = set(new_droits+del_droits)
|
|
modif_interdite = [d for d in modif_droit if d not in droits_modifiable]
|
|
if modif_interdite:
|
|
self.dialog.msgbox("Vous n'avez pas le droits de modifier les droits %s, seulement les droits '%s'" % (", ".join(modif_interdite), ", ".join(droits_modifiable)),
|
|
title="Droits de %s %s" % (adherent['prenom'][0], adherent['nom'][0]))
|
|
raise Continue(self_cont)
|
|
else:"""
|
|
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])]
|
|
)
|
|
def modif_proprio_blacklist(self, proprio, cont):
|
|
self.dialog.msgbox("todo", width=0, height=0)
|
|
return cont
|
|
|
|
def proprio_vente(self, proprio, cont):
|
|
self.dialog.msgbox("todo", width=0, height=0)
|
|
return cont
|
|
|
|
def create_adherent(self, cont):
|
|
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):
|
|
associationCrans = self.conn.search(dn="ou=data,dc=crans,dc=org", scope=0)[0]
|
|
return self.create_machine_proprio(cont=cont, proprio=associationCrans)
|
|
|
|
@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},
|
|
'' : {'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"]
|
|
if isinstance(proprio, objets.AssociationCrans):
|
|
proprio = None
|
|
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_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):
|
|
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=[(key, menu.get(key, menu[''])['text'], menu.get(key, menu['']).get('help', "")) for key in menu_order])
|
|
|
|
self_cont = TailCall(self.menu_principal, tag=tag, proprio=proprio, machine=machine)
|
|
(code, tag) = self.handle_dialog(TailCall(handle_exit_code, self.dialog, self.dialog.DIALOG_ESC), 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 self_cont
|
|
|
|
def main(gc, cont=None):
|
|
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')
|