# -*- 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 # ############################################################################## """Fonctions sur les utilisateurs """ # Anciennement ZScoUsers.py, fonctions de gestion des données réécrite avec flask/SQLAlchemy import base64 import jaxml import md5 import re import string import time from flask import current_app, url_for, g from flask_login import current_user import cracklib # pylint: disable=import-error from app import db from app.auth.models import Permission from app.auth.models import User from app.scodoc import html_sco_header from app.scodoc import sco_cache from app.scodoc import sco_etud from app.scodoc import sco_excel from app.scodoc import sco_preferences from app.scodoc.gen_tables import GenTable from app.scodoc.notes_log import log from app.scodoc.scolog import logdb from app.scodoc.sco_etud import format_prenom, format_nom from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message import app.scodoc.notesdb as ndb import app.scodoc.sco_utils as scu from app.scodoc.sco_exceptions import ( AccessDenied, ScoException, ScoValueError, ScoInvalidDateError, ScoLockedFormError, ScoGenError, ) def is_valid_password(cleartxt): """Check password. returns True if OK. """ if ( hasattr(scu.CONFIG, "MIN_PASSWORD_LENGTH") and scu.CONFIG.MIN_PASSWORD_LENGTH > 0 and len(cleartxt) < scu.CONFIG.MIN_PASSWORD_LENGTH ): return False # invalid: too short try: _ = cracklib.FascistCheck(cleartxt) return True except ValueError: return False # --------------- # --------------- # XXX TODO supprimer ndb.GetUsersDBConnexion def index_html(context, REQUEST, all_depts=False, with_inactives=False, format="html"): "gestion utilisateurs..." all_depts = int(all_depts) with_inactives = int(with_inactives) H = [html_sco_header.html_sem_header(context, REQUEST, "Gestion des utilisateurs")] H.append("
Ajouter un utilisateur'.format( url_for("users.create_user_form", scodoc_dept=g.scodoc_dept).encode( scu.SCO_ENCODING ) # sco8 ) ) H.append( ' Importer des utilisateurs
'.format( url_for("users.import_users_form", scodoc_dept=g.scodoc_dept).encode( scu.SCO_ENCODING ) # sco8 ) ) if all_depts: checked = "checked" else: checked = "" if with_inactives: olds_checked = "checked" else: olds_checked = "" H.append( """""" % (REQUEST.URL0, checked, olds_checked) ) L = list_users( context, g.scodoc_dept, all_depts=all_depts, with_inactives=with_inactives, format=format, REQUEST=REQUEST, with_links=current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept), ) if format != "html": return L H.append(L) F = html_sco_header.sco_footer(context, REQUEST) return "\n".join(H) + F def list_users( context, dept, all_depts=False, # tous les departements with_inactives=False, # inclut les anciens utilisateurs (status "old") format="html", with_links=True, REQUEST=None, ): "List users, returns a table in the specified format" from app.scodoc.sco_permissions_check import can_handle_passwd if dept and not all_depts: users = get_user_list(dept=dept, with_inactives=with_inactives) comm = "dept. %s" % dept.encode(scu.SCO_ENCODING) # sco8 else: users = get_user_list(with_inactives=with_inactives) comm = "tous" if with_inactives: comm += ", avec anciens" comm = "(" + comm + ")" # -- Add some information and links: r = [] for u in users: # Can current user modify this user ? can_modify = can_handle_passwd(u, allow_admindepts=True) d = u.to_dict() r.append(d) # Add links if with_links and can_modify: target = url_for( "users.user_info_page", scodoc_dept=dept, user_name=u.user_name ).encode( scu.SCO_ENCODING ) # sco8 d["_user_name_target"] = target d["_nom_target"] = target d["_prenom_target"] = target # Hide passwd modification date (depending on visitor's permission) if not can_modify: d["date_modif_passwd"] = "(non visible)" title = "Utilisateurs définis dans ScoDoc" tab = GenTable( rows=r, columns_ids=( "user_name", "nom_fmt", "prenom_fmt", "email", "dept", "roles_string", "date_expiration", "date_modif_passwd", "passwd_temp", "status_txt", ), titles={ "user_name": "Login", "nom_fmt": "Nom", "prenom_fmt": "Prénom", "email": "Mail", "dept": "Dept.", "roles": "Rôles", "date_expiration": "Expiration", "date_modif_passwd": "Modif. mot de passe", "passwd_temp": "Temp.", "status_txt": "Etat", }, caption=title, page_title="title", html_title="""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=sco_preferences.SemPreferences(context), ) return tab.make_page( context, format=format, with_html_headers=False, REQUEST=REQUEST ) def get_user_list(dept=None, with_inactives=False): """Returns list of users. If dept, select users from this dept, else return all users. """ # was get_userlist q = User.query if dept is not None: q = q.filter_by(dept=dept) if not with_inactives: q = q.filter_by(active=True) return q.order_by(User.nom, User.user_name).all() def _user_list(user_name): "return user as a dict" u = User.query.filter_by(user_name=user_name).first() if u: return u.to_dict() else: return None def user_info(user_name=None, user=None): """Dict avec 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: info = _user_list(user_name) else: info = [user.copy()] user_name = user["user_name"] if not info: # 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: # Ensure we never publish password hash if "password_hash" in info: del info["password_hash"] # p = format_prenom(info["prenom"]) 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"] = sco_etud.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 user_info_page(context, user_name=None, REQUEST=None): """Display page of info about given user. If user_name not specified, user current_user """ from app.scodoc.sco_permissions_check import can_handle_passwd # peut on divulguer ces infos ? if not can_handle_passwd(current_user, allow_admindepts=True): raise AccessDenied("Vous n'avez pas la permission de voir cette page") dept = g.scodoc_dept if not user_name: user = current_user else: user = User.query.filter_by(user_name=user_name).first() if not user: raise ScoValueError("invalid user_name") H = [ html_sco_header.sco_header( context, REQUEST, page_title="Utilisateur %s" % user.user_name.encode("utf-8"), # sco8 ) ] F = html_sco_header.sco_footer(context, REQUEST) H.append("L' utilisateur '%s' n'est pas défini dans ce module.
" % user_name ) if user.has_permission(Permission.ScoEditAllNotes, dept): H.append("(il peut modifier toutes les notes de %s)
" % dept) if user.has_permission(Permission.ScoEditAllEvals, dept): H.append("(il peut modifier toutes les évaluations de %s)
" % dept) if user.has_permission(Permission.ScoImplement, dept): H.append("(il peut creer des formations en %s)
" % dept) else: H.append( """
Login : %(user_name)s
Nom : %(nom)s
Prénom : %(prenom)s
Mail : %(email)s
Roles : %(roles_string)s
Dept : %(dept)s
Dernière modif mot de passe: %(date_modif_passwd)s
Date d'expiration: %(date_expiration)s
Se déconnecter: logout
' % url_for("auth.logout") ) # Liste des permissions H.append( ' ") if current_user.has_permission(Permission.ScoUsersAdmin, dept): H.append( '' % url_for("users.index_html", scodoc_dept=g.scodoc_dept) ) return scu.sco8_join(H) + F def check_modif_user(edit, user_name="", nom="", prenom="", email="", roles=[]): """Vérifie que cet utilisateur peut être créé (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 = _user_list(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 ? nom = nom.lower().strip() prenom = prenom.lower().strip() similar_users = User.query.filter( User.nom.ilike(nom), User.prenom.ilike(prenom) ).all() if edit: minmatch = 1 else: minmatch = 0 if len(similar_users) > minmatch: return ( False, "des utilisateurs proches existent: " + ", ".join( [ "%s %s (pseudo=%s)" % (x.prenom, x.nom, x.user_name) for x in similar_users ] ), ) # Roles ? if not roles: return False, "aucun rôle sélectionné, êtes vous sûr ?" # ok return True, "" def user_edit(user_name, vals): """Edit the user specified by user_name (ported from Zope to SQLAlchemy, hence strange !) """ u = User.query.filter_by(user_name=user_name).first() if not u: raise ScoValueError("Invalid user_name") u.from_dict(vals) db.session.add(u) db.session.commit()