
l'édition des machines est fini, celle des adhérents à plus de 70% ou 80%. Il ne restera bientôt que les clubs.
2139 lines
108 KiB
Python
Executable file
2139 lines
108 KiB
Python
Executable file
#!/bin/bash /usr/scripts/python.sh
|
|
# -*- coding: utf-8 -*-
|
|
|
|
u"""
|
|
Interface utilisateur du système de gestion des machines
|
|
et adhérents du crans
|
|
|
|
Copyright (C) Valentin Samir
|
|
Licence : GPLv3
|
|
|
|
"""
|
|
### sur le dialog (1.1) de zamok, il manque les fonctionnalité suivante présente dans la 1.2
|
|
### --default-button pour choisir le bouton sélectionner par defaut
|
|
### --not-tags pour masquer les tags quand il ne servent à rien pour l'utilisateur (mais sont utilisé comme index par le programme)
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import ldap
|
|
import signal
|
|
import inspect
|
|
import tempfile
|
|
import collections
|
|
sys.path.append('/usr/scripts/')
|
|
from pythondialog import Dialog
|
|
from pythondialog import DialogError
|
|
|
|
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
|
|
|
|
debugf=None
|
|
debug_enable = True
|
|
|
|
def mydebug(txt):
|
|
global debugf, debug_enable
|
|
if debug_enable:
|
|
if debugf is None:
|
|
debugf = open('/tmp/gest_crans_lc.log', 'w')
|
|
if isinstance(txt, list):
|
|
for v in txt:
|
|
mydebug(' ' + str(v))
|
|
else:
|
|
debugf.write(str(txt)+'\n')
|
|
debugf.flush()
|
|
os.fsync(debugf)
|
|
|
|
# Implémentation "à la main" de la tail récursion en python
|
|
# voir http://kylem.net/programming/tailcall.html
|
|
# je trouve ça assez sioux
|
|
# En plus, ça nous permet de gérer plus facilement le
|
|
# code en CPS
|
|
class TailCaller(object) :
|
|
"""
|
|
Classe permetant, en décorant des fonctions avec, d'avoir de la tail récursion
|
|
faite "à la main" en python (voir http://kylem.net/programming/tailcall.html)
|
|
"""
|
|
other_callers = {}
|
|
def __init__(self, f) :
|
|
self.f = f
|
|
self.func_name = f.func_name
|
|
TailCaller.other_callers[id(self)]=f.func_name
|
|
def __del__(self):
|
|
del(TailCaller.other_callers[id(self)])
|
|
|
|
def __call__(self, *args, **kwargs) :
|
|
mydebug("***%s calling" % self.func_name)
|
|
stacklvl = len(inspect.stack())
|
|
try:
|
|
ret = self.f(*args, **kwargs)
|
|
except Continue as c:
|
|
ret = c.tailCall
|
|
while isinstance(ret, TailCall):
|
|
# Si la continuation a été créer par un TailCaller précédent, on propage
|
|
# Cela permet d'assurer qu'un TailCaller ne s'occupe que des TailCall
|
|
# Crée après lui.
|
|
if stacklvl>=ret.stacklvl:
|
|
mydebug("***%s terminate" % self.func_name)
|
|
raise Continue(ret)
|
|
mydebug("***%s doing %s" % (self.func_name, ret))
|
|
try:
|
|
ret = ret.handle()
|
|
except Continue as c:
|
|
ret = c.tailCall
|
|
mydebug("***%s returning %s" % (self.func_name, ret))
|
|
return ret
|
|
|
|
def tailcaller(f):
|
|
"""
|
|
Décorateur retardant la décoration par TailCaller d'une fonction
|
|
À utiliser sur les fonctions faisant de la tail récursion
|
|
et devant retourner une valeur. On l'utilise de toute
|
|
façon sur la fonction d'entrée dans l'application
|
|
Une fonction de devrait pas instancier de TailCall sans
|
|
être décoré par ce décorteur de façon générale
|
|
"""
|
|
|
|
f.tailCaller = True
|
|
return f
|
|
|
|
class Continue(Exception):
|
|
"""Exception pour envoyer des TailCall en les raisant"""
|
|
def __init__(self, tailCall):
|
|
self.tailCall = tailCall
|
|
|
|
class TailCall(object) :
|
|
"""
|
|
Appel tail récursif à une fonction et ses argument
|
|
On peut voir un TailCall comme le fait de retarder
|
|
l'appel à une fonction (avec ses listes d'arguements)
|
|
à un moment futur. La fonction sera appelée dans les
|
|
cas suivant :
|
|
* Le TailCall est retourné par une fonction qui est un TailCaller
|
|
* L'exception Continue(TailCall(...)) est levée et traverse
|
|
une fonction qui est un TailCaller
|
|
"""
|
|
|
|
def __init__(self, call, *args, **kwargs) :
|
|
self.stacklvl = len(inspect.stack())
|
|
if isinstance(call, TailCall):
|
|
call.kwargs.update(**kwargs)
|
|
kwargs = call.kwargs
|
|
args = call.args + args
|
|
call = call.call
|
|
|
|
self.call = call
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
|
|
def __str__(self):
|
|
return "TailCall<%s(%s%s%s)>" % (self.call.func_name, ', '.join(repr(a) for a in self.args), ', ' if self.args and self.kwargs else '', ', '.join("%s=%s" % (repr(k),repr(v)) for (k,v) in self.kwargs.items()))
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
self.kwargs.update(kwargs)
|
|
self.args = self.args + args
|
|
return self
|
|
|
|
def handle(self) :
|
|
caller = None
|
|
call = self.call
|
|
while isinstance(call, TailCaller) :
|
|
caller = call
|
|
call = self.call.f
|
|
return call(*self.args, **self.kwargs)
|
|
|
|
def unicode_of_Error(x):
|
|
"""Formatte l'exception de type ValueError"""
|
|
return u"\n".join(unicode(i, 'utf-8') if type(i) == str
|
|
else repr(i) for i in x.args)
|
|
|
|
|
|
def handle_exit_code(d, code):
|
|
"""Gère les codes de retour dialog du menu principal"""
|
|
if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
|
|
if code == d.DIALOG_CANCEL:
|
|
msg = "Vous avez choisi Annuler dans la dernière fenêtre de dialogue.\n\n" \
|
|
"Voulez vous quitter le programme ?"
|
|
os.system('clear')
|
|
sys.exit(0)
|
|
else:
|
|
msg = "Vous avez appuyer sur ESC dans la dernière fenêtre de dialogue.\n\n" \
|
|
"Voulez vous quitter le programme ?"
|
|
if d.yesno(msg, width=60) == d.DIALOG_OK:
|
|
os.system('clear')
|
|
sys.exit(0)
|
|
return False
|
|
else:
|
|
return True # code est d.DIALOG_OK
|
|
|
|
|
|
class GestCrans(object):
|
|
"""Interface de gestion des machines et des adhérents du crans, version lc_ldap"""
|
|
def __getattribute__(self, attr):
|
|
ret = super(GestCrans, self).__getattribute__(attr)
|
|
# Petit hack pour que ça soit les methodes de l'objet instancié qui soient
|
|
# décorée par TailCaller et pas les méthodes statiques de la classe GestCrans
|
|
if getattr(ret, 'tailCaller', False) and not isinstance(ret, TailCaller):
|
|
ret = TailCaller(ret)
|
|
setattr(self, attr, ret)
|
|
return ret
|
|
|
|
def __init__(self):
|
|
if not debug_enable:
|
|
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
|
# On initialise le moteur de rendu en spécifiant qu'on va faire du dialog
|
|
printing.template(dialog=True)
|
|
|
|
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
|
|
|
|
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:
|
|
# On effectue ce qu'il y a a faire dans todo
|
|
return todo(*todo_args)
|
|
# 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, height=10)
|
|
raise Continue(error_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) = 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) = 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) = 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) = 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) = 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(prorio, 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(prorio, realm, attrs)
|
|
raise Continue(cont(machine=machine))
|
|
|
|
|
|
if machine:
|
|
objectClass = machine["objectClass"][0]
|
|
|
|
(code, tags) = 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) = 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, machine, cont):
|
|
"""Permet d'ajouter un certificat à une machine à partir du PEM du certificat"""
|
|
# input multiline en utilisant un editbox
|
|
def box():
|
|
fp, path = tempfile.mkstemp()
|
|
os.close(fp)
|
|
cmd = ['--editbox', path, "0", "0"]
|
|
(code, output) = self.dialog._perform(*(cmd,),
|
|
no_mouse=True, # 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, cont):
|
|
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) = box()
|
|
self_cont = TailCall(self.create_certificat, machine=machine, 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, 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 modif_machine_certificat(self, machine, cont, tag=None, certificat=None):
|
|
"""Permet l'édition d'un certificat d'une machine"""
|
|
self_cont = TailCall(self.modif_machine_certificat, machine=machine, cont=cont, certificat=certificat)
|
|
if certificat is None:
|
|
certificat_index = self.edit_certificat_select(machine=machine, title="Modification des certificats de %s" % machine['host'][0], cont=cont)
|
|
if certificat_index == 'new':
|
|
raise Continue(TailCall(self.create_certificat, machine=machine, cont=self_cont))
|
|
certificat = machine.certificats()[certificat_index]
|
|
menu = {
|
|
'Hostname' : {'text':"Noms d'hôte utilisant le certificat", "attribut":attributs.hostCert},
|
|
'TLSA' : {'text':"Paramètres pour les champs dns TLSA", "callback":self.certificat_tlsa},
|
|
'Autre': {'text' : "Modifier les attribut booléen comme revocked", "callback":self.modif_certificat_boolean},
|
|
'Supprimer' : {'text' : "Supprimer le certificat", "callback":self.delete_certificat},
|
|
}
|
|
menu_order = ['Hostname', 'TLSA', 'Autre', 'Supprimer']
|
|
def box(default_item=None):
|
|
return self.dialog.menu(
|
|
"Paramètres pour le certificat N°0x%X émis par %s, valable du %s au %s" % (
|
|
int(str(certificat['serialNumber'][0])),
|
|
certificat['issuerCN'][0],
|
|
time.strftime("%d/%m/%Y", time.localtime(int(certificat['start'][0]))),
|
|
time.strftime("%d/%m/%Y", time.localtime(int(certificat['end'][0])))
|
|
),
|
|
width=0,
|
|
height=0,
|
|
menu_height=0,
|
|
item_help=0,
|
|
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']) 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) = box(tag)
|
|
cancel_cont = cont(machine=machine) if certificat is None else self_cont(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')]
|
|
for cert in machine.certificats():
|
|
choices.append((str(index), "Emit par %s pour %s du %s au %s" % (cert['issuerCN'][0], ', '.join(str(cn) for cn in cert['hostCert']), time.strftime("%d/%m/%Y", time.localtime(int(cert['start'][0]))), time.strftime("%d/%m/%Y", time.localtime(int(cert['end'][0]))))))
|
|
index+=1
|
|
return self.dialog.menu(
|
|
"Modifier ou ajouter un certificat ?",
|
|
width=0,
|
|
height=0,
|
|
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 == 'new':
|
|
return tag
|
|
else:
|
|
return int(tag)
|
|
|
|
(code, tag) = 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,
|
|
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) = 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):
|
|
if not tag in menu_order:
|
|
raise Continue(self_cont)
|
|
else:
|
|
return self.machine_information(
|
|
cont=self_cont,
|
|
machine=None,
|
|
objectClass=menu[tag]['objectClass'],
|
|
proprio=proprio,
|
|
realm=menu[tag]['realm']
|
|
)
|
|
|
|
(code, tag) = 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])]
|
|
)
|
|
|
|
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) = 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) = 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) = 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) = 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) = 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) = 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) = 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) = 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])
|
|
|
|
(code, tag) = box(tag)
|
|
callback = menu.get(tag, menu[''])['callback']
|
|
if handle_exit_code(self.dialog, code) and callback:
|
|
return TailCall(callback, cont=TailCall(self.menu_principal, tag=tag, machine=machine, proprio=proprio))
|
|
else:
|
|
return TailCall(self.menu_principal, tag=tag, proprio=proprio, machine=machine)
|
|
|
|
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')
|