# -*- mode: python -*- # -*- coding: utf-8 -*- ############################################################################## # # Gestion scolarite IUT # # Copyright (c) 1999 - 2021 Emmanuel Viennet. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Emmanuel Viennet emmanuel.viennet@viennet.net # ############################################################################## """ Gestion des utilisateurs (table SQL pour Zope User Folder) """ import string import re import time import md5 import base64 import jaxml from sco_zope import * # pylint: disable=unused-wildcard-import # --------------- import sco_utils as scu import notesdb as ndb from notes_log import log from scolog import logdb from scolars import format_prenom, format_nom import sco_import_users import sco_excel from TrivialFormulator import TrivialFormulator, TF, tf_error_message from gen_tables import GenTable import scolars import sco_cache import sco_users from sco_permissions import ( ScoEditAllEvals, ScoEditAllNotes, ScoImplement, ScoSuperAdmin, ScoUsersAdmin, ScoUsersView, ScoView, ) from sco_exceptions import ( AccessDenied, ScoException, ScoValueError, ScoInvalidDateError, ScoLockedFormError, ScoGenError, ) # --------------- # cache global: chaque instance, repérée par son URL, a un cache # qui est recréé à la demande # On cache ici la liste des utilisateurs, pour une duree limitee # (une minute). CACHE_userlist = {} # --------------- class ZScoUsers( ObjectManager, PropertyManager, RoleManager, Item, Persistent, Implicit ): "ZScousers object" meta_type = "ZScoUsers" security = ClassSecurityInfo() # This is the list of the methods associated to 'tabs' in the ZMI # Be aware that The first in the list is the one shown by default, so if # the 'View' tab is the first, you will never see your tabs by cliquing # on the object. manage_options = ( ({"label": "Contents", "action": "manage_main"},) + PropertyManager.manage_options # add the 'Properties' tab + ({"label": "View", "action": "index_html"},) + Item.manage_options # add the 'Undo' & 'Owner' tab + RoleManager.manage_options # add the 'Security' tab ) # no permissions, only called from python def __init__(self, id, title): "initialise a new instance" self.id = id self.title = title # Connexion to SQL database of users: # Ugly but necessary during transition out of Zope: _db_cnx_string = "dbname=SCOUSERS port=5432" security.declareProtected("Change DTML Documents", "GetUsersDBConnexion") GetUsersDBConnexion = ndb.GetUsersDBConnexion # -------------------------------------------------------------------- # # Users (top level) # # -------------------------------------------------------------------- # used to view content of the object security.declareProtected(ScoUsersView, "index_html") def index_html(self, REQUEST, all=0, with_olds=0, format="html"): "gestion utilisateurs..." all = int(all) with_olds = int(with_olds) # Controle d'acces authuser = REQUEST.AUTHENTICATED_USER user_name = str(authuser) # log('user: %s roles: %s'%(user_name,authuser.getRolesInContext(self))) user = self._user_list(args={"user_name": user_name}) if not user: zope_roles = authuser.getRolesInContext(self) if ("Manager" in zope_roles) or ("manage" in zope_roles): dept = "" # special case for zope admin else: raise AccessDenied("Vous n'avez pas la permission de voir cette page") else: dept = user[0]["dept"] H = [self.sco_header(REQUEST, page_title="Gestion des utilisateurs")] H.append("

Gestion des utilisateurs

") if authuser.has_permission(ScoUsersAdmin, self): H.append( '

Ajouter un utilisateur' ) H.append( '   Importer des utilisateurs

' ) if all: checked = "checked" else: checked = "" if with_olds: olds_checked = "checked" else: olds_checked = "" H.append( """

Tous les départements Avec anciens utilisateurs

""" % (REQUEST.URL0, checked, olds_checked) ) L = self.list_users( dept, all=all, with_olds=with_olds, format=format, REQUEST=REQUEST, with_links=authuser.has_permission(ScoUsersAdmin, self), ) if format != "html": return L H.append(L) F = self.sco_footer(REQUEST) return "\n".join(H) + F _userEditor = ndb.EditableTable( "sco_users", "user_id", ( "user_id", "user_name", "passwd", "roles", "date_modif_passwd", "nom", "prenom", "email", "dept", "passwd_temp", "status", "date_expiration", ), output_formators={ "date_modif_passwd": ndb.DateISOtoDMY, "date_expiration": ndb.DateISOtoDMY, }, input_formators={ "date_modif_passwd": ndb.DateDMYtoISO, "date_expiration": ndb.DateDMYtoISO, }, sortkey="nom", filter_nulls=False, ) def _user_list(self, **kw): # list info sur utilisateur(s) cnx = self.GetUsersDBConnexion() users = self._userEditor.list(cnx, **kw) for u in users: if u["status"] == "old": u["status_txt"] = "(ancien)" else: u["status_txt"] = "" return users def _user_edit(self, user_name, vals): # edit user cnx = self.GetUsersDBConnexion() vals["user_name"] = user_name self._userEditor.edit(cnx, vals) self.get_userlist_cache().inval_cache() # > self.acl_users.cache_removeUser(user_name) # exUserFolder's caches self.acl_users.xcache_removeUser(user_name) # Ensure that if status is "old", login is disabled # note that operation is reversible without having to re-enter a password # We change the roles (to avoid dealing with passwd hash, controled by exUserFolder) u = self._user_list(args={"user_name": user_name})[0] if u["status"] == "old" and u["roles"] and u["roles"][0] != "-": roles = ["-" + r for r in u["roles"].split(",")] cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) self.acl_users.scodoc_editUser(cursor, user_name, roles=roles) self.get_userlist_cache().inval_cache() elif not u["status"] and u["roles"] and u["roles"][0] == "-": roles = [r[1:] for r in u["roles"].split(",") if (r and r[0] == "-")] cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) self.acl_users.scodoc_editUser(cursor, user_name, roles=roles) self.get_userlist_cache().inval_cache() def _user_delete(self, user_name): # delete user cnx = self.GetUsersDBConnexion() user_id = self._user_list(args={"user_name": user_name})[0]["user_id"] self._userEditor.delete(cnx, user_id) self.get_userlist_cache().inval_cache() # > def _all_roles(self): "ensemble de tous les roles attribués ou attribuables" roles = set(self.DeptUsersRoles()) cnx = self.GetUsersDBConnexion() L = self._userEditor.list(cnx, {}) for l in L: roles.update([x.strip() for x in l["roles"].split(",")]) return [r for r in roles if r and r[0] != "-"] security.declareProtected(ScoUsersAdmin, "user_info") def user_info(self, user_name=None, user=None): """Donne infos sur l'utilisateur (qui peut ne pas etre dans notre base). Si user_name est specifie, interroge la BD. Sinon, user doit etre un dict. """ if user_name: infos = self._user_list(args={"user_name": user_name}) else: infos = [user.copy()] user_name = user["user_name"] if not infos: # special case: user is not in our database return { "user_name": user_name, "nom": user_name, "prenom": "", "email": "", "dept": "", "nomprenom": user_name, "prenomnom": user_name, "prenom_fmt": "", "nom_fmt": user_name, "nomcomplet": user_name, "nomplogin": user_name, "nomnoacc": scu.suppress_accents(user_name), "passwd_temp": 0, "status": "", "date_expiration": None, } else: info = infos[0] # always conceal password ! del info["passwd"] # always conceal password ! # if info["prenom"]: p = format_prenom(info["prenom"]) else: p = "" if info["nom"]: n = format_nom( info["nom"], uppercase=False ) # strcapitalize(strlower(info['nom'])) else: n = user_name prenom_abbrv = scu.abbrev_prenom(p) # nomprenom est le nom capitalisé suivi de l'initiale du prénom info["nomprenom"] = (n + " " + prenom_abbrv).strip() # prenomnom est l'initiale du prénom suivie du nom info["prenomnom"] = (prenom_abbrv + " " + n).strip() # nom_fmt et prenom_fmt: minuscule capitalisé info["nom_fmt"] = n info["prenom_fmt"] = scolars.format_prenom(p) # nomcomplet est le prenom et le nom complets info["nomcomplet"] = info["prenom_fmt"] + " " + info["nom_fmt"] # nomplogin est le nom en majuscules suivi du prénom et du login # e.g. Dupont Pierre (dupont) info["nomplogin"] = "%s %s (%s)" % (scu.strupper(n), p, info["user_name"]) # nomnoacc est le nom en minuscules sans accents info["nomnoacc"] = scu.suppress_accents(scu.strlower(info["nom"])) return info def _can_handle_passwd(self, authuser, user_name, allow_admindepts=False): """true if authuser can see or change passwd of user_name. If allow_admindepts, allow Admin from all depts (so they can view users from other depts and add roles to them). authuser is a Zope user object. user_name is a string. """ # Is authuser a zope admin ? zope_roles = authuser.getRolesInContext(self) if ("Manager" in zope_roles) or ("manage" in zope_roles): return True # Anyone can change its own passwd (or see its informations) if str(authuser) == user_name: return True # has permission ? if not authuser.has_permission(ScoUsersAdmin, self): return False # Ok, now check that authuser can manage users from this departement # Get user info user = self._user_list(args={"user_name": user_name}) if not user: return False # we don't have infos on this user ! # Get authuser info auth_name = str(authuser) authuser_info = self._user_list(args={"user_name": auth_name}) if not authuser_info: return False # not admin, and not in out database auth_dept = authuser_info[0]["dept"] if not auth_dept: return True # if no dept, can access users from all depts ! if auth_dept == user[0]["dept"] or allow_admindepts: return True else: return False def do_change_password(self, user_name, password): user = self._user_list(args={"user_name": user_name}) assert len(user) == 1, "database inconsistency: len(user)=%d" % len(user) # should not occur, already tested in _can_handle_passwd cnx = self.GetUsersDBConnexion() # en mode autocommit cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor.execute( "update sco_users set date_modif_passwd=now(), passwd_temp=0 where user_name=%(user_name)s", {"user_name": user_name}, ) # Laisse le exUserFolder modifier les donnees: self.acl_users.scodoc_editUser( cursor, user_name, password=password, roles=[user[0]["roles"]] ) log("change_password: change ok for %s" % user_name) self.get_userlist_cache().inval_cache() # > security.declareProtected(ScoView, "change_password") def change_password(self, user_name, password, password2, REQUEST): "change a password" # ScoUsersAdmin: modif tous les passwd de SON DEPARTEMENT # sauf si pas de dept (admin global) H = [] F = self.sco_footer(REQUEST) # Check access permission if not self._can_handle_passwd(REQUEST.AUTHENTICATED_USER, user_name): # access denied log( "change_password: access denied (authuser=%s, user_name=%s, ip=%s)" % (REQUEST.AUTHENTICATED_USER, user_name, REQUEST.REMOTE_ADDR) ) raise AccessDenied( "vous n'avez pas la permission de changer ce mot de passe" ) # check password if password != password2: H.append( """

Les deux mots de passes saisis sont différents !

Recommencer

""" % user_name ) else: if not sco_users.is_valid_password(password): H.append( """

ce mot de passe n\'est pas assez compliqué !
(oui, il faut un mot de passe vraiment compliqué !)

Recommencer

""" % user_name ) else: # ok, strong password # MD5 hash (now computed by exUserFolder) # digest = md5.new() # digest.update(password) # digest = digest.digest() # md5pwd = string.strip(base64.encodestring(digest)) # self.do_change_password(user_name, password) # # ici page simplifiee car on peut ne plus avoir # le droit d'acceder aux feuilles de style H.append( "

Changement effectué !

Ne notez pas ce mot de passe, mais mémorisez le !

Rappel: il est interdit de communiquer son mot de passe à un tiers, même si c'est un collègue de confiance !

Si vous n'êtes pas administrateur, le système va vous redemander votre login et nouveau mot de passe au prochain accès.

" ) return ( """ Mot de passe changé

Mot de passe changé !

""" % (scu.SCO_ENCODING, scu.SCO_ENCODING) + "\n".join(H) + 'Continuer' % self.ScoURL() ) return self.sco_header(REQUEST) + "\n".join(H) + F security.declareProtected(ScoView, "form_change_password") def form_change_password(self, REQUEST, user_name=None): """Formulaire changement mot de passe Un utilisateur peut toujours changer son mot de passe""" authuser = REQUEST.AUTHENTICATED_USER if not user_name: user_name = str(authuser) H = [self.sco_header(REQUEST, user_check=False)] F = self.sco_footer(REQUEST) # check access if not self._can_handle_passwd(authuser, user_name): return ( "\n".join(H) + "

Vous n'avez pas la permission de changer ce mot de passe

" + F ) # H.append( """

Changement du mot de passe de %(user_name)s

Nouveau mot de passe:
Confirmation:

Vous pouvez aussi: renvoyer un mot de passe aléatoire temporaire par mail à l'utilisateur """ % {"user_name": user_name, "url": REQUEST.URL0} ) return "\n".join(H) + F security.declareProtected(ScoView, "userinfo") def userinfo(self, user_name=None, REQUEST=None): "display page of info about connected user" authuser = REQUEST.AUTHENTICATED_USER if not user_name: user_name = str(authuser) # peut on divulguer ces infos ? if not self._can_handle_passwd( REQUEST.AUTHENTICATED_USER, user_name, allow_admindepts=True ): raise AccessDenied("Vous n'avez pas la permission de voir cette page") H = [self.sco_header(REQUEST, page_title="Utilisateur %s" % user_name)] F = self.sco_footer(REQUEST) H.append("

Utilisateur: %s" % user_name) info = self._user_list(args={"user_name": user_name}) if info: H.append("%(status_txt)s" % info[0]) H.append("

") if not info: H.append( "

L' utilisateur '%s' n'est pas défini dans ce module.

" % user_name ) if authuser.has_permission(ScoEditAllNotes, self): H.append("

(il peut modifier toutes les notes)

") if authuser.has_permission(ScoEditAllEvals, self): H.append("

(il peut modifier toutes les évaluations)

") if authuser.has_permission(ScoImplement, self): H.append("

(il peut creer des formations)

") else: H.append( """

Login : %(user_name)s
Nom : %(nom)s
Prénom : %(prenom)s
Mail : %(email)s
Roles : %(roles)s
Dept : %(dept)s
Dernière modif mot de passe: %(date_modif_passwd)s
Date d'expiration: %(date_expiration)s

") if str(authuser) == user_name: H.append( '

Se déconnecter: logout

' ) # Liste des permissions H.append( '

Permission de cet utilisateur:

") if authuser.has_permission(ScoUsersAdmin, self): H.append( '

Liste de tous les utilisateurs

' % self.ScoURL() ) return "\n".join(H) + F security.declareProtected(ScoUsersAdmin, "create_user_form") def create_user_form(self, REQUEST, user_name=None, edit=0): "form. creation ou edit utilisateur" # Get authuser info authuser = REQUEST.AUTHENTICATED_USER auth_name = str(authuser) authuser_info = self._user_list(args={"user_name": auth_name}) initvalues = {} # Access control zope_roles = authuser.getRolesInContext(self) if ( not authuser_info and not ("Manager" in zope_roles) and not ("manage" in zope_roles) ): # not admin, and not in database raise AccessDenied("invalid user (%s)" % auth_name) if authuser_info: auth_dept = authuser_info[0]["dept"] else: auth_dept = "" # edit = int(edit) H = [self.sco_header(REQUEST, bodyOnLoad="init_tf_form('')")] F = self.sco_footer(REQUEST) if edit: if not user_name: raise ValueError("missing argument: user_name") initvalues = self._user_list(args={"user_name": user_name})[0] H.append("

Modification de l'utilisateur %s

" % user_name) else: H.append("

Création d'un utilisateur

") if authuser.has_permission(ScoSuperAdmin, self): H.append("""

Vous êtes super administrateur !

""") # Noms de roles pouvant etre attribues aux utilisateurs via ce dialogue # si pas SuperAdmin, restreint aux rôles EnsX, SecrX, DeptX # if authuser.has_permission(ScoSuperAdmin, self): log("create_user_form called by %s (super admin)" % (auth_name,)) editable_roles = set(self._all_roles()) else: editable_roles = set(self.DeptUsersRoles()) # log('create_user_form: editable_roles=%s' % editable_roles) # if not edit: submitlabel = "Créer utilisateur" orig_roles = set() else: submitlabel = "Modifier utilisateur" initvalues["roles"] = initvalues["roles"].split(",") or [] orig_roles = set(initvalues["roles"]) if initvalues["status"] == "old": editable_roles = set() # can't change roles of a disabled user # add existing user roles displayed_roles = list(editable_roles.union(orig_roles)) displayed_roles.sort() disabled_roles = {} # pour desactiver les roles que l'on ne peut pas editer for i in range(len(displayed_roles)): if displayed_roles[i] not in editable_roles: disabled_roles[i] = True # log('create_user_form: displayed_roles=%s' % displayed_roles) descr = [ ("edit", {"input_type": "hidden", "default": edit}), ("nom", {"title": "Nom", "size": 20, "allow_null": False}), ("prenom", {"title": "Prénom", "size": 20, "allow_null": False}), ] if auth_name != user_name: # no one can't change its own status descr.append( ( "status", { "title": "Statut", "input_type": "radio", "labels": ("actif", "ancien"), "allowed_values": ("", "old"), }, ) ) if not edit: descr += [ ( "user_name", { "title": "Pseudo (login)", "size": 20, "allow_null": False, "explanation": "nom utilisé pour la connexion. Doit être unique parmi tous les utilisateurs.", }, ), ( "passwd", { "title": "Mot de passe", "input_type": "password", "size": 14, "allow_null": False, }, ), ( "passwd2", { "title": "Confirmer mot de passe", "input_type": "password", "size": 14, "allow_null": False, }, ), ] else: descr += [ ( "user_name", {"input_type": "hidden", "default": initvalues["user_name"]}, ), ("user_id", {"input_type": "hidden", "default": initvalues["user_id"]}), ] descr += [ ( "email", { "title": "e-mail", "input_type": "text", "explanation": "vivement recommandé: utilisé pour contacter l'utilisateur", "size": 20, "allow_null": True, }, ) ] if not auth_dept: # si auth n'a pas de departement (admin global) # propose de choisir le dept du nouvel utilisateur # sinon, il sera créé dans le même département que auth descr.append( ( "dept", { "title": "Département", "input_type": "text", "size": 12, "allow_null": True, "explanation": """département d\'appartenance de l\'utilisateur (s'il s'agit d'un administrateur, laisser vide si vous voulez qu'il puisse créer des utilisateurs dans d'autres départements)""", }, ) ) can_choose_dept = True else: can_choose_dept = False if edit: descr.append( ( "d", { "input_type": "separator", "title": "L'utilisateur appartient au département %s" % auth_dept, }, ) ) else: descr.append( ( "d", { "input_type": "separator", "title": "L'utilisateur sera crée dans le département %s" % auth_dept, }, ) ) descr += [ ( "date_expiration", { "title": "Date d'expiration", # j/m/a "input_type": "date", "explanation": "j/m/a, laisser vide si pas de limite", "size": 9, "allow_null": True, }, ), ( "roles", { "title": "Rôles", "input_type": "checkbox", "vertical": True, "allowed_values": displayed_roles, "disabled_items": disabled_roles, }, ), ( "force", { "title": "Ignorer les avertissements", "input_type": "checkbox", "explanation": "passer outre les avertissements (homonymes, etc)", "labels": ("",), "allowed_values": ("1",), }, ), ] if "tf-submitted" in REQUEST.form and not "roles" in REQUEST.form: REQUEST.form["roles"] = [] if "tf-submitted" in REQUEST.form: # Ajoute roles existants mais non modifiables (disabled dans le form) # orig_roles - editable_roles REQUEST.form["roles"] = list( set(REQUEST.form["roles"]).union(orig_roles - editable_roles) ) tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, descr, initvalues=initvalues, submitlabel=submitlabel, cancelbutton="Annuler", ) if tf[0] == 0: return "\n".join(H) + "\n" + tf[1] + F elif tf[0] == -1: return REQUEST.RESPONSE.redirect(self.UsersURL()) else: vals = tf[2] roles = set(vals["roles"]).intersection(editable_roles) if REQUEST.form.has_key("edit"): edit = int(REQUEST.form["edit"]) else: edit = 0 try: force = int(vals["force"][0]) except: force = 0 if edit: user_name = initvalues["user_name"] else: user_name = vals["user_name"] # ce login existe ? err = None users = self._user_list(args={"user_name": user_name}) if edit and not users: # safety net, le user_name ne devrait pas changer err = "identifiant %s inexistant" % user_name if not edit and users: err = "identifiant %s déjà utilisé" % user_name if err: H.append(tf_error_message("""Erreur: %s""" % err)) return "\n".join(H) + "\n" + tf[1] + F if not force: ok, msg = self._check_modif_user( edit, user_name=user_name, nom=vals["nom"], prenom=vals["prenom"], email=vals["email"], roles=vals["roles"], ) if not ok: H.append( tf_error_message( """Attention: %s (vous pouvez forcer l'opération en cochant "Ignorer les avertissements" en bas de page)""" % msg ) ) return "\n".join(H) + "\n" + tf[1] + F if edit: # modif utilisateur (mais pas passwd) if (not can_choose_dept) and vals.has_key("dept"): del vals["dept"] if vals.has_key("passwd"): del vals["passwd"] if vals.has_key("date_modif_passwd"): del vals["date_modif_passwd"] if vals.has_key("user_name"): del vals["user_name"] if (auth_name == user_name) and vals.has_key("status"): del vals["status"] # no one can't change its own status # traitement des roles: ne doit pas affecter les roles # que l'on en controle pas: for role in orig_roles: if role and not role in editable_roles: roles.add(role) vals["roles"] = ",".join(roles) # ok, edit log("sco_users: editing %s by %s" % (user_name, auth_name)) # log('sco_users: previous_values=%s' % initvalues) # log('sco_users: new_values=%s' % vals) self._user_edit(user_name, vals) return REQUEST.RESPONSE.redirect( "userinfo?user_name=%s&head_message=Utilisateur %s modifié" % (user_name, user_name) ) else: # creation utilisateur vals["roles"] = ",".join(vals["roles"]) # check identifiant if not re.match(r"^[a-zA-Z0-9@\\\-_\\\.]+$", vals["user_name"]): msg = tf_error_message( "identifiant invalide (pas d'accents ni de caractères spéciaux)" ) return "\n".join(H) + msg + "\n" + tf[1] + F # check passwords if vals["passwd"] != vals["passwd2"]: msg = tf_error_message( """Les deux mots de passes ne correspondent pas !""" ) return "\n".join(H) + msg + "\n" + tf[1] + F if not sco_users.is_valid_password(vals["passwd"]): msg = tf_error_message( """Mot de passe trop simple, recommencez !""" ) return "\n".join(H) + msg + "\n" + tf[1] + F if not can_choose_dept: vals["dept"] = auth_dept # ok, go log("sco_users: new_user %s by %s" % (vals["user_name"], auth_name)) self.create_user(vals, REQUEST=REQUEST) def _check_modif_user( self, edit, user_name="", nom="", prenom="", email="", roles=[] ): """Vérifie que et utilisateur peut etre crée (edit=0) ou modifié (edit=1) Cherche homonymes. returns (ok, msg) - ok : si vrai, peut continuer avec ces parametres (si ok est faux, l'utilisateur peut quand même forcer la creation) - msg: message warning a presenter l'utilisateur """ if not user_name or not nom or not prenom: return False, "champ requis vide" if not email: return False, "vous devriez indiquer le mail de l'utilisateur créé !" # ce login existe ? users = self._user_list(args={"user_name": user_name}) if edit and not users: # safety net, le user_name ne devrait pas changer return False, "identifiant %s inexistant" % user_name if not edit and users: return False, "identifiant %s déjà utilisé" % user_name # Des noms/prénoms semblables existent ? cnx = self.GetUsersDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) cursor.execute( "select * from sco_users where lower(nom) ~ %(nom)s and lower(prenom) ~ %(prenom)s;", {"nom": nom.lower().strip(), "prenom": prenom.lower().strip()}, ) res = cursor.dictfetchall() if edit: minmatch = 1 else: minmatch = 0 if len(res) > minmatch: return ( False, "des utilisateurs proches existent: " + ", ".join( [ "%s %s (pseudo=%s)" % (x["prenom"], x["nom"], x["user_name"]) for x in res ] ), ) # Roles ? if not roles and not (edit and users[0]["status"] == "old"): # nb: si utilisateur desactivé (old), pas de role attribué return False, "aucun rôle sélectionné, êtes vous sûr ?" # ok return True, "" security.declareProtected(ScoUsersAdmin, "import_users_form") def import_users_form(self, REQUEST, user_name=None, edit=0): """Import utilisateurs depuis feuille Excel""" head = self.sco_header(REQUEST, page_title="Import utilisateurs") H = [ head, """

Téléchargement d'une nouvelle liste d'utilisateurs

A utiliser pour importer de nouveaux utilisateurs (enseignants ou secrétaires)

L'opération se déroule en deux étapes. Dans un premier temps, vous téléchargez une feuille Excel type. Vous devez remplir cette feuille, une ligne décrivant chaque utilisateur. Ensuite, vous indiquez le nom de votre fichier dans la case "Fichier Excel" ci-dessous, et cliquez sur "Télécharger" pour envoyer au serveur votre liste.

""", ] help = """

Lors de la creation des utilisateurs, les opérations suivantes sont effectuées:

  1. vérification des données;
  2. génération d'un mot de passe alétoire pour chaque utilisateur;
  3. création de chaque utilisateur;
  4. envoi à chaque utilisateur de son mot de passe initial par mail.
""" H.append( """
  1. Obtenir la feuille excel à remplir
  2. """ ) F = self.sco_footer(REQUEST) tf = TrivialFormulator( REQUEST.URL0, REQUEST.form, ( ( "xlsfile", {"title": "Fichier Excel:", "input_type": "file", "size": 40}, ), ("formsemestre_id", {"input_type": "hidden"}), ), submitlabel="Télécharger", ) if tf[0] == 0: return "\n".join(H) + tf[1] + "
" + help + F elif tf[0] == -1: return REQUEST.RESPONSE.redirect(self.UsersURL()) else: # IMPORT diag = sco_import_users.import_excel_file( tf[2]["xlsfile"], REQUEST=REQUEST, context=self ) H = [head] H.append("

Import excel: %s

" % diag) H.append("

OK, import terminé !

") H.append( '

Continuer

' % self.UsersURL() ) return "\n".join(H) + help + F security.declareProtected(ScoUsersAdmin, "import_users_generate_excel_sample") def import_users_generate_excel_sample(self, REQUEST): "une feuille excel pour importation utilisateurs" data = sco_import_users.generate_excel_sample() return sco_excel.sendExcelFile(REQUEST, data, "ImportUtilisateurs.xls") security.declareProtected(ScoUsersAdmin, "create_user") def create_user(self, args, REQUEST=None): "creation utilisateur zope" cnx = self.GetUsersDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) passwd = args["passwd"] args["passwd"] = "undefined" if "passwd2" in args: del args["passwd2"] log("create_user: args=%s" % args) # log apres supr. du mot de passe ! _ = self._userEditor.create(cnx, args) self.get_userlist_cache().inval_cache() # > # call exUserFolder to set passwd roles = args["roles"].split(",") self.acl_users.scodoc_editUser( cursor, args["user_name"], password=passwd, roles=roles ) if REQUEST: return REQUEST.RESPONSE.redirect(self.UsersURL()) security.declareProtected(ScoUsersAdmin, "delete_user_form") def delete_user_form(self, REQUEST, user_name, dialog_confirmed=False): "delete user" authuser = REQUEST.AUTHENTICATED_USER if not self._can_handle_passwd(authuser, user_name): return ( self.sco_header(REQUEST, user_check=False) + "

Vous n'avez pas la permission de supprimer cet utilisateur

" + self.sco_footer(REQUEST) ) r = self._user_list(args={"user_name": user_name}) if len(r) != 1: return ScoValueError("utilisateur %s inexistant" % user_name) if not dialog_confirmed: return self.confirmDialog( """

Confirmer la suppression de l\'utilisateur %s ?

En général, il est déconseillé de supprimer un utilisateur, son identité étant référencé dans les modules de formation. N'utilisez cette fonction qu'en cas d'erreur (création de doublons, etc).

""" % user_name, dest_url="", REQUEST=REQUEST, cancel_url=self.UsersURL(), parameters={"user_name": user_name}, ) self._user_delete(user_name) REQUEST.RESPONSE.redirect( self.UsersURL() + r"?head_message=Utilisateur%20supprimé" ) security.declareProtected(ScoView, "list_users") def list_users( self, dept, all=False, # tous les departements with_olds=False, # inclue les anciens utilisateurs (status "old") format="html", with_links=True, REQUEST=None, ): "List users" authuser = REQUEST.AUTHENTICATED_USER if dept and not all: r = self.get_userlist(dept=dept, with_olds=with_olds) comm = "dept. %s" % dept else: r = self.get_userlist(with_olds=with_olds) comm = "tous" if with_olds: comm += ", avec anciens" comm = "(" + comm + ")" # -- Add some information and links: for u in r: # Can current user modify this user ? can_modify = self._can_handle_passwd( authuser, u["user_name"], allow_admindepts=True ) # Add links if with_links and can_modify: target = "userinfo?user_name=%(user_name)s" % u u["_user_name_target"] = target u["_nom_target"] = target u["_prenom_target"] = target # Hide passwd modification date (depending on rights wrt user) if not can_modify: u["date_modif_passwd"] = "(non visible)" # Add spaces between roles to ease line wrap if u["roles"]: u["roles"] = ", ".join(u["roles"].split(",")) # Convert dates to ISO if XML output if format == "xml" and u["date_modif_passwd"] != "NA": u["date_modif_passwd"] = ndb.DateDMYtoISO(u["date_modif_passwd"]) or "" # Convert date_expiration and date_modif_passwd to ISO to ease sorting if u["date_expiration"]: u["date_expiration_iso"] = ndb.DateDMYtoISO(u["date_expiration"]) else: u["date_expiration_iso"] = "" if u["date_modif_passwd"]: u["date_modif_passwd_iso"] = ndb.DateDMYtoISO(u["date_expiration"]) else: u["date_modif_passwd_iso"] = "" title = "Utilisateurs définis dans ScoDoc" tab = GenTable( rows=r, columns_ids=( "user_name", "nom_fmt", "prenom_fmt", "email", "dept", "roles", "date_expiration_iso", "date_modif_passwd_iso", "passwd_temp", "status_txt", ), titles={ "user_name": "Login", "nom_fmt": "Nom", "prenom_fmt": "Prénom", "email": "Mail", "dept": "Dept.", "roles": "Rôles", "date_expiration_iso": "Expiration", "date_modif_passwd_iso": "Modif. mot de passe", "passwd_temp": "Temp.", "status_txt": "Etat", }, caption=title, page_title="title", html_title="""

%d utilisateurs %s

Cliquer sur un nom pour changer son mot de passe

""" % (len(r), comm), html_class="table_leftalign list_users", html_with_td_classes=True, html_sortable=True, base_url="%s?all=%s" % (REQUEST.URL0, all), pdf_link=False, # table is too wide to fit in a paper page => disable pdf preferences=self.get_preferences(), ) return tab.make_page( self, format=format, with_html_headers=False, REQUEST=REQUEST ) def get_userlist_cache(self): url = self.ScoURL() if CACHE_userlist.has_key(url): return CACHE_userlist[url] else: log("get_userlist_cache: new simpleCache") CACHE_userlist[url] = sco_cache.expiringCache(max_validity=60) return CACHE_userlist[url] security.declareProtected(ScoView, "get_userlist") def get_userlist(self, dept=None, with_olds=False): """Returns list of users. If dept, select users from this dept, else return all users. """ # on ne cache que la liste sans les "olds" if with_olds: r = None else: cache = self.get_userlist_cache() r = cache.get(dept) if r != None: return r else: args = {} if not with_olds: args["status"] = None if dept != None: args["dept"] = dept r = self._user_list(args=args) l = [self.user_info(user=user) for user in r] if not with_olds: cache.set(dept, l) return l security.declareProtected(ScoView, "get_userlist_xml") def get_userlist_xml(self, dept=None, start="", limit=25, REQUEST=None): """Returns XML list of users with name (nomplogin) starting with start. Used for forms auto-completion.""" userlist = self.get_userlist(dept=dept) start = scu.suppression_diacritics(unicode(start, "utf-8")) start = scu.strlower(str(start)) userlist = [user for user in userlist if user["nomnoacc"].startswith(start)] if REQUEST: REQUEST.RESPONSE.setHeader("content-type", scu.XML_MIMETYPE) doc = jaxml.XML_document(encoding=scu.SCO_ENCODING) doc.results() for user in userlist[:limit]: doc._push() doc.rs(user["nomplogin"], id=user["user_id"], info="") doc._pop() return repr(doc) security.declareProtected(ScoView, "get_user_name_from_nomplogin") def get_user_name_from_nomplogin(self, nomplogin): """Returns user_name (login) from nomplogin""" m = re.match(r".*\((.*)\)", nomplogin.strip()) if m: return m.group(1) else: return None security.declareProtected(ScoView, "reset_password_form") def reset_password_form(self, user_name=None, dialog_confirmed=False, REQUEST=None): """Form to reset a password""" if not dialog_confirmed: return self.confirmDialog( """

Ré-initialiser le mot de passe de %s ?

Le mot de passe de %s va être choisi au hasard et lui être envoyé par mail. Il devra ensuite se connecter et le changer.

""" % (user_name, user_name), parameters={"user_name": user_name}, REQUEST=REQUEST, ) self.reset_password(user_name=user_name, REQUEST=REQUEST) return REQUEST.RESPONSE.redirect( self.UsersURL() + r"?head_message=mot%20de%20passe%20de%20" + user_name + "%20reinitialise" ) security.declareProtected(ScoView, "reset_password") def reset_password(self, user_name=None, REQUEST=None): """Reset a password: - set user's passwd_temp to 1 - set roles to 'ScoReset' - generate a random password and mail it """ authuser = REQUEST.AUTHENTICATED_USER auth_name = str(authuser) if not user_name: user_name = auth_name # Access control if not self._can_handle_passwd(authuser, user_name): raise AccessDenied( "vous n'avez pas la permission de changer ce mot de passe" ) log("reset_password: %s" % user_name) # Check that user has valid mail info = self.user_info(user_name=user_name) if not scu.is_valid_mail(info["email"]): raise Exception("pas de mail valide associé à l'utilisateur") # Generate random password password = sco_import_users.generate_password() self.do_change_password(user_name, password) # Flag it as temporary: cnx = self.GetUsersDBConnexion() cursor = cnx.cursor(cursor_factory=ndb.ScoDocCursor) ui = {"user_name": user_name} cursor.execute( "update sco_users set passwd_temp=1 where user_name='%(user_name)s'" % ui ) # Send email info["passwd"] = password sco_import_users.mail_password(info, context=self, reset=True) # -------------------------------------------------------------------- # # Zope Product Administration # # -------------------------------------------------------------------- def manage_addZScoUsers( self, id="id_ZScousers", title="The Title for ZScoUsers Object", REQUEST=None ): "Add a ZScoUsers instance to a folder." self._setObject(id, ZScoUsers(id, title)) if REQUEST is not None: return self.manage_main(self, REQUEST)