forked from ScoDoc/DocScoDoc
308 lines
10 KiB
Python
308 lines
10 KiB
Python
# -*- 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
|
|
#
|
|
##############################################################################
|
|
|
|
"""Import d'utilisateurs via fichier Excel
|
|
"""
|
|
import random, time
|
|
import re
|
|
|
|
from email.mime.multipart import MIMEMultipart
|
|
from email.mime.text import MIMEText
|
|
from email.header import Header
|
|
|
|
from app import db, Departement
|
|
from app.scodoc import sco_emails
|
|
import app.scodoc.sco_utils as scu
|
|
from app.scodoc.notes_log import log
|
|
from app.scodoc.sco_exceptions import AccessDenied, ScoValueError, ScoException
|
|
from app.scodoc import sco_excel
|
|
from app.scodoc import sco_preferences
|
|
from app.scodoc import sco_users
|
|
|
|
from flask import g
|
|
from flask_login import current_user
|
|
from app.auth.models import User, UserRole
|
|
|
|
TITLES = ("user_name", "nom", "prenom", "email", "roles", "dept")
|
|
COMMENTS = (
|
|
"""user_name:
|
|
Composé de lettres (minuscules ou majuscules), de chiffres ou du caractère _
|
|
""",
|
|
"""nom:
|
|
Maximum 64 caractères""",
|
|
"""prenom:
|
|
Maximum 64 caractères""",
|
|
"""email:
|
|
Maximum 120 caractères""",
|
|
"""roles:
|
|
un plusieurs rôles séparés par ','
|
|
chaque role est fait de 2 composantes séparées par _:
|
|
1. Le role (Ens, Secr ou Admin)
|
|
2. Le département (en majuscule)
|
|
Exemple: "Ens_RT,Admin_INFO"
|
|
""",
|
|
"""dept:
|
|
Le département d'appartenance du l'utillsateur. Laisser vide si l'utilisateur intervient dans plusieurs dépatements
|
|
""",
|
|
)
|
|
|
|
|
|
def generate_excel_sample():
|
|
"""generates an excel document suitable to import users"""
|
|
style = sco_excel.excel_make_style(bold=True)
|
|
titles = TITLES
|
|
titles_styles = [style] * len(titles)
|
|
return sco_excel.excel_simple_table(
|
|
titles=titles,
|
|
titles_styles=titles_styles,
|
|
sheet_name="Utilisateurs ScoDoc",
|
|
comments=COMMENTS,
|
|
)
|
|
|
|
|
|
def import_excel_file(datafile):
|
|
"Create users from Excel file"
|
|
# Check current user privilege
|
|
auth_dept = current_user.dept
|
|
auth_name = str(current_user)
|
|
if not current_user.is_administrator():
|
|
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
|
|
# Récupération des informations sur l'utilisateur courant
|
|
log("sco_import_users.import_excel_file by %s" % auth_name)
|
|
|
|
exceldata = datafile.read()
|
|
if not exceldata:
|
|
raise ScoValueError("Ficher excel vide ou invalide")
|
|
_, data = sco_excel.excel_bytes_to_list(exceldata)
|
|
if not data: # probably a bug
|
|
raise ScoException("import_excel_file: empty file !")
|
|
# 1- --- check title line
|
|
fs = [scu.stripquotes(s).lower() for s in data[0]]
|
|
log("excel: fs='%s'\ndata=%s" % (str(fs), str(data)))
|
|
# check cols
|
|
cols = {}.fromkeys(TITLES)
|
|
unknown = []
|
|
for tit in fs:
|
|
if tit not in cols:
|
|
unknown.append(tit)
|
|
else:
|
|
del cols[tit]
|
|
if cols or unknown:
|
|
raise ScoValueError(
|
|
"colonnes incorrectes (on attend %d, et non %d) <br/> (colonnes manquantes: %s, colonnes invalides: %s)"
|
|
% (len(TITLES), len(fs), list(cols.keys()), unknown)
|
|
)
|
|
# ok, same titles...
|
|
U = []
|
|
for line in data[1:]:
|
|
d = {}
|
|
for i in range(len(fs)):
|
|
d[fs[i]] = line[i]
|
|
U.append(d)
|
|
|
|
return import_users(U, auth_dept=auth_dept)
|
|
|
|
|
|
def import_users(users, auth_dept=""):
|
|
"""Import des utilisateurs:
|
|
Pour chaque utilisateur à créer:
|
|
- vérifier données
|
|
- générer mot de passe aléatoire
|
|
- créer utilisateur et mettre le mot de passe
|
|
- envoyer mot de passe par mail
|
|
|
|
En cas d'erreur: supprimer tous les utilisateurs que l'on vient de créer.
|
|
"""
|
|
|
|
def append_msg(msg):
|
|
msg_list.append("Ligne %s : %s" % (line, msg))
|
|
|
|
if len(users) == 0:
|
|
ok = False
|
|
msg_list = ["Feuilles vide ou illisible"]
|
|
else:
|
|
created = [] # liste de uid créés
|
|
msg_list = []
|
|
line = 1 # satr from excel line #2
|
|
ok = True
|
|
try:
|
|
for u in users:
|
|
line = line + 1
|
|
ok, msg = sco_users.check_modif_user(
|
|
0,
|
|
user_name=u["user_name"],
|
|
nom=u["nom"],
|
|
prenom=u["prenom"],
|
|
email=u["email"],
|
|
roles=u["roles"],
|
|
)
|
|
if not ok:
|
|
append_msg("identifiant '%s' %s" % (u["user_name"], msg))
|
|
# raise ScoValueError(
|
|
# "données invalides pour %s: %s" % (u["user_name"], msg)
|
|
# )
|
|
u["passwd"] = generate_password()
|
|
#
|
|
# check identifiant
|
|
if not re.match(r"^[a-zA-Z0-9@\\\-_\\\.]*$", u["user_name"]):
|
|
ok = False
|
|
append_msg(
|
|
"identifiant '%s' invalide (pas d'accents ni de caractères spéciaux)"
|
|
% u["user_name"]
|
|
)
|
|
elif len(u["user_name"]) > 64:
|
|
ok = False
|
|
append_msg(
|
|
"identifiant '%s' trop long (64 caractères)" % u["user_name"]
|
|
)
|
|
if len(u["nom"]) > 64:
|
|
ok = False
|
|
append_msg("nom '%s' trop long (64 caractères)" % u["nom"])
|
|
if len(u["prenom"]) > 64:
|
|
ok = False
|
|
append_msg("prenom '%s' trop long (64 caractères)" % u["prenom"])
|
|
if len(u["email"]) > 120:
|
|
ok = False
|
|
append_msg("email '%s' trop long (120 caractères)" % u["email"])
|
|
# check département
|
|
if u["dept"] != "":
|
|
dept = Departement.query.filter_by(acronym=u["dept"]).first()
|
|
if dept is None:
|
|
ok = False
|
|
append_msg("département '%s' inexistant" % u["dept"])
|
|
for role in u["roles"].split(","):
|
|
try:
|
|
_, _ = UserRole.role_dept_from_string(role)
|
|
except ScoValueError as value_error:
|
|
ok = False
|
|
append_msg("role : %s " % role)
|
|
# Création de l'utilisateur (via SQLAlchemy)
|
|
if ok:
|
|
user = User()
|
|
user.from_dict(u, new_user=True)
|
|
db.session.add(user)
|
|
created.append(u["user_name"])
|
|
db.session.commit()
|
|
except ScoValueError as value_error:
|
|
log("import_users: exception: abort create %s" % str(created))
|
|
db.session.rollback()
|
|
raise ScoValueError(msg) # re-raise exception
|
|
|
|
for user in users:
|
|
mail_password(user)
|
|
return ok, msg_list
|
|
|
|
|
|
# --------- Génération du mot de passe initial -----------
|
|
# Adapté de http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440564
|
|
# Alphabet tres simple pour des mots de passe simples...
|
|
|
|
|
|
ALPHABET = r"""ABCDEFGHIJKLMNPQRSTUVWXYZ123456789123456789AEIOU"""
|
|
PASSLEN = 6
|
|
RNG = random.Random(time.time())
|
|
|
|
|
|
def generate_password():
|
|
"""This function creates a pseudo random number generator object, seeded with
|
|
the cryptographic hash of the passString. The contents of the character set
|
|
is then shuffled and a selection of passLength words is made from this list.
|
|
This selection is returned as the generated password."""
|
|
l = list(ALPHABET) # make this mutable so that we can shuffle the characters
|
|
RNG.shuffle(l) # shuffle the character set
|
|
# pick up only a subset from the available characters:
|
|
return "".join(RNG.sample(l, PASSLEN))
|
|
|
|
|
|
def mail_password(u, context=None, reset=False):
|
|
"Send password by email"
|
|
if not u["email"]:
|
|
return
|
|
|
|
u[
|
|
"url"
|
|
] = (
|
|
scu.ScoURL()
|
|
) # TODO set auth page URL ? (shared by all departments) ../auth/login
|
|
|
|
txt = (
|
|
"""
|
|
Bonjour %(prenom)s %(nom)s,
|
|
|
|
"""
|
|
% u
|
|
)
|
|
if reset:
|
|
txt += (
|
|
"""
|
|
votre mot de passe ScoDoc a été ré-initialisé.
|
|
|
|
Le nouveau mot de passe est: %(passwd)s
|
|
Votre nom d'utilisateur est %(user_name)s
|
|
|
|
Vous devrez changer ce mot de passe lors de votre première connexion
|
|
sur %(url)s
|
|
"""
|
|
% u
|
|
)
|
|
else:
|
|
txt += (
|
|
"""
|
|
vous avez été déclaré comme utilisateur du logiciel de gestion de scolarité ScoDoc.
|
|
|
|
Votre nom d'utilisateur est %(user_name)s
|
|
Votre mot de passe est: %(passwd)s
|
|
|
|
Le logiciel est accessible sur: %(url)s
|
|
|
|
Vous êtes invité à changer ce mot de passe au plus vite (cliquez sur
|
|
votre nom en haut à gauche de la page d'accueil).
|
|
"""
|
|
% u
|
|
)
|
|
|
|
txt += (
|
|
"""
|
|
|
|
ScoDoc est un logiciel libre développé à l'Université Paris 13 par Emmanuel Viennet.
|
|
Pour plus d'informations sur ce logiciel, voir %s
|
|
|
|
"""
|
|
% scu.SCO_WEBSITE
|
|
)
|
|
msg = MIMEMultipart()
|
|
if reset:
|
|
msg["Subject"] = Header("Mot de passe ScoDoc", scu.SCO_ENCODING)
|
|
else:
|
|
msg["Subject"] = Header("Votre accès ScoDoc", scu.SCO_ENCODING)
|
|
msg["From"] = sco_preferences.get_preference("email_from_addr")
|
|
msg["To"] = u["email"]
|
|
msg.epilogue = ""
|
|
txt = MIMEText(txt, "plain", scu.SCO_ENCODING)
|
|
msg.attach(txt)
|
|
# sco_emails.sendEmail(msg) # TODO ScoDoc9 pending function
|