2021-06-26 21:57:54 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# ScoDoc
#
2022-01-01 14:49:42 +01:00
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
2021-06-26 21:57:54 +02:00
#
# 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
#
##############################################################################
"""
Module users : interface gestion utilisateurs
ré - écriture pour Flask ScoDoc7 / ZScoUsers . py
Vues s ' appuyant sur auth et sco_users
Emmanuel Viennet , 2021
"""
2021-07-27 16:55:50 +02:00
import datetime
2021-07-03 16:19:42 +02:00
import re
2021-10-30 12:03:21 +02:00
from enum import auto , IntEnum
2021-07-11 13:03:13 +02:00
from xml . etree import ElementTree
2021-06-28 10:45:00 +02:00
2021-08-01 10:16:16 +02:00
import flask
2021-10-16 23:22:03 +02:00
from flask import g , url_for , request , current_app , flash
2021-08-28 16:01:41 +02:00
from flask import redirect , render_template
2021-08-22 13:24:36 +02:00
2021-06-28 10:45:00 +02:00
from flask_login import current_user
2021-10-16 23:22:03 +02:00
from wtforms import HiddenField , PasswordField , StringField , SubmitField
from wtforms . validators import DataRequired , Email , ValidationError , EqualTo
2021-06-26 21:57:54 +02:00
2021-07-01 18:54:07 +02:00
from app import db
2021-08-28 16:01:41 +02:00
from app . auth . forms import DeactivateUserForm
2021-06-26 21:57:54 +02:00
from app . auth . models import Permission
from app . auth . models import User
2021-07-03 16:19:42 +02:00
from app . auth . models import Role
from app . auth . models import UserRole
2021-10-15 19:17:40 +02:00
from app . auth . models import is_valid_password
2021-10-15 15:12:46 +02:00
from app . email import send_email
2021-08-13 09:31:49 +02:00
from app . models import Departement
2021-06-26 21:57:54 +02:00
from app . decorators import (
2021-08-13 00:34:58 +02:00
scodoc ,
2021-06-26 21:57:54 +02:00
scodoc7func ,
permission_required ,
)
2021-08-22 13:24:36 +02:00
from app . scodoc import html_sco_header , sco_import_users , sco_excel
2021-06-26 21:57:54 +02:00
from app . scodoc import sco_users
2021-06-27 12:11:39 +02:00
from app . scodoc import sco_utils as scu
2021-07-11 13:03:13 +02:00
from app . scodoc import sco_xml
2021-08-29 19:57:32 +02:00
from app import log
2021-07-03 16:19:42 +02:00
from app . scodoc . sco_exceptions import AccessDenied , ScoValueError
2021-10-15 15:12:46 +02:00
from app . scodoc . sco_import_users import generate_password
2021-07-01 18:54:07 +02:00
from app . scodoc . sco_permissions_check import can_handle_passwd
2021-07-03 16:19:42 +02:00
from app . scodoc . TrivialFormulator import TrivialFormulator , tf_error_message
2021-06-26 21:57:54 +02:00
from app . views import users_bp as bp
2021-10-16 23:22:03 +02:00
from flask_wtf import FlaskForm
_ = lambda x : x # sans babel
_l = _
class ChangePasswordForm ( FlaskForm ) :
user_name = HiddenField ( )
2021-10-17 12:15:24 +02:00
old_password = PasswordField ( _l ( " Identifiez-vous " ) )
2021-10-16 23:22:03 +02:00
new_password = PasswordField ( _l ( " Nouveau mot de passe " ) )
bis_password = PasswordField (
_l ( " Répéter " ) ,
validators = [
EqualTo (
" new_password " ,
message = " Les deux saisies sont " " différentes, recommencez " ,
) ,
] ,
)
2021-10-17 12:15:24 +02:00
email = StringField (
_l ( " Email " ) ,
validators = [
DataRequired ( ) ,
Email ( message = " adresse email invalide, recommencez " ) ,
] ,
)
2021-10-17 11:19:01 +02:00
submit = SubmitField ( )
cancel = SubmitField ( " Annuler " )
2021-10-16 23:22:03 +02:00
def validate_email ( self , email ) :
2021-10-17 12:15:24 +02:00
user = User . query . filter_by ( email = email . data . strip ( ) ) . first ( )
2021-10-16 23:22:03 +02:00
if user is not None and self . user_name . data != user . user_name :
2021-10-17 12:15:24 +02:00
raise ValidationError (
_ ( " Cette adresse e-mail est déjà attribuée à un autre compte " )
)
2021-10-16 23:22:03 +02:00
def validate_new_password ( self , new_password ) :
if new_password . data != " " and not is_valid_password ( new_password . data ) :
raise ValidationError ( f " Mot de passe trop simple, recommencez " )
def validate_old_password ( self , old_password ) :
2021-10-17 11:19:01 +02:00
if not current_user . check_password ( old_password . data ) :
raise ValidationError ( " Mot de passe actuel incorrect, ré-essayez " )
2021-06-26 21:57:54 +02:00
2021-10-30 12:03:21 +02:00
class Mode ( IntEnum ) :
WELCOME_AND_CHANGE_PASSWORD = auto ( )
WELCOME_ONLY = auto ( )
SILENT = auto ( )
2021-06-26 21:57:54 +02:00
@bp.route ( " / " )
@bp.route ( " /index_html " )
2021-08-13 00:34:58 +02:00
@scodoc
2021-06-26 21:57:54 +02:00
@permission_required ( Permission . ScoUsersView )
2021-08-21 00:24:51 +02:00
@scodoc7func
2021-09-27 10:20:10 +02:00
def index_html ( all_depts = False , with_inactives = False , format = " html " ) :
2021-06-26 21:57:54 +02:00
return sco_users . index_html (
all_depts = all_depts ,
2021-06-27 12:11:39 +02:00
with_inactives = with_inactives ,
2021-06-26 21:57:54 +02:00
format = format ,
)
2021-06-27 12:11:39 +02:00
@bp.route ( " /user_info " )
2021-08-13 00:34:58 +02:00
@scodoc
2021-06-27 12:11:39 +02:00
@permission_required ( Permission . ScoUsersView )
2021-08-21 00:24:51 +02:00
@scodoc7func
2021-09-27 10:20:10 +02:00
def user_info ( user_name , format = " json " ) :
2021-08-22 13:24:36 +02:00
info = sco_users . user_info ( user_name )
2021-09-21 15:53:33 +02:00
return scu . sendResult ( info , name = " user " , format = format )
2021-06-27 12:11:39 +02:00
2021-06-28 10:45:00 +02:00
@bp.route ( " /create_user_form " , methods = [ " GET " , " POST " ] )
2021-08-13 00:34:58 +02:00
@scodoc
2021-06-28 10:45:00 +02:00
@permission_required ( Permission . ScoUsersAdmin )
2021-08-21 00:24:51 +02:00
@scodoc7func
2021-09-27 10:20:10 +02:00
def create_user_form ( user_name = None , edit = 0 , all_roles = 1 ) :
2021-09-13 23:06:42 +02:00
" form. création ou edition utilisateur "
2021-06-28 10:45:00 +02:00
auth_dept = current_user . dept
2021-10-30 12:09:35 +02:00
from_mail = current_user . email
2021-06-28 10:45:00 +02:00
initvalues = { }
edit = int ( edit )
2021-09-13 23:06:42 +02:00
all_roles = int ( all_roles )
2021-10-15 15:34:10 +02:00
H = [
html_sco_header . sco_header (
bodyOnLoad = " init_tf_form( ' ' ) " ,
javascripts = [ " js/user_form.js " ] ,
)
]
2021-07-29 10:19:00 +02:00
F = html_sco_header . sco_footer ( )
2021-06-28 10:45:00 +02:00
if edit :
if not user_name :
raise ValueError ( " missing argument: user_name " )
2021-12-29 11:26:54 +01:00
the_user = User . query . filter_by ( user_name = user_name ) . first ( )
if not the_user :
2021-07-03 16:19:42 +02:00
raise ScoValueError ( " utilisateur inexistant " )
2021-12-29 11:26:54 +01:00
initvalues = the_user . to_dict ( )
2021-06-28 10:45:00 +02:00
H . append ( " <h2>Modification de l ' utilisateur %s </h2> " % user_name )
else :
H . append ( " <h2>Création d ' un utilisateur</h2> " )
is_super_admin = False
if current_user . has_permission ( Permission . ScoSuperAdmin , g . scodoc_dept ) :
H . append ( """ <p class= " warning " >Vous êtes super administrateur !</p> """ )
is_super_admin = True
2021-09-13 23:06:42 +02:00
if all_roles :
# tous sauf SuperAdmin
standard_roles = [
r
for r in Role . query . all ( )
if r . permissions != Permission . ALL_PERMISSIONS [ 0 ]
]
else :
# Les rôles standards créés à l'initialisation de ScoDoc:
standard_roles = [
Role . get_named_role ( r ) for r in ( " Ens " , " Secr " , " Admin " , " RespPe " )
]
# Départements auxquels ont peut associer des rôles via ce dialogue:
2021-07-03 16:19:42 +02:00
# si SuperAdmin, tous les rôles standards dans tous les départements
# sinon, les départements dans lesquels l'utilisateur a le droit
2021-06-28 10:45:00 +02:00
if is_super_admin :
log ( " create_user_form called by %s (super admin) " % ( current_user . user_name , ) )
2021-12-29 11:26:54 +01:00
administrable_dept_acronyms = [ d . acronym for d in Departement . query . all ( ) ]
2021-06-28 10:45:00 +02:00
else :
2021-07-03 16:19:42 +02:00
# Si on n'est pas SuperAdmin, liste les départements dans lesquels on a la
# permission ScoUsersAdmin
2021-12-29 11:26:54 +01:00
administrable_dept_acronyms = sorted (
2021-07-03 16:19:42 +02:00
set (
[
2022-01-04 20:03:38 +01:00
x . dept or " "
2021-07-03 16:19:42 +02:00
for x in UserRole . query . filter_by ( user = current_user )
if x . role . has_permission ( Permission . ScoUsersAdmin ) and x . dept
]
)
)
2021-12-29 11:26:54 +01:00
editable_roles_set = {
( r , dept ) for r in standard_roles for dept in administrable_dept_acronyms
}
2021-06-28 10:45:00 +02:00
#
if not edit :
submitlabel = " Créer utilisateur "
orig_roles = set ( )
else :
submitlabel = " Modifier utilisateur "
2021-07-03 16:19:42 +02:00
if " roles_string " in initvalues :
initvalues [ " roles " ] = initvalues [ " roles_string " ] . split ( " , " )
else :
initvalues [ " roles " ] = [ ]
2021-07-27 16:55:50 +02:00
if " date_expiration " in initvalues :
2021-08-09 14:29:03 +02:00
initvalues [ " date_expiration " ] = (
2021-12-29 11:26:54 +01:00
the_user . date_expiration . strftime ( " %d / % m/ % Y " )
if the_user . date_expiration
else " "
2021-08-09 14:29:03 +02:00
)
2021-12-29 11:26:54 +01:00
initvalues [ " status " ] = " " if the_user . active else " old "
2021-07-03 16:19:42 +02:00
orig_roles = { # set des roles existants avant édition
UserRole . role_dept_from_string ( role_dept )
for role_dept in initvalues [ " roles " ]
2021-09-29 10:27:49 +02:00
if role_dept
2021-07-03 16:19:42 +02:00
}
if not initvalues [ " active " ] :
editable_roles_set = set ( ) # can't change roles of a disabled user
2021-09-28 16:20:15 +02:00
editable_roles_strings = {
r . name + " _ " + ( dept or " " ) for ( r , dept ) in editable_roles_set
}
orig_roles_strings = { r . name + " _ " + ( dept or " " ) for ( r , dept ) in orig_roles }
2021-06-28 10:45:00 +02:00
# add existing user roles
2021-07-03 16:19:42 +02:00
displayed_roles = list ( editable_roles_set . union ( orig_roles ) )
2021-10-01 23:48:11 +02:00
displayed_roles . sort ( key = lambda x : ( x [ 1 ] or " " , x [ 0 ] . name or " " ) )
2021-09-28 16:20:15 +02:00
displayed_roles_strings = [
r . name + " _ " + ( dept or " " ) for ( r , dept ) in displayed_roles
2021-07-03 16:19:42 +02:00
]
2021-09-28 16:20:15 +02:00
displayed_roles_labels = [ f " { dept } : { r . name } " for ( r , dept ) in displayed_roles ]
2022-01-04 20:03:38 +01:00
disabled_roles = { } # pour désactiver les roles que l'on ne peut pas éditer
2021-07-03 16:19:42 +02:00
for i in range ( len ( displayed_roles_strings ) ) :
if displayed_roles_strings [ i ] not in editable_roles_strings :
2021-06-28 10:45:00 +02:00
disabled_roles [ i ] = True
descr = [
( " edit " , { " input_type " : " hidden " , " default " : edit } ) ,
( " nom " , { " title " : " Nom " , " size " : 20 , " allow_null " : False } ) ,
( " prenom " , { " title " : " Prénom " , " size " : 20 , " allow_null " : False } ) ,
]
2021-07-03 16:19:42 +02:00
if current_user . user_name != user_name :
# no one can change its own status
2021-06-28 10:45:00 +02:00
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 ,
2021-10-15 15:12:46 +02:00
" explanation " : " nom utilisé pour la connexion. Doit être unique parmi tous les utilisateurs. "
2021-10-15 15:34:10 +02:00
" Lettres ou chiffres uniquement. " ,
2021-10-15 15:12:46 +02:00
} ,
) ,
( " formsemestre_id " , { " input_type " : " hidden " } ) ,
2021-06-28 10:45:00 +02:00
(
2021-09-11 22:46:37 +02:00
" password " ,
2021-06-28 10:45:00 +02:00
{
" title " : " Mot de passe " ,
" input_type " : " password " ,
" size " : 14 ,
2021-08-28 16:01:41 +02:00
" allow_null " : True ,
" explanation " : " optionnel, l ' utilisateur pourra le saisir avec son mail " ,
2021-06-28 10:45:00 +02:00
} ,
) ,
(
2021-09-11 22:46:37 +02:00
" password2 " ,
2021-06-28 10:45:00 +02:00
{
" title " : " Confirmer mot de passe " ,
" input_type " : " password " ,
" size " : 14 ,
2021-08-28 16:01:41 +02:00
" allow_null " : True ,
2021-06-28 10:45:00 +02:00
} ,
) ,
]
else :
descr + = [
(
" user_name " ,
{ " input_type " : " hidden " , " default " : initvalues [ " user_name " ] } ,
) ,
2021-07-03 16:19:42 +02:00
( " user_name " , { " input_type " : " hidden " , " default " : initvalues [ " user_name " ] } ) ,
2021-06-28 10:45:00 +02:00
]
descr + = [
(
" email " ,
{
" title " : " e-mail " ,
" input_type " : " text " ,
2021-08-28 16:01:41 +02:00
" explanation " : " requis, doit fonctionner " ,
2021-06-28 10:45:00 +02:00
" size " : 20 ,
2021-08-28 16:01:41 +02:00
" allow_null " : False ,
2021-06-28 10:45:00 +02:00
} ,
)
]
2021-10-15 19:17:40 +02:00
if not edit : # options création utilisateur
descr + = [
(
" welcome " ,
{
" title " : " Message d ' accueil " ,
" input_type " : " checkbox " ,
" explanation " : " Envoie un mail d ' accueil à l ' utilisateur. " ,
" labels " : ( " " , ) ,
" allowed_values " : ( " 1 " , ) ,
" default " : " 1 " ,
} ,
) ,
(
" reset_password " ,
{
" title " : " " ,
" input_type " : " checkbox " ,
" explanation " : " indiquer par mail de changer le mot de passe initial " ,
" labels " : ( " " , ) ,
" allowed_values " : ( " 1 " , ) ,
" default " : " 1 " ,
# "attributes": ["style='margin-left:20pt'"],
} ,
) ,
]
2021-12-29 11:26:54 +01:00
# Si auth n'a pas de departement (admin global)
# propose de choisir librement le dept du nouvel utilisateur
# sinon, menu proposant l'ensembe des départements dans lesquels
# nous avons la permission ScoUserAdmin + le dept actuel de l'utilisateur
# modifié.
2021-06-28 10:45:00 +02:00
if not auth_dept :
descr . append (
(
" dept " ,
{
" title " : " Département " ,
" input_type " : " text " ,
" size " : 12 ,
" allow_null " : True ,
2021-12-29 11:26:54 +01:00
" explanation " : """ département de rattachement 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 )
""" ,
2021-06-28 10:45:00 +02:00
} ,
)
)
can_choose_dept = True
else :
2021-12-29 11:26:54 +01:00
selectable_dept_acronyms = set ( administrable_dept_acronyms )
2022-01-04 20:03:38 +01:00
if edit and the_user . dept is not None : # ajoute dept actuel de l'utilisateur
2021-12-29 11:26:54 +01:00
selectable_dept_acronyms | = { the_user . dept }
if len ( selectable_dept_acronyms ) > 1 :
can_choose_dept = True
selectable_dept_acronyms = sorted ( list ( selectable_dept_acronyms ) )
2021-06-28 10:45:00 +02:00
descr . append (
(
2021-12-29 11:26:54 +01:00
" dept " ,
2021-06-28 10:45:00 +02:00
{
2021-12-29 11:26:54 +01:00
" title " : " Département " ,
" input_type " : " menu " ,
" explanation " : """ département de rattachement de l ' utilisateur """ ,
" labels " : selectable_dept_acronyms ,
" allowed_values " : selectable_dept_acronyms ,
2021-06-28 10:45:00 +02:00
} ,
)
)
2021-12-29 11:26:54 +01:00
else : # pas de choix de département
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 ,
} ,
)
2021-06-28 10:45:00 +02:00
)
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 ,
2021-07-03 16:19:42 +02:00
" labels " : displayed_roles_labels ,
" allowed_values " : displayed_roles_strings ,
2021-06-28 10:45:00 +02:00
" disabled_items " : disabled_roles ,
} ,
) ,
(
" force " ,
{
" title " : " Ignorer les avertissements " ,
" input_type " : " checkbox " ,
" explanation " : " passer outre les avertissements (homonymes, etc) " ,
" labels " : ( " " , ) ,
" allowed_values " : ( " 1 " , ) ,
} ,
) ,
]
2021-09-28 09:14:04 +02:00
vals = scu . get_request_args ( )
2021-09-27 10:20:10 +02:00
if " tf_submitted " in vals and not " roles " in vals :
vals [ " roles " ] = [ ]
if " tf_submitted " in vals :
2021-06-28 10:45:00 +02:00
# Ajoute roles existants mais non modifiables (disabled dans le form)
2021-09-27 10:20:10 +02:00
vals [ " roles " ] = list (
set ( vals [ " roles " ] ) . union ( orig_roles_strings - editable_roles_strings )
2021-06-28 10:45:00 +02:00
)
tf = TrivialFormulator (
2021-09-18 10:10:02 +02:00
request . base_url ,
2021-09-27 10:20:10 +02:00
vals ,
2021-06-28 10:45:00 +02:00
descr ,
initvalues = initvalues ,
submitlabel = submitlabel ,
cancelbutton = " Annuler " ,
)
if tf [ 0 ] == 0 :
return " \n " . join ( H ) + " \n " + tf [ 1 ] + F
elif tf [ 0 ] == - 1 :
2021-07-31 18:01:10 +02:00
return flask . redirect ( scu . UsersURL ( ) )
2021-06-28 10:45:00 +02:00
else :
vals = tf [ 2 ]
2021-07-03 16:19:42 +02:00
roles = set ( vals [ " roles " ] ) . intersection ( editable_roles_strings )
2021-09-27 10:20:10 +02:00
if " edit " in vals :
edit = int ( vals [ " edit " ] )
2021-06-28 10:45:00 +02:00
else :
edit = 0
try :
force = int ( vals [ " force " ] [ 0 ] )
2021-07-27 16:55:50 +02:00
except ( IndexError , ValueError , TypeError ) :
2021-06-28 10:45:00 +02:00
force = 0
if edit :
user_name = initvalues [ " user_name " ]
else :
user_name = vals [ " user_name " ]
# ce login existe ?
err = None
2021-07-03 16:19:42 +02:00
users = sco_users . _user_list ( user_name )
2021-06-28 10:45:00 +02:00
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
2021-10-10 10:52:06 +02:00
ok , msg = sco_users . check_modif_user (
edit ,
2021-10-13 16:32:43 +02:00
enforce_optionals = not force ,
2021-10-10 10:52:06 +02:00
user_name = user_name ,
nom = vals [ " nom " ] ,
prenom = vals [ " prenom " ] ,
email = vals [ " email " ] ,
2021-10-15 15:34:10 +02:00
dept = vals . get ( " dept " , auth_dept ) ,
2021-10-10 10:52:06 +02:00
roles = vals [ " roles " ] ,
)
if not ok :
2021-10-10 21:09:27 +02:00
H . append ( tf_error_message ( msg ) )
2021-10-10 10:52:06 +02:00
return " \n " . join ( H ) + " \n " + tf [ 1 ] + F
2021-06-28 10:45:00 +02:00
2021-10-07 23:00:02 +02:00
if " date_expiration " in vals :
try :
if vals [ " date_expiration " ] :
vals [ " date_expiration " ] = datetime . datetime . strptime (
vals [ " date_expiration " ] , " %d / % m/ % Y "
)
2021-10-16 07:10:55 +02:00
if vals [ " date_expiration " ] < datetime . datetime . now ( ) :
H . append ( tf_error_message ( " date expiration passée " ) )
return " \n " . join ( H ) + " \n " + tf [ 1 ] + F
2021-10-07 23:00:02 +02:00
else :
vals [ " date_expiration " ] = None
except ValueError :
H . append ( tf_error_message ( " date expiration invalide " ) )
return " \n " . join ( H ) + " \n " + tf [ 1 ] + F
2021-09-11 22:46:37 +02:00
if edit : # modif utilisateur (mais pas password ni user_name !)
2021-07-09 13:45:10 +02:00
if ( not can_choose_dept ) and " dept " in vals :
2021-06-28 10:45:00 +02:00
del vals [ " dept " ]
2021-09-11 22:46:37 +02:00
if " password " in vals :
del vals [ " passwordd " ]
2021-07-09 13:45:10 +02:00
if " date_modif_passwd " in vals :
2021-06-28 10:45:00 +02:00
del vals [ " date_modif_passwd " ]
2021-07-09 13:45:10 +02:00
if " user_name " in vals :
2021-06-28 10:45:00 +02:00
del vals [ " user_name " ]
2021-07-09 13:45:10 +02:00
if ( current_user . user_name == user_name ) and " status " in vals :
2021-06-28 10:45:00 +02:00
del vals [ " status " ] # no one can't change its own status
2021-07-27 16:55:50 +02:00
if " status " in vals :
vals [ " active " ] = vals [ " status " ] == " "
2021-12-29 11:26:54 +01:00
# Département:
if auth_dept : # pas super-admin
if vals [ " dept " ] not in selectable_dept_acronyms :
del vals [ " dept " ] # ne change pas de dept
2021-06-28 10:45:00 +02:00
# traitement des roles: ne doit pas affecter les roles
# que l'on en controle pas:
2021-07-03 16:19:42 +02:00
for role in orig_roles_strings : # { "Ens_RT", "Secr_CJ", ... }
if role and not role in editable_roles_strings :
2021-06-28 10:45:00 +02:00
roles . add ( role )
2021-07-03 16:19:42 +02:00
vals [ " roles_string " ] = " , " . join ( roles )
2021-06-28 10:45:00 +02:00
# ok, edit
2021-07-03 16:19:42 +02:00
log ( " sco_users: editing %s by %s " % ( user_name , current_user . user_name ) )
log ( " sco_users: previous_values= %s " % initvalues )
log ( " sco_users: new_values= %s " % vals )
sco_users . user_edit ( user_name , vals )
2021-07-31 18:01:10 +02:00
return flask . redirect (
2021-07-03 16:19:42 +02:00
" user_info_page?user_name= %s &head_message=Utilisateur %s modifié "
2021-06-28 10:45:00 +02:00
% ( user_name , user_name )
)
else : # creation utilisateur
2021-08-22 00:17:47 +02:00
vals [ " roles_string " ] = " , " . join ( vals [ " roles " ] )
2021-06-28 10:45:00 +02:00
# 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
2021-10-15 15:12:46 +02:00
# Traitement initial (mode) : 3 cas
2021-10-30 12:03:21 +02:00
# cf énumération Mode
2021-10-15 15:12:46 +02:00
# A: envoi de welcome + procedure de reset
# B: envoi de welcome seulement (mot de passe saisie dans le formulaire)
2021-10-15 19:17:40 +02:00
# C: Aucun envoi (mot de passe saisi dans le formulaire)
2021-10-15 15:34:10 +02:00
if vals [ " welcome:list " ] == " 1 " :
if vals [ " reset_password:list " ] == " 1 " :
2021-10-30 12:03:21 +02:00
mode = Mode . WELCOME_AND_CHANGE_PASSWORD
2021-10-15 15:12:46 +02:00
else :
2021-10-30 12:03:21 +02:00
mode = Mode . WELCOME_ONLY
2021-10-15 15:12:46 +02:00
else :
2021-10-30 12:03:21 +02:00
mode = Mode . SILENT
2021-10-15 15:12:46 +02:00
2021-06-28 10:45:00 +02:00
# check passwords
2021-10-30 12:03:21 +02:00
if mode == Mode . WELCOME_AND_CHANGE_PASSWORD :
2021-10-15 15:34:10 +02:00
vals [ " password " ] = generate_password ( )
2021-10-15 15:12:46 +02:00
else :
if vals [ " password " ] :
if vals [ " password " ] != vals [ " password2 " ] :
msg = tf_error_message (
""" Les deux mots de passes ne correspondent pas ! """
)
return " \n " . join ( H ) + msg + " \n " + tf [ 1 ] + F
2021-10-15 19:17:40 +02:00
if not is_valid_password ( vals [ " password " ] ) :
2021-10-15 15:12:46 +02:00
msg = tf_error_message (
""" Mot de passe trop simple, recommencez ! """
)
return " \n " . join ( H ) + msg + " \n " + tf [ 1 ] + F
2021-12-29 11:26:54 +01:00
# Département:
2021-06-28 10:45:00 +02:00
if not can_choose_dept :
vals [ " dept " ] = auth_dept
2021-12-29 11:26:54 +01:00
else :
if auth_dept : # pas super-admin
if vals [ " dept " ] not in selectable_dept_acronyms :
raise ScoValueError ( " département invalide " )
2021-06-28 10:45:00 +02:00
# ok, go
2021-07-03 16:19:42 +02:00
log (
" sco_users: new_user %s by %s "
% ( vals [ " user_name " ] , current_user . user_name )
)
2021-12-29 11:26:54 +01:00
the_user = User ( )
the_user . from_dict ( vals , new_user = True )
db . session . add ( the_user )
2021-07-03 16:19:42 +02:00
db . session . commit ( )
2021-10-15 15:12:46 +02:00
# envoi éventuel d'un message
2021-10-30 12:03:21 +02:00
if mode == Mode . WELCOME_AND_CHANGE_PASSWORD or mode == Mode . WELCOME_ONLY :
if mode == Mode . WELCOME_AND_CHANGE_PASSWORD :
2021-12-29 11:26:54 +01:00
token = the_user . get_reset_password_token ( )
2021-10-15 15:12:46 +02:00
else :
token = None
send_email (
2021-10-15 19:17:40 +02:00
" [ScoDoc] Création de votre compte " ,
2021-10-30 12:09:35 +02:00
sender = from_mail , # current_app.config["ADMINS"][0],
2021-12-29 11:26:54 +01:00
recipients = [ the_user . email ] ,
text_body = render_template (
" email/welcome.txt " , user = the_user , token = token
) ,
2021-10-15 15:34:10 +02:00
html_body = render_template (
2021-12-29 11:26:54 +01:00
" email/welcome.html " , user = the_user , token = token
2021-10-15 15:34:10 +02:00
) ,
2021-10-15 15:12:46 +02:00
)
2021-07-31 18:01:10 +02:00
return flask . redirect (
2021-08-28 16:01:41 +02:00
url_for (
" users.user_info_page " ,
scodoc_dept = g . scodoc_dept ,
user_name = user_name ,
head_message = " Nouvel utilisateur créé " ,
)
2021-07-03 16:19:42 +02:00
)
2021-06-26 21:57:54 +02:00
2021-08-22 13:24:36 +02:00
@bp.route ( " /import_users_generate_excel_sample " )
@scodoc
@permission_required ( Permission . ScoUsersAdmin )
@scodoc7func
2021-09-27 10:20:10 +02:00
def import_users_generate_excel_sample ( ) :
2021-08-22 13:24:36 +02:00
" une feuille excel pour importation utilisateurs "
data = sco_import_users . generate_excel_sample ( )
2021-09-21 22:19:08 +02:00
return scu . send_file ( data , " ImportUtilisateurs " , scu . XLSX_SUFFIX , scu . XLSX_MIMETYPE )
2021-08-22 13:24:36 +02:00
@bp.route ( " /import_users_form " , methods = [ " GET " , " POST " ] )
@scodoc
@permission_required ( Permission . ScoUsersAdmin )
@scodoc7func
2021-09-27 10:20:10 +02:00
def import_users_form ( ) :
2021-08-22 13:24:36 +02:00
""" Import utilisateurs depuis feuille Excel """
head = html_sco_header . sco_header ( page_title = " Import utilisateurs " )
H = [
head ,
""" <h2>Téléchargement d ' une nouvelle liste d ' utilisateurs</h2>
< p style = " color: red " > A utiliser pour importer de < b > nouveaux < / b > utilisateurs ( enseignants ou secrétaires )
< / p >
< p >
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 .
< / p >
""" ,
]
help = """ <p class= " help " >
Lors de la creation des utilisateurs , les opérations suivantes sont effectuées :
< / p >
< ol class = " help " >
< li > vérification des données ; < / li >
< li > génération d ' un mot de passe alétoire pour chaque utilisateur;</li>
< li > création de chaque utilisateur ; < / li >
< li > envoi à chaque utilisateur de son < b > mot de passe initial par mail < / b > . < / li >
< / ol > """
H . append (
""" <ol><li><a class= " stdlink " href= " import_users_generate_excel_sample " >
Obtenir la feuille excel à remplir < / a > < / li > < li > """
)
F = html_sco_header . sco_footer ( )
tf = TrivialFormulator (
2021-09-18 10:10:02 +02:00
request . base_url ,
2021-09-27 16:42:14 +02:00
scu . get_request_args ( ) ,
2021-08-22 13:24:36 +02:00
(
(
" xlsfile " ,
{ " title " : " Fichier Excel: " , " input_type " : " file " , " size " : 40 } ,
) ,
2021-10-13 16:32:43 +02:00
(
" force " ,
{
" title " : " Ignorer les avertissements " ,
" input_type " : " checkbox " ,
" explanation " : " passer outre les avertissements (homonymes, etc) " ,
" labels " : ( " " , ) ,
" allowed_values " : ( " 1 " , ) ,
} ,
) ,
2021-08-22 13:24:36 +02:00
( " formsemestre_id " , { " input_type " : " hidden " } ) ,
) ,
submitlabel = " Télécharger " ,
)
if tf [ 0 ] == 0 :
return " \n " . join ( H ) + tf [ 1 ] + " </li></ol> " + help + F
elif tf [ 0 ] == - 1 :
2021-09-18 13:42:19 +02:00
return flask . redirect ( url_for ( " scolar.index_html " , docodc_dept = g . scodoc_dept ) )
2021-08-22 13:24:36 +02:00
else :
# IMPORT
2021-10-13 16:32:43 +02:00
ok , diag , nb_created = sco_import_users . import_excel_file (
tf [ 2 ] [ " xlsfile " ] , tf [ 2 ] [ " force " ]
)
2021-08-22 19:17:43 +02:00
H = [ html_sco_header . sco_header ( page_title = " Import utilisateurs " ) ]
H . append ( " <ul> " )
for d in diag :
H . append ( " <li> %s </li> " % d )
H . append ( " </ul> " )
2021-08-22 13:24:36 +02:00
if ok :
2021-10-13 15:27:19 +02:00
dest = url_for ( " users.index_html " , scodoc_dept = g . scodoc_dept , all_depts = 1 )
2021-08-22 19:17:43 +02:00
H . append ( " <p>Ok, Import terminé ( %s utilisateurs créés)!</p> " % nb_created )
2021-08-22 13:24:36 +02:00
H . append ( ' <p><a class= " stdlink " href= " %s " >Continuer</a></p> ' % dest )
else :
dest = url_for ( " users.import_users_form " , scodoc_dept = g . scodoc_dept )
H . append ( " <p>Erreur, importation annulée !</p> " )
H . append ( ' <p><a class= " stdlink " href= " %s " >Continuer</a></p> ' % dest )
return " \n " . join ( H ) + html_sco_header . sco_footer ( )
2021-06-26 21:57:54 +02:00
2021-06-27 12:11:39 +02:00
@bp.route ( " /user_info_page " )
2021-08-13 00:34:58 +02:00
@scodoc
2021-06-28 10:45:00 +02:00
@permission_required ( Permission . ScoUsersView )
2021-08-21 00:24:51 +02:00
@scodoc7func
2021-10-17 11:19:01 +02:00
def user_info_page ( user_name = 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 " )
return render_template (
" auth/user_info_page.html " ,
user = user ,
title = f " Utilisateur { user . user_name } " ,
Permission = Permission ,
dept = dept ,
)
2021-06-28 10:45:00 +02:00
@bp.route ( " /get_user_list_xml " )
2021-08-13 00:34:58 +02:00
@scodoc
2021-06-28 10:45:00 +02:00
@permission_required ( Permission . ScoView )
2021-08-21 00:24:51 +02:00
@scodoc7func
2021-09-27 10:20:10 +02:00
def get_user_list_xml ( dept = None , start = " " , limit = 25 ) :
2021-06-28 10:45:00 +02:00
""" Returns XML list of users with name (nomplogin) starting with start.
2021-09-03 18:17:43 +02:00
Used for forms auto - completion .
"""
# suggère seulement seulement les utilisateurs actifs:
2021-06-28 10:45:00 +02:00
userlist = sco_users . get_user_list ( dept = dept )
2021-11-06 11:04:47 +01:00
start = scu . suppress_accents ( str ( start ) ) . lower ( )
2021-06-28 10:45:00 +02:00
# TODO : à refaire avec une requete SQL #py3
# (et en json)
userlist = [
user
for user in userlist
2021-08-21 00:24:51 +02:00
if scu . suppress_accents ( ( user . nom or " " ) . lower ( ) ) . startswith ( start )
2021-06-28 10:45:00 +02:00
]
2021-07-11 13:03:13 +02:00
doc = ElementTree . Element ( " results " )
2021-06-28 10:45:00 +02:00
for user in userlist [ : limit ] :
2021-07-12 23:34:18 +02:00
x_rs = ElementTree . Element ( " rs " , id = str ( user . id ) , info = " " )
2021-07-11 13:03:13 +02:00
x_rs . text = user . get_nomplogin ( )
doc . append ( x_rs )
2021-09-27 10:20:10 +02:00
data = sco_xml . XML_HEADER + ElementTree . tostring ( doc ) . decode ( scu . SCO_ENCODING )
return scu . send_file ( data , mime = scu . XML_MIMETYPE )
2021-07-01 18:54:07 +02:00
2021-10-16 23:22:03 +02:00
@bp.route ( " /form_change_password " , methods = [ " GET " , " POST " ] )
2021-08-13 00:34:58 +02:00
@scodoc
2021-07-01 18:54:07 +02:00
@permission_required ( Permission . ScoView )
2021-08-21 00:24:51 +02:00
@scodoc7func
2021-09-27 10:20:10 +02:00
def form_change_password ( user_name = None ) :
2021-07-01 18:54:07 +02:00
""" Formulaire de changement mot de passe de l ' utilisateur user_name.
Un utilisateur peut toujours changer son propre mot de passe .
"""
if not user_name :
2021-10-16 23:22:03 +02:00
user = current_user
2021-07-01 18:54:07 +02:00
else :
2021-10-16 23:22:03 +02:00
user = User . query . filter_by ( user_name = user_name ) . first ( )
2021-07-01 18:54:07 +02:00
# check access
2021-10-16 23:22:03 +02:00
if not can_handle_passwd ( user ) :
return " \n " . join (
[
html_sco_header . sco_header ( user_check = False ) ,
" <p>Vous n ' avez pas la permission de changer ce mot de passe</p> " ,
html_sco_header . sco_footer ( ) ,
]
2021-07-01 18:54:07 +02:00
)
2021-10-16 23:22:03 +02:00
form = ChangePasswordForm ( user_name = user . user_name , email = user . email )
2021-10-17 11:19:01 +02:00
destination = url_for (
" users.user_info_page " ,
scodoc_dept = g . scodoc_dept ,
user_name = user_name ,
)
if request . method == " POST " and form . cancel . data : # cancel button clicked
return redirect ( destination )
2021-10-16 23:22:03 +02:00
if form . validate_on_submit ( ) :
messages = [ ]
if form . new_password . data != " " : # change password
user . set_password ( form . new_password . data )
messages . append ( " Mot de passe modifié " )
2021-10-17 12:15:24 +02:00
if form . email . data . strip ( ) != user . email : # change email
user . email = form . email . data . strip ( )
2021-10-16 23:22:03 +02:00
messages . append ( " Adresse email modifiée " )
db . session . commit ( )
flash ( " \n " . join ( messages ) )
2021-10-17 11:19:01 +02:00
return redirect ( destination )
return render_template (
2021-10-30 12:05:51 +02:00
" auth/change_password.html " ,
form = form ,
title = " Modification compte ScoDoc " ,
auth_username = current_user . user_name ,
2021-10-17 11:19:01 +02:00
)
2021-07-01 18:54:07 +02:00
@bp.route ( " /change_password " , methods = [ " POST " ] )
2021-08-13 00:34:58 +02:00
@scodoc
2021-07-01 18:54:07 +02:00
@permission_required ( Permission . ScoView )
2021-08-21 00:24:51 +02:00
@scodoc7func
2021-09-27 10:20:10 +02:00
def change_password ( user_name , password , password2 ) :
2021-07-01 18:54:07 +02:00
" Change the password for user given by user_name "
u = User . query . filter_by ( user_name = user_name ) . first ( )
# Check access permission
if not can_handle_passwd ( u ) :
# access denied
log (
2021-09-18 13:42:19 +02:00
" change_password: access denied (authuser= %s , user_name= %s ) "
% ( current_user , user_name )
2021-07-01 18:54:07 +02:00
)
raise AccessDenied ( " vous n ' avez pas la permission de changer ce mot de passe " )
H = [ ]
2021-07-29 10:19:00 +02:00
F = html_sco_header . sco_footer ( )
2021-07-01 18:54:07 +02:00
# check password
if password != password2 :
H . append (
""" <p>Les deux mots de passes saisis sont différents !</p>
< p > < a href = " form_change_password?user_name= %s " class = " stdlink " > Recommencer < / a > < / p > """
% user_name
)
else :
2021-10-15 19:17:40 +02:00
if not is_valid_password ( password ) :
2021-07-01 18:54:07 +02:00
H . append (
""" <p><b>ce mot de passe n \' est pas assez compliqué !</b><br/>(oui, il faut un mot de passe vraiment compliqué !)</p>
< p > < a href = " form_change_password?user_name= %s " class = " stdlink " > Recommencer < / a > < / p >
"""
% user_name
)
else :
# ok, strong password
db . session . add ( u )
u . set_password ( password )
db . session . commit ( )
#
# ici page simplifiee car on peut ne plus avoir
# le droit d'acceder aux feuilles de style
H . append (
" <h2>Changement effectué !</h2><p>Ne notez pas ce mot de passe, mais mémorisez le !</p><p>Rappel: il est <b>interdit</b> de communiquer son mot de passe à un tiers, même si c ' est un collègue de confiance !</p><p><b>Si vous n ' êtes pas administrateur, le système va vous redemander votre login et nouveau mot de passe au prochain accès.</b></p> "
)
return (
2021-10-10 21:09:27 +02:00
""" <?xml version= " 1.0 " encoding= " %s " ?>
2021-07-01 18:54:07 +02:00
< ! DOCTYPE html PUBLIC " -//W3C//DTD XHTML 1.0 Transitional//EN " " http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd " >
< html >
< head >
< title > Mot de passe changé < / title >
< meta http - equiv = " Content-Type " content = " text/html; charset= %s " / >
< body > < h1 > Mot de passe changé ! < / h1 >
"""
2021-10-10 21:09:27 +02:00
% ( scu . SCO_ENCODING , scu . SCO_ENCODING )
+ " \n " . join ( H )
+ ' <a href= " %s " class= " stdlink " >Continuer</a></body></html> '
% scu . ScoURL ( )
2021-07-01 18:54:07 +02:00
)
2021-07-29 16:31:15 +02:00
return html_sco_header . sco_header ( ) + " \n " . join ( H ) + F
2021-08-28 16:01:41 +02:00
@bp.route ( " /toggle_active_user/<user_name> " , methods = [ " GET " , " POST " ] )
@scodoc
@permission_required ( Permission . ScoUsersAdmin )
def toggle_active_user ( user_name : str = None ) :
""" Change active status of a user account """
u = User . query . filter_by ( user_name = user_name ) . first ( )
if not u :
raise ScoValueError ( " invalid user_name " )
form = DeactivateUserForm ( )
if (
2021-10-10 21:09:27 +02:00
request . method == " POST " and form . cancel . data
2021-08-28 16:01:41 +02:00
) : # if cancel button is clicked, the form.cancel.data will be True
# flash
return redirect ( url_for ( " users.index_html " , scodoc_dept = g . scodoc_dept ) )
if form . validate_on_submit ( ) :
u . active = not u . active
db . session . add ( u )
db . session . commit ( )
return redirect ( url_for ( " users.index_html " , scodoc_dept = g . scodoc_dept ) )
return render_template ( " auth/toogle_active_user.html " , form = form , u = u )