[gest_crans_lc] Docstring et gestion des permission sur les shells

Seul les nounou peuvent éditer les shells restrictif, les cableurs peuvent éditer
les shells standard
This commit is contained in:
Valentin Samir 2014-03-25 11:55:37 +01:00
parent 12fa9bfc39
commit 2eeac7608c

View file

@ -47,6 +47,7 @@ debugf=None
debug_enable = False debug_enable = False
def mydebug(txt): def mydebug(txt):
"""Petit fonction pour écrire des messages de débug dans /tmp/gest_crans_lc.log"""
global debugf, debug_enable global debugf, debug_enable
if debug_enable: if debug_enable:
if debugf is None: if debugf is None:
@ -145,7 +146,12 @@ class TailCall(object) :
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())) return "TailCall<%s(%s%s%s)>" % (self.call.func_name, ', '.join(repr(a) for a in self.args), ', ' if self.args and self.kwargs else '', ', '.join("%s=%s" % (repr(k),repr(v)) for (k,v) in self.kwargs.items()))
def copy(self): def copy(self):
''' Patch the deepcopy dispatcher to pass modules back unchanged ''' '''
Renvois une copie de l'objet courant
attention les elements et args ou kwargs sont juste linké
ça n'est pas gennant dans la mesure où ils ne sont normalement pas
éditer mais remplacé par d'autres éléments
'''
result = TailCall(self.call, *list(self.args), **dict(self.kwargs)) result = TailCall(self.call, *list(self.args), **dict(self.kwargs))
result.stacklvl = self.stacklvl result.stacklvl = self.stacklvl
return result return result
@ -156,6 +162,11 @@ class TailCall(object) :
return self return self
def handle(self) : def handle(self) :
"""
Exécute la fonction call sur sa liste d'argument.
on déréférence les TailCaller le plus possible pour réduire
la taille de la stack
"""
caller = None caller = None
call = self.call call = self.call
while isinstance(call, TailCaller) : while isinstance(call, TailCaller) :
@ -164,7 +175,7 @@ class TailCall(object) :
return call(*self.args, **self.kwargs) return call(*self.args, **self.kwargs)
def unicode_of_Error(x): def unicode_of_Error(x):
"""Formatte l'exception de type ValueError""" """Formatte des exception"""
return u"\n".join(unicode(i, 'utf-8') if type(i) == str return u"\n".join(unicode(i, 'utf-8') if type(i) == str
else repr(i) for i in x.args) else repr(i) for i in x.args)
@ -173,8 +184,8 @@ def handle_exit_code(d, code):
"""Gère les codes de retour dialog du menu principal""" """Gère les codes de retour dialog du menu principal"""
if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): if code in (d.DIALOG_CANCEL, d.DIALOG_ESC):
if code == d.DIALOG_CANCEL: if code == d.DIALOG_CANCEL:
msg = "Vous avez choisi Annuler dans la dernière fenêtre de dialogue.\n\n" \ #msg = "Vous avez choisi Annuler dans la dernière fenêtre de dialogue.\n\n" \
"Voulez vous quitter le programme ?" # "Voulez vous quitter le programme ?"
os.system('clear') os.system('clear')
sys.exit(0) sys.exit(0)
else: else:
@ -188,6 +199,7 @@ def handle_exit_code(d, code):
return True # code est d.DIALOG_OK return True # code est d.DIALOG_OK
def raiseKeyboardInterrupt(x, y): def raiseKeyboardInterrupt(x, y):
"""fonction utilisée pour réactiver les Ctrl-C"""
raise KeyboardInterrupt() raise KeyboardInterrupt()
class GestCrans(object): class GestCrans(object):
@ -212,6 +224,7 @@ class GestCrans(object):
self.error_to_raise = (Continue, DialogError, ldap.SERVER_DOWN) self.error_to_raise = (Continue, DialogError, ldap.SERVER_DOWN)
def check_ldap(self): def check_ldap(self):
"""Se connecte à la base ldap et vérifie les droits de l'utilisateur courant"""
self.check_ldap_last = time.time() self.check_ldap_last = time.time()
# S'il y a --test dans les argument, on utilise la base de test # S'il y a --test dans les argument, on utilise la base de test
@ -251,6 +264,9 @@ class GestCrans(object):
_dialog = None _dialog = None
@property @property
def dialog(self): def dialog(self):
"""
Renvois l'objet dialog. De plus renouvelle régulièrement la connexion à la base ldap
"""
# Tous les self.timeout, on refraichie la connexion ldap # Tous les self.timeout, on refraichie la connexion ldap
if time.time() - self.check_ldap_last > self.timeout: if time.time() - self.check_ldap_last > self.timeout:
self.check_ldap_last = time.time() self.check_ldap_last = time.time()
@ -260,6 +276,9 @@ class GestCrans(object):
return self._dialog return self._dialog
def nyan(self, cont, *args, **kwargs): def nyan(self, cont, *args, **kwargs):
"""
Nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan nyan
"""
(lines, cols) = get_screen_size() (lines, cols) = get_screen_size()
print "\033[48;5;17m" print "\033[48;5;17m"
print " "*(lines * cols) print " "*(lines * cols)
@ -272,6 +291,11 @@ class GestCrans(object):
@tailcaller @tailcaller
def handle_dialog(self, cancel_cont, box, *args): def handle_dialog(self, cancel_cont, box, *args):
"""
Gère les fonctions appelant une fênetre dialog :
gestion de l'appuie sur Ctrl-C et rattrapage des exception / segfault de dialog.
cancel_cont représente l'endroit où retourner si Ctrl-C
"""
ctrlC=False ctrlC=False
ret=None ret=None
try: try:
@ -296,7 +320,14 @@ class GestCrans(object):
@tailcaller @tailcaller
def handle_dialog_result(self, code, output, cancel_cont, error_cont, codes_todo=[]): def handle_dialog_result(self, code, output, cancel_cont, error_cont, codes_todo=[]):
""" codes_todo = [(code, todo, todo_args)]""" """
Gère les fonctions traitant les résultat d'appels à dialog.
s'occupe de gérer les exceptions, Ctrl-C, propagation de certaine exceptions, l'appuis sur annuler.
Le code à exécuté lui ai passé via la liste codes_todo, qui doit contenir une liste de triple :
(code de retour dialog, fonction à exécuter, liste des arguements de la fonction)
la fonction est appelée sur ses arguements si le code retourné par dialog correspond.
codes_todo = [(code, todo, todo_args)]
"""
# Si on a appuyé sur annulé ou ESC, on s'en va via la continuation donnée en argument # 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): if code in (self.dialog.DIALOG_CANCEL, self.dialog.DIALOG_ESC):
raise Continue(cancel_cont) raise Continue(cancel_cont)
@ -845,6 +876,7 @@ class GestCrans(object):
text + printing.sprint(item, **params) + text_bottom, text + printing.sprint(item, **params) + text_bottom,
no_collapse=True, no_collapse=True,
colors=True, colors=True,
no_mouse=True,
timeout=self.timeout, timeout=self.timeout,
title=title, title=title,
defaultno=defaultno, defaultno=defaultno,
@ -1143,6 +1175,10 @@ les valeurs valident sont :
@tailcaller @tailcaller
def get_password(self, cont, confirm=True, title="Choix d'un mot de passe", **kwargs): def get_password(self, cont, confirm=True, title="Choix d'un mot de passe", **kwargs):
"""
Affiche une série d'inpuxbox pour faire entrer un mot de passe puis le retourne,
si confirm=True, il y a une confirmation du mot de passe de demandée
"""
def todo(self_cont, cont): def todo(self_cont, cont):
(code, pass1) = self.dialog.passwordbox("Entrez un mot de passe", title=title, timeout=self.timeout, **kwargs) (code, pass1) = self.dialog.passwordbox("Entrez un mot de passe", title=title, timeout=self.timeout, **kwargs)
if code != self.dialog.DIALOG_OK: if code != self.dialog.DIALOG_OK:
@ -1255,6 +1291,7 @@ les valeurs valident sont :
def gen_csr(self, certificat, cont): def gen_csr(self, certificat, cont):
"""Permet de générer un csr à partir de la clef privée du certificat"""
def todo(certificat, self_cont, cont): def todo(certificat, self_cont, cont):
if certificat['encrypted']: if certificat['encrypted']:
if "machineCrans" in certificat.machine()["objectClass"]: if "machineCrans" in certificat.machine()["objectClass"]:
@ -1310,6 +1347,7 @@ les valeurs valident sont :
) )
def get_certificat(self, certificat, cont, privatekey=False, csr=False): def get_certificat(self, certificat, cont, privatekey=False, csr=False):
"""Permet d'afficher le certificat courant"""
def box(text): def box(text):
fp, path = tempfile.mkstemp() fp, path = tempfile.mkstemp()
os.write(fp, text) os.write(fp, text)
@ -1646,6 +1684,7 @@ les valeurs valident sont :
) )
def create_machine_proprio(self, cont, proprio, tag=None): def create_machine_proprio(self, cont, proprio, tag=None):
"""Permet d'ajouter une machine à un proprio (adherent, club ou AssociationCrans)"""
a = attributs a = attributs
menu_droits = { menu_droits = {
'Fixe' : [a.soi, a.cableur, a.nounou], 'Fixe' : [a.soi, a.cableur, a.nounou],
@ -1744,6 +1783,7 @@ les valeurs valident sont :
def modif_adherent(self, cont, adherent=None, proprio=None, tag=None): def modif_adherent(self, cont, adherent=None, proprio=None, tag=None):
"""Menu d'édition d'un adhérent"""
if adherent is None: if adherent is None:
adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont) adherent = self.select(["adherent"], "Recherche d'un adhérent pour modification", cont=cont)
a = attributs a = attributs
@ -1954,11 +1994,13 @@ les valeurs valident sont :
) )
def adherent_etudes(self, adherent, cont): def adherent_etudes(self, adherent, cont):
"""Gestion des études de l'adhérent"""
self.dialog.msgbox("todo", width=0, height=0) self.dialog.msgbox("todo", width=0, height=0)
return cont return cont
@tailcaller @tailcaller
def adherent_chambre_campus(self, success_cont, cont, adherent, create=False): def adherent_chambre_campus(self, success_cont, cont, adherent, create=False):
"""Permet de faire déménager d'adhérent sur le campus"""
def box(): def box():
return self.dialog.inputbox( return self.dialog.inputbox(
"chambre ?", "chambre ?",
@ -2019,6 +2061,7 @@ les valeurs valident sont :
) )
@tailcaller @tailcaller
def adherent_chambre_ext(self, keep_machine, keep_compte, success_cont, cont, adherent, create=False): def adherent_chambre_ext(self, keep_machine, keep_compte, success_cont, cont, adherent, create=False):
"""Permet de faire déménager l'adhérent hors campus"""
if keep_machine and not keep_compte: if keep_machine and not keep_compte:
raise EnvironmentError("On ne devrait pas supprimer un compte crans et y laisser des machines") raise EnvironmentError("On ne devrait pas supprimer un compte crans et y laisser des machines")
elif keep_machine and keep_compte: elif keep_machine and keep_compte:
@ -2118,6 +2161,7 @@ les valeurs valident sont :
) )
def adherent_chambre(self, adherent, cont, default_item=None): def adherent_chambre(self, adherent, cont, default_item=None):
"""Permet de faire déménager d'adhérent"""
a = attributs a = attributs
menu_droits = { menu_droits = {
'0' : [a.cableur, a.nounou], '0' : [a.cableur, a.nounou],
@ -2165,6 +2209,7 @@ les valeurs valident sont :
@tailcaller @tailcaller
def proprio_compte_create(self, proprio, cont, warning=True, guess_login=True, guess_pass=0, return_obj=False, update_obj='proprio'): def proprio_compte_create(self, proprio, cont, warning=True, guess_login=True, guess_pass=0, return_obj=False, update_obj='proprio'):
"""Permet de créer un compte crans à un proprio (club ou adhérent)"""
def box_warning(warning, proprio, cont): def box_warning(warning, proprio, cont):
# Affiche-t-on le warning sur la consutation de l'adresse crans # Affiche-t-on le warning sur la consutation de l'adresse crans
if warning: if warning:
@ -2264,7 +2309,7 @@ les valeurs valident sont :
@tailcaller @tailcaller
def proprio_compte_password(self, proprio, cont, return_obj=False): def proprio_compte_password(self, proprio, cont, return_obj=False):
"""Permet de changer le mot de passe d'un compte crans"""
def test_password(password, self_cont): def test_password(password, self_cont):
(good, msg) = checkpass(password, dialog=True) (good, msg) = checkpass(password, dialog=True)
if not good: if not good:
@ -2338,6 +2383,7 @@ les valeurs valident sont :
) )
def proprio_compte_etat(self, proprio, disable, cont): def proprio_compte_etat(self, proprio, disable, cont):
"""Permet de d'éastiver ou activer un compte crans avec l'attribut shadowExpire"""
with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio:
if disable: if disable:
proprio["shadowExpire"]=0 proprio["shadowExpire"]=0
@ -2347,20 +2393,41 @@ les valeurs valident sont :
raise Continue(cont(proprio=proprio)) raise Continue(cont(proprio=proprio))
def proprio_compte_shell(self, proprio, cont, choices_values=None): def proprio_compte_shell(self, proprio, cont, choices_values=None):
"""Permet de modifier le shell d'un compte crans"""
a = attributs
# Seul les nounous peuvent changer les shells restrictifs
shells_droits = {
'default' : [a.soi, a.nounou, a.cableur],
'rbash' : [a.nounou],
'rssh' : [a.nounou],
'badPassSh' : [a.nounou],
'disconnect_shell':[a.nounou],
}
shell = os.path.basename(str(proprio['loginShell'][0])).lower() shell = os.path.basename(str(proprio['loginShell'][0])).lower()
shells = config.shells_gest_crans shells = config.shells_gest_crans
shells_order = config.shells_gest_crans_order shells_order = config.shells_gest_crans_order
def box(): def box():
# liste des shell éditables par l'utilisateur
editable_sheels = [s for s in shells_order if self.has_right(shells_droits.get(s, shells_droits['default']), proprio)]
# Si l'utilisateur de gest_crans peut éditer le shell courant
# il peut le changer pour un autre shell qu'il peut éditer
if shell in editable_sheels:
choices=[(s, shells[s]['desc'], 1 if s == shell else 0) for s in editable_sheels]
return self.dialog.radiolist( return self.dialog.radiolist(
text="", text="",
height=0, width=0, list_height=0, height=0, width=0, list_height=0,
choices=choices_values if choices_values else [(s, shells[s]['desc'], 1 if s == shell else 0) for s in shells_order], choices=choices_values if choices_values else choices,
title="Shell de %s %s" % (proprio.get('prenom', [""])[0], proprio['nom'][0]), title="Shell de %s %s" % (proprio.get('prenom', [""])[0], proprio['nom'][0]),
timeout=self.timeout timeout=self.timeout
) )
# Sinon, on affiche un message d'erreur et on annule
else:
self.dialog.msgbox("Vous ne pouvez pas changer le shell de cet utilisateur",
title="Édition du shell impossible", timeout=self.timeout, width=0, height=0)
return (self.dialog.DIALOG_CANCEL, None)
def todo(output, shell, shells, proprio, self_cont, cont): def todo(output, shell, shells, proprio, self_cont, cont):
loginShell = shells[output]['path'] loginShell = shells[output]['path']
if shell != output: if shell and shell != output:
with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio: with self.conn.search(dn=proprio.dn, scope=0, mode='rw')[0] as proprio:
proprio['loginShell']=unicode(loginShell) proprio['loginShell']=unicode(loginShell)
proprio.save() proprio.save()
@ -2381,6 +2448,7 @@ les valeurs valident sont :
) )
def proprio_compte(self, proprio, cont, default_item=None): def proprio_compte(self, proprio, cont, default_item=None):
"""Menu de gestion du compte crans d'un proprio"""
has_compte = 'cransAccount' in proprio['objectClass'] has_compte = 'cransAccount' in proprio['objectClass']
disabled_compte = has_compte and 0 in proprio['shadowExpire'] disabled_compte = has_compte and 0 in proprio['shadowExpire']
a = attributs a = attributs
@ -2391,6 +2459,7 @@ les valeurs valident sont :
"Désactiver" : [a.nounou], "Désactiver" : [a.nounou],
"Créer" : [a.cableur, a.nounou], "Créer" : [a.cableur, a.nounou],
"Supprimer" : [a.cableur, a.nounou], "Supprimer" : [a.cableur, a.nounou],
"Shell" : [a.nounou, a.soi, a.cableur],
} }
menu = { menu = {
"Password" : {"text":"Changer le mot de passe du compte", "help":"", "callback":self.proprio_compte_password}, "Password" : {"text":"Changer le mot de passe du compte", "help":"", "callback":self.proprio_compte_password},
@ -2433,7 +2502,7 @@ les valeurs valident sont :
scrollbar=True, scrollbar=True,
cancel_label="Retour", cancel_label="Retour",
backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login, backtitle=u"Vous êtes connecté en tant que %s" % self.conn.current_login,
choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[key], proprio)]) choices=[(k, menu[k]['text'], menu[k]['help']) for k in menu_order if self.has_right(menu_droits[k], proprio)])
def todo(tag, menu, proprio, self_cont): def todo(tag, menu, proprio, self_cont):
if not tag in menu_order: if not tag in menu_order:
@ -2457,6 +2526,7 @@ les valeurs valident sont :
) )
def adherent_droits(self, adherent, cont, choices_values=None): def adherent_droits(self, adherent, cont, choices_values=None):
"""Gestion des droits d'un adhérent"""
def box(): def box():
return self.dialog.checklist( return self.dialog.checklist(
text="", text="",
@ -2484,12 +2554,10 @@ les valeurs valident sont :
codes_todo=[([self.dialog.DIALOG_OK], todo, [droits, adherent, self_cont, 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
@tailcaller @tailcaller
def proprio_vente_set(self, article, cont): def proprio_vente_set(self, article, cont):
"""Permet de définir la quantité de l'article à vendre"""
def box(): def box():
if article['pu'] == '*': if article['pu'] == '*':
return self.dialog.inputbox(title="Montant pour %s ?" % article['designation'], return self.dialog.inputbox(title="Montant pour %s ?" % article['designation'],
@ -2530,6 +2598,7 @@ les valeurs valident sont :
@tailcaller @tailcaller
def proprio_vente(self, proprio, cont, tags=[], tag_paiment=None, to_set=[], have_set=[]): def proprio_vente(self, proprio, cont, tags=[], tag_paiment=None, to_set=[], have_set=[]):
"""Menu de vente du crans. Permet également de recharger le solde crans"""
box_paiement = { box_paiement = {
"liquide" : "Espèces", "liquide" : "Espèces",
"cheque" : "Chèque", "cheque" : "Chèque",
@ -2668,6 +2737,7 @@ les valeurs valident sont :
) )
def create_adherent(self, cont): def create_adherent(self, cont):
"""Crée un adhérent et potentiellement son compte crans avec lui"""
def mycont(adherent=None, **kwargs): def mycont(adherent=None, **kwargs):
if adherent: if adherent:
raise Continue(TailCall(self.modif_adherent, cont=cont, adherent=adherent)) raise Continue(TailCall(self.modif_adherent, cont=cont, adherent=adherent))
@ -2721,10 +2791,12 @@ les valeurs valident sont :
return self.create_machine_proprio(cont=cont, proprio=club) return self.create_machine_proprio(cont=cont, proprio=club)
def create_machine_crans(self, cont): def create_machine_crans(self, cont):
"""Permet l'ajout d'une machine à l'association"""
associationCrans = self.conn.search(dn="ou=data,dc=crans,dc=org", scope=0)[0] associationCrans = self.conn.search(dn="ou=data,dc=crans,dc=org", scope=0)[0]
return self.create_machine_proprio(cont=cont, proprio=associationCrans) return self.create_machine_proprio(cont=cont, proprio=associationCrans)
def has_right(self, list, obj=None): def has_right(self, list, obj=None):
"""Vérifie que l'un des droits de l'utilisateur courant est inclus dans list"""
if obj: if obj:
droits = obj.rights() droits = obj.rights()
else: else:
@ -2840,7 +2912,12 @@ les valeurs valident sont :
else: else:
return self_cont return self_cont
@TailCaller
def main(gc, cont=None): def main(gc, cont=None):
"""
Fonction principale gérant l'appel au menu principal,
le timeout des écrans dialog et les reconnexion a ldap en cas de perte de la connexion
"""
while True: while True:
try: try:
# tant que le timeout est atteint on revient au menu principal # tant que le timeout est atteint on revient au menu principal
@ -2849,12 +2926,19 @@ def main(gc, cont=None):
# Si l'erreur n'est pas due à un timeout, on la propage # Si l'erreur n'est pas due à un timeout, on la propage
if time.time() - gc.dialog_last_access < gc.timeout: if time.time() - gc.dialog_last_access < gc.timeout:
raise raise
else:
try:
gc.nyan(TailCall(lambda:None))
except Continue:
pass
# Si on perd la connexion à ldap, on en ouvre une nouvelle # Si on perd la connexion à ldap, on en ouvre une nouvelle
except ldap.SERVER_DOWN: 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: 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() gc.check_ldap()
else: else:
return return
if __name__ == '__main__': if __name__ == '__main__':
main(GestCrans()) main(GestCrans())
os.system('clear') os.system('clear')