début implémentation gestion utilisateurs

This commit is contained in:
Emmanuel Viennet 2021-06-26 21:57:54 +02:00
parent 769671b0d4
commit f991ffdca5
21 changed files with 455 additions and 45 deletions

View File

@ -51,9 +51,11 @@ Puis d'installer Flask comme suit:
Installation: Installation:
apt-get install libcrack2-dev
pip install flask pip install flask
# et pas mal d'autres paquets # et pas mal d'autres paquets
donc utiliser: donc utiliser:
pip install -r requirements.txt pip install -r requirements.txt
@ -62,6 +64,43 @@ pour régénerer ce fichier:
pip freeze > requirements.txt pip freeze > requirements.txt
## Bases de données
ScoDoc8 utilise les bases de département de ScoDoc7, mais une nouvelle base
utilisateurs nommée `SCO8USERS` (au lieu de `SCOUSERS`), qui est manipulée
via `sqlalchemy`.
### Configuration de la base utilisateurs
On va créer une base nommée `SCO8USERS`, appartenant à l'utilisateur (aka role) postgres `scodoc`.
Cet utilisateur sera créé ainsi sur le serveur linux::
su - postgres
createuser --no-createdb --no-createrole --pwprompt scodoc
Puis création de la base (en tant qu'utilisateur `postgres`):
createdb -O scodoc SCO8USERS
### Initialisation base par flask
En tant qu'utilisateur `www-data` (compat. avec ScoDoc7):
flask db init
flask db migrate -m "users and roles tables"
flask db upgrade
Puis initialisation de l'appli:
flask user-db-init
On peut ensuite créer des utilisateurs tests avec:
flask user-create toto Ens RT
flask user-create tata Ens RT
flask user-create tutu Ens Info
flask user-create titi Secr RT
### Bidouilles temporaires ### Bidouilles temporaires
Installer le bon vieux `pyExcelerator` dans l'environnement: Installer le bon vieux `pyExcelerator` dans l'environnement:

View File

@ -68,12 +68,15 @@ def create_app(config_class=Config):
from app.views import scolar_bp from app.views import scolar_bp
from app.views import notes_bp from app.views import notes_bp
from app.views import users_bp
from app.views import absences_bp from app.views import absences_bp
# https://scodoc.fr/ScoDoc/RT/Scolarite/... # https://scodoc.fr/ScoDoc/RT/Scolarite/...
app.register_blueprint(scolar_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite") app.register_blueprint(scolar_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite")
# https://scodoc.fr/ScoDoc/RT/Scolarite/Notes/... # https://scodoc.fr/ScoDoc/RT/Scolarite/Notes/...
app.register_blueprint(notes_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Notes") app.register_blueprint(notes_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Notes")
# https://scodoc.fr/ScoDoc/RT/Scolarite/Users/...
app.register_blueprint(users_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Users")
# https://scodoc.fr/ScoDoc/RT/Scolarite/Absences/... # https://scodoc.fr/ScoDoc/RT/Scolarite/Absences/...
app.register_blueprint( app.register_blueprint(
absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences" absences_bp, url_prefix="/ScoDoc/<scodoc_dept>/Scolarite/Absences"

View File

@ -16,14 +16,14 @@ _l = _
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
username = StringField(_l("Username"), validators=[DataRequired()]) user_name = StringField(_l("Username"), validators=[DataRequired()])
password = PasswordField(_l("Password"), validators=[DataRequired()]) password = PasswordField(_l("Password"), validators=[DataRequired()])
remember_me = BooleanField(_l("Remember Me")) remember_me = BooleanField(_l("Remember Me"))
submit = SubmitField(_l("Sign In")) submit = SubmitField(_l("Sign In"))
class UserCreationForm(FlaskForm): class UserCreationForm(FlaskForm):
username = StringField(_l("Username"), validators=[DataRequired()]) user_name = StringField(_l("Username"), validators=[DataRequired()])
email = StringField(_l("Email"), validators=[DataRequired(), Email()]) email = StringField(_l("Email"), validators=[DataRequired(), Email()])
password = PasswordField(_l("Password"), validators=[DataRequired()]) password = PasswordField(_l("Password"), validators=[DataRequired()])
password2 = PasswordField( password2 = PasswordField(
@ -31,10 +31,10 @@ class UserCreationForm(FlaskForm):
) )
submit = SubmitField(_l("Register")) submit = SubmitField(_l("Register"))
def validate_username(self, username): def validate_user_name(self, user_name):
user = User.query.filter_by(username=username.data).first() user = User.query.filter_by(user_name=user_name.data).first()
if user is not None: if user is not None:
raise ValidationError(_("Please use a different username.")) raise ValidationError(_("Please use a different user_name."))
def validate_email(self, email): def validate_email(self, email):
user = User.query.filter_by(email=email.data).first() user = User.query.filter_by(email=email.data).first()

View File

@ -26,13 +26,23 @@ class User(UserMixin, db.Model):
"""ScoDoc users, handled by Flask / SQLAlchemy""" """ScoDoc users, handled by Flask / SQLAlchemy"""
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True) user_name = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True)
nom = db.Column(db.String(64))
prenom = db.Column(db.String(64))
dept = db.Column(db.String(32), index=True)
is_old = db.Column(db.Boolean, default=False, index=True)
password_hash = db.Column(db.String(128)) password_hash = db.Column(db.String(128))
about_me = db.Column(db.String(140))
last_seen = db.Column(db.DateTime, default=datetime.utcnow) last_seen = db.Column(db.DateTime, default=datetime.utcnow)
date_modif_passwd = db.Column(db.DateTime, default=datetime.utcnow)
date_created = db.Column(db.DateTime, default=datetime.utcnow)
date_expiration = db.Column(db.DateTime, default=None)
passwd_temp = db.Column(db.Boolean, default=False)
token = db.Column(db.String(32), index=True, unique=True) token = db.Column(db.String(32), index=True, unique=True)
token_expiration = db.Column(db.DateTime) token_expiration = db.Column(db.DateTime)
roles = db.relationship("Role", secondary="user_role", viewonly=True) roles = db.relationship("Role", secondary="user_role", viewonly=True)
Permission = Permission Permission = Permission
@ -52,10 +62,10 @@ class User(UserMixin, db.Model):
current_app.logger.info("creating user with roles={}".format(self.roles)) current_app.logger.info("creating user with roles={}".format(self.roles))
def __repr__(self): def __repr__(self):
return "<User {}>".format(self.username) return "<User {u.user_name} id={u.id} dept={u.dept}>".format(u=self)
def __str__(self): def __str__(self):
return self.username return self.user_name
def set_password(self, password): def set_password(self, password):
"Set password" "Set password"
@ -91,17 +101,32 @@ class User(UserMixin, db.Model):
def to_dict(self, include_email=False): def to_dict(self, include_email=False):
data = { data = {
"date_expiration": self.date_expiration.isoformat() + "Z"
if self.date_expiration
else None,
"date_modif_passwd": self.date_modif_passwd.isoformat() + "Z"
if self.date_modif_passwd
else None,
"date_created": self.date_created.isoformat() + "Z"
if self.date_created
else None,
"dept": self.dept,
"id": self.id, "id": self.id,
"username": self.username, "is_old": self.is_old,
"status_txt": "(ancien)" if self.is_old else "",
"last_seen": self.last_seen.isoformat() + "Z", "last_seen": self.last_seen.isoformat() + "Z",
"about_me": self.about_me, "nom": self.nom,
"prenom": self.prenom,
"roles": self.roles,
"roles_string": self.get_roles_string(),
"user_name": self.user_name,
} }
if include_email: if include_email:
data["email"] = self.email data["email"] = self.email
return data return data
def from_dict(self, data, new_user=False): def from_dict(self, data, new_user=False):
for field in ["username", "email", "about_me"]: for field in ["user_name", "non", "prenom", "dept", "status", "email"]:
if field in data: if field in data:
setattr(self, field, data[field]) setattr(self, field, data[field])
if new_user and "password" in data: if new_user and "password" in data:
@ -169,6 +194,12 @@ class User(UserMixin, db.Model):
for role in self.roles: for role in self.roles:
yield role yield role
def get_roles_string(self):
"""string repr. of user's roles (with depts)
e.g. "EnsRT, EnsInfo, SecrCJ"
"""
return ", ".join("{r.role.name}{r.dept}".format(r=r) for r in self.user_roles)
def is_administrator(self): def is_administrator(self):
return self.has_permission(Permission.ScoSuperAdmin, None) return self.has_permission(Permission.ScoSuperAdmin, None)

View File

@ -15,9 +15,11 @@ from app.auth.forms import (
ResetPasswordRequestForm, ResetPasswordRequestForm,
ResetPasswordForm, ResetPasswordForm,
) )
from app.auth.models import Permission
from app.auth.models import User from app.auth.models import User
from app.auth.email import send_password_reset_email from app.auth.email import send_password_reset_email
from app.decorators import scodoc7func, admin_required from app.decorators import admin_required
_ = lambda x: x # sans babel _ = lambda x: x # sans babel
_l = _ _l = _
@ -29,9 +31,9 @@ def login():
return redirect(url_for("main.index")) return redirect(url_for("main.index"))
form = LoginForm() form = LoginForm()
if form.validate_on_submit(): if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first() user = User.query.filter_by(user_name=form.user_name.data).first()
if user is None or not user.check_password(form.password.data): if user is None or not user.check_password(form.password.data):
flash(_("Invalid username or password")) flash(_("Invalid user_name or password"))
return redirect(url_for("auth.login")) return redirect(url_for("auth.login"))
login_user(user, remember=form.remember_me.data) login_user(user, remember=form.remember_me.data)
next_page = request.args.get("next") next_page = request.args.get("next")
@ -53,11 +55,11 @@ def create_user():
"Form creating new user" "Form creating new user"
form = UserCreationForm() form = UserCreationForm()
if form.validate_on_submit(): if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data) user = User(user_name=form.user_name.data, email=form.email.data)
user.set_password(form.password.data) user.set_password(form.password.data)
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
flash("User {} created".format(user.username)) flash("User {} created".format(user.user_name))
return redirect(url_for("main.index")) return redirect(url_for("main.index"))
return render_template( return render_template(
"auth/register.html", title=u"Création utilisateur", form=form "auth/register.html", title=u"Création utilisateur", form=form

View File

@ -26,7 +26,7 @@ class ZUser(object):
def __init__(self): def __init__(self):
"create, based on `flask_login.current_user`" "create, based on `flask_login.current_user`"
self.username = current_user.username self.username = current_user.user_name
def __str__(self): def __str__(self):
return self.username return self.username

View File

@ -27,6 +27,9 @@
"""Page accueil département (liste des semestres, etc) """Page accueil département (liste des semestres, etc)
""" """
from flask import g
import app.scodoc.sco_utils as scu import app.scodoc.sco_utils as scu
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
@ -37,6 +40,7 @@ from app.scodoc import sco_modalites
from app.scodoc import sco_news from app.scodoc import sco_news
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_up_to_date from app.scodoc import sco_up_to_date
from app.scodoc import sco_users
def index_html(context, REQUEST=None, showcodes=0, showsemtable=0): def index_html(context, REQUEST=None, showcodes=0, showsemtable=0):
@ -90,7 +94,7 @@ def index_html(context, REQUEST=None, showcodes=0, showsemtable=0):
sem["groupicon"] = emptygroupicon sem["groupicon"] = emptygroupicon
# S'il n'y a pas d'utilisateurs dans la base, affiche message # S'il n'y a pas d'utilisateurs dans la base, affiche message
if not context.Users.get_userlist(): if not sco_users.get_user_list(dept=g.scodoc_dept):
H.append( H.append(
"""<h2>Aucun utilisateur défini !</h2><p>Pour définir des utilisateurs """<h2>Aucun utilisateur défini !</h2><p>Pour définir des utilisateurs
<a href="Users">passez par la page Utilisateurs</a>. <a href="Users">passez par la page Utilisateurs</a>.

View File

@ -43,6 +43,7 @@ from app.scodoc import sco_compute_moy
from app.scodoc import sco_edit_matiere from app.scodoc import sco_edit_matiere
from app.scodoc import sco_edit_module from app.scodoc import sco_edit_module
from app.scodoc import sco_edit_ue from app.scodoc import sco_edit_ue
from app.scodoc import sco_etud
from app.scodoc import sco_evaluations from app.scodoc import sco_evaluations
from app.scodoc import sco_formations from app.scodoc import sco_formations
from app.scodoc import sco_formsemestre from app.scodoc import sco_formsemestre
@ -52,7 +53,7 @@ from app.scodoc import sco_parcours_dut
from app.scodoc import sco_permissions_check from app.scodoc import sco_permissions_check
from app.scodoc import sco_portal_apogee from app.scodoc import sco_portal_apogee
from app.scodoc import sco_preferences from app.scodoc import sco_preferences
from app.scodoc import sco_etud from app.scodoc import sco_users
def _default_sem_title(F): def _default_sem_title(F):
@ -139,7 +140,7 @@ def do_formsemestre_createwithmodules(context, REQUEST=None, edit=False):
) )
# Liste des enseignants avec forme pour affichage / saisie avec suggestion # Liste des enseignants avec forme pour affichage / saisie avec suggestion
userlist = context.Users.get_userlist() userlist = [sco_users.user_info(u) for u in sco_users.get_user_list()]
login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)" login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)"
for u in userlist: for u in userlist:
login2display[u["user_name"]] = u["nomplogin"] login2display[u["user_name"]] = u["nomplogin"]

View File

@ -3,6 +3,8 @@
"""Functions checking permissions for some common operations """Functions checking permissions for some common operations
""" """
from flask import g
from flask_login import current_user
import app.scodoc.notesdb as ndb import app.scodoc.notesdb as ndb
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
@ -188,3 +190,26 @@ def can_change_groups(context, REQUEST, formsemestre_id):
if uid in sem["responsables"]: if uid in sem["responsables"]:
return True return True
return False return False
def can_handle_passwd(user, allow_admindepts=False):
"""True if the current user can see or change passwd info of user.
If allow_admindepts, allow Admin from all depts (so they can view users from other depts
and add roles to them).
user is a User instance.
"""
if current_user.is_administrator():
return True # super admin
# Anyone can change his own passwd (or see his informations)
if user.user_name == current_user.user_name:
return True
# If don't have permission in the current dept, abort
if not current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept):
return False
# Now check that current_user can manage users from this departement
if not current_user.dept:
return True # if no dept, can access users from all depts !
if (current_user.dept == user.dept) or allow_admindepts:
return True
else:
return False

View File

@ -28,12 +28,48 @@
"""Fonctions sur les utilisateurs """Fonctions sur les utilisateurs
""" """
# Anciennement dans ZScoUsers.py, séparé pour migration # Anciennement ZScoUsers.py, fonctions de gestion des données réécrite avec flask/SQLAlchemy
import string
import re
import time
import md5
import base64
import jaxml
from flask import current_app, url_for, g
from flask_login import current_user
import cracklib # pylint: disable=import-error import cracklib # pylint: disable=import-error
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_import_users
from app.scodoc import sco_preferences
from app.scodoc.gen_tables import GenTable
from app.scodoc.notes_log import log
from app.scodoc.sco_permissions_check import can_handle_passwd
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 import app.scodoc.sco_utils as scu
from app.scodoc.sco_exceptions import (
AccessDenied,
ScoException,
ScoValueError,
ScoInvalidDateError,
ScoLockedFormError,
ScoGenError,
)
def is_valid_password(cleartxt): def is_valid_password(cleartxt):
"""Check password. """Check password.
@ -50,3 +86,170 @@ def is_valid_password(cleartxt):
return True return True
except ValueError: except ValueError:
return False return False
# ---------------
# ---------------
# XXX TODO supprimer ndb.GetUsersDBConnexion
def index_html(context, REQUEST, all_depts=False, with_olds=False, format="html"):
"gestion utilisateurs..."
all_depts = int(all_depts)
with_olds = int(with_olds)
H = [html_sco_header.html_sem_header(context, REQUEST, "Gestion des utilisateurs")]
H.append("<h2>Gestion des utilisateurs</h2>")
if current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept):
H.append(
'<p><a href="{}" class="stdlink">Ajouter un utilisateur</a>'.format(
url_for("users.create_user_form", scodoc_dept=g.scodoc_dept).encode(
scu.SCO_ENCODING
) # sco8
)
)
H.append(
'&nbsp;&nbsp; <a href="{}" class="stdlink">Importer des utilisateurs</a></p>'.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_olds:
olds_checked = "checked"
else:
olds_checked = ""
H.append(
"""<p><form name="f" action="%s" method="get">
<input type="checkbox" name="all_depts" value="1" onchange="document.f.submit();" %s>Tous les départements</input>
<input type="checkbox" name="with_olds" value="1" onchange="document.f.submit();" %s>Avec anciens utilisateurs</input>
</form></p>"""
% (REQUEST.URL0, checked, olds_checked)
)
L = list_users(
context,
g.scodoc_dept,
all_depts=all_depts,
with_olds=with_olds,
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_olds=False, # inclut les anciens utilisateurs (status "old")
format="html",
with_links=True,
REQUEST=None,
):
"List users, returns a table in the specified format"
if dept and not all_depts:
users = get_user_list(dept=dept, with_olds=with_olds)
comm = "dept. %s" % dept.encode(scu.SCO_ENCODING) # sco8
else:
r = get_user_list(with_olds=with_olds)
comm = "tous"
if with_olds:
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.userinfo", scodoc_dept=g.scodoc_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="""<h2>%d utilisateurs %s</h2>
<p class="help">Cliquer sur un nom pour changer son mot de passe</p>"""
% (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_olds=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_olds:
q = q.filter_by(is_old=False)
return q.all()
# def get_user_infos(user_list):
# return [ user_info(u) for u in user_list ]

View File

@ -26,7 +26,7 @@
{% if current_user.is_anonymous %} {% if current_user.is_anonymous %}
<li><a href="{{ url_for('auth.login') }}">Login</a></li> <li><a href="{{ url_for('auth.login') }}">Login</a></li>
{% else %} {% else %}
<li>{{current_user.username}}</li> <li>{{current_user.user_name}}</li>
<li><a href="{{ url_for('auth.logout') }}">Logout</a></li> <li><a href="{{ url_for('auth.logout') }}">Logout</a></li>
{% endif %} {% endif %}
</ul> </ul>

View File

@ -1,4 +1,4 @@
<p>Bonjour {{ user.username }},</p> <p>Bonjour {{ user.user_name }},</p>
<p> <p>
Pour réinitialiser votre mot de passe ScoDoc, Pour réinitialiser votre mot de passe ScoDoc,
<a href="{{ url_for('auth.reset_password', token=token, _external=True) }}"> <a href="{{ url_for('auth.reset_password', token=token, _external=True) }}">

View File

@ -1,4 +1,4 @@
Bonjour {{ user.username }}, Bonjour {{ user.user_name }},
Pour réinitialiser votre mot de passe ScoDoc, suivre le lien: Pour réinitialiser votre mot de passe ScoDoc, suivre le lien:

View File

@ -5,9 +5,10 @@ from flask import Blueprint
scolar_bp = Blueprint("scolar", __name__) scolar_bp = Blueprint("scolar", __name__)
notes_bp = Blueprint("notes", __name__) notes_bp = Blueprint("notes", __name__)
users_bp = Blueprint("users", __name__)
absences_bp = Blueprint("absences", __name__) absences_bp = Blueprint("absences", __name__)
essais_bp = Blueprint("essais", __name__) essais_bp = Blueprint("essais", __name__)
from app.views import notes, scolar, absences, essais from app.views import notes, scolar, absences, users, essais
scolar.context.Notes = notes.context # XXX transitoire #sco8 scolar.context.Notes = notes.context # XXX transitoire #sco8

View File

@ -123,6 +123,7 @@ from app.scodoc import sco_synchro_etuds
from app.scodoc import sco_tag_module from app.scodoc import sco_tag_module
from app.scodoc import sco_ue_external from app.scodoc import sco_ue_external
from app.scodoc import sco_undo_notes from app.scodoc import sco_undo_notes
from app.scodoc import sco_users
from app.scodoc.gen_tables import GenTable from app.scodoc.gen_tables import GenTable
from app.scodoc.sco_pdf import PDFLOCK from app.scodoc.sco_pdf import PDFLOCK
from app.scodoc.sco_permissions import Permission from app.scodoc.sco_permissions import Permission
@ -712,7 +713,7 @@ def edit_enseignants_form(context, REQUEST, moduleimpl_id):
footer = html_sco_header.sco_footer(context, REQUEST) footer = html_sco_header.sco_footer(context, REQUEST)
# Liste des enseignants avec forme pour affichage / saisie avec suggestion # Liste des enseignants avec forme pour affichage / saisie avec suggestion
userlist = context.Users.get_userlist() userlist = sco_users.get_user_list()
login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)" login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)"
for u in userlist: for u in userlist:
login2display[u["user_name"]] = u["nomplogin"] login2display[u["user_name"]] = u["nomplogin"]
@ -821,9 +822,9 @@ def edit_moduleimpl_resp(context, REQUEST, moduleimpl_id):
bodyOnLoad="init_tf_form('')", bodyOnLoad="init_tf_form('')",
) )
] ]
help = """<p class="help">Taper le début du nom de l'enseignant.</p>""" help_str = """<p class="help">Taper le début du nom de l'enseignant.</p>"""
# Liste des enseignants avec forme pour affichage / saisie avec suggestion # Liste des enseignants avec forme pour affichage / saisie avec suggestion
userlist = context.Users.get_userlist() userlist = [sco_users.user_info(u) for u in sco_users.get_user_list()]
login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)" login2display = {} # user_name : forme pour affichage = "NOM Prenom (login)"
for u in userlist: for u in userlist:
login2display[u["user_name"]] = u["nomplogin"] login2display[u["user_name"]] = u["nomplogin"]
@ -863,7 +864,10 @@ def edit_moduleimpl_resp(context, REQUEST, moduleimpl_id):
) )
if tf[0] == 0: if tf[0] == 0:
return ( return (
"\n".join(H) + tf[1] + help + html_sco_header.sco_footer(context, REQUEST) "\n".join(H)
+ tf[1]
+ help_str
+ html_sco_header.sco_footer(context, REQUEST)
) )
elif tf[0] == -1: elif tf[0] == -1:
return REQUEST.RESPONSE.redirect( return REQUEST.RESPONSE.redirect(

85
app/views/users.py Normal file
View File

@ -0,0 +1,85 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# ScoDoc
#
# 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
#
##############################################################################
"""
Module users: interface gestion utilisateurs
-écriture pour Flask ScoDoc7 / ZScoUsers.py
Vues s'appuyant sur auth et sco_users
Emmanuel Viennet, 2021
"""
from flask import g
from flask import current_app
from app.auth.models import Permission
from app.auth.models import User
from app.decorators import (
scodoc7func,
ScoDoc7Context,
permission_required,
admin_required,
login_required,
)
from app.scodoc import sco_users
from app.views import users_bp as bp
context = ScoDoc7Context("users") # sco8
# ------- Fonctions vraiment spécifiques à ScoDoc:
@bp.route("/")
@bp.route("/index_html")
@permission_required(Permission.ScoUsersView)
@scodoc7func(context)
def index_html(context, REQUEST, all_depts=False, with_olds=False, format="html"):
return sco_users.index_html(
context,
REQUEST=REQUEST,
all_depts=all_depts,
with_olds=with_olds,
format=format,
)
@bp.route("/create_user_form")
def create_user_form():
raise NotImplementedError()
@bp.route("/import_users_form")
def import_users_form():
raise NotImplementedError()
@bp.route("/userinfo")
def userinfo():
raise NotImplementedError()

View File

@ -8,7 +8,7 @@ Usage:
check_zope_usage.py publishedmethods.csv string-constants.txt check_zope_usage.py publishedmethods.csv string-constants.txt
publishedmethods.csv : fichier texte, module et un nom de méthode / ligne publishedmethods.csv : fichier texte, module et un nom de méthode / ligne
ZScoUsers get_userlist ZScoUsers get_user_list
comme extrait par zopelistmethods.py comme extrait par zopelistmethods.py
string-constants.txt : les constantes chaines, extraites par extract_code_strings.py string-constants.txt : les constantes chaines, extraites par extract_code_strings.py

View File

@ -1,13 +1,18 @@
alembic==1.5.5 alembic==1.5.5
astroid==1.6.6
attrdict==2.0.1 attrdict==2.0.1
Babel==2.9.0 Babel==2.9.0
backports.functools-lru-cache==1.6.4
blinker==1.4 blinker==1.4
certifi==2021.5.30 certifi==2021.5.30
chardet==4.0.0 chardet==4.0.0
click==7.1.2 click==7.1.2
configparser==4.0.2
cracklib==2.9.3
dnspython==1.16.0 dnspython==1.16.0
dominate==2.6.0 dominate==2.6.0
email-validator==1.1.2 email-validator==1.1.2
enum34==1.1.10
Flask==1.1.4 Flask==1.1.4
Flask-Babel==2.0.0 Flask-Babel==2.0.0
Flask-Bootstrap==3.3.7.1 Flask-Bootstrap==3.3.7.1
@ -17,18 +22,23 @@ Flask-Migrate==2.7.0
Flask-Moment==0.11.0 Flask-Moment==0.11.0
Flask-SQLAlchemy==2.4.4 Flask-SQLAlchemy==2.4.4
Flask-WTF==0.14.3 Flask-WTF==0.14.3
futures==3.3.0
icalendar==4.0.7 icalendar==4.0.7
idna==2.10 idna==2.10
isort==4.3.21
itsdangerous==1.1.0 itsdangerous==1.1.0
jaxml==3.2 jaxml==3.2
Jinja2==2.11.2 Jinja2==2.11.2
lazy-object-proxy==1.6.0
Mako==1.1.4 Mako==1.1.4
MarkupSafe==1.1.1 MarkupSafe==1.1.1
mccabe==0.6.1
Pillow==6.2.2 Pillow==6.2.2
pkg-resources==0.0.0 pkg-resources==0.0.0
psycopg2==2.8.6 psycopg2==2.8.6
pyExcelerator==0.6.3a0 pyExcelerator==0.6.3a0
PyJWT==1.7.1 PyJWT==1.7.1
pylint==1.9.5
PyRSS2Gen==1.1 PyRSS2Gen==1.1
python-dateutil==2.8.1 python-dateutil==2.8.1
python-dotenv==0.15.0 python-dotenv==0.15.0
@ -36,6 +46,7 @@ python-editor==1.0.4
pytz==2021.1 pytz==2021.1
reportlab==3.5.59 reportlab==3.5.59
requests==2.25.1 requests==2.25.1
singledispatch==3.6.2
six==1.15.0 six==1.15.0
SQLAlchemy==1.3.23 SQLAlchemy==1.3.23
stripogram==1.5 stripogram==1.5
@ -43,4 +54,5 @@ typing==3.7.4.3
urllib3==1.26.5 urllib3==1.26.5
visitor==0.1.3 visitor==0.1.3
Werkzeug==1.0.1 Werkzeug==1.0.1
wrapt==1.12.1
WTForms==2.3.3 WTForms==2.3.3

View File

@ -52,14 +52,14 @@ def user_db_init():
click.echo("created initial roles") click.echo("created initial roles")
# Ensure that admin exists # Ensure that admin exists
if Config.SCODOC_ADMIN_MAIL: if Config.SCODOC_ADMIN_MAIL:
admin_username = Config.SCODOC_ADMIN_LOGIN admin_user_name = Config.SCODOC_ADMIN_LOGIN
user = User.query.filter_by(username=admin_username).first() user = User.query.filter_by(user_name=admin_user_name).first()
if not user: if not user:
user = User(username=admin_username, email=Config.SCODOC_ADMIN_MAIL) user = User(user_name=admin_user_name, email=Config.SCODOC_ADMIN_MAIL)
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
click.echo( click.echo(
"created initial admin user, login: {u.username}, email: {u.email}".format( "created initial admin user, login: {u.user_name}, email: {u.email}".format(
u=user u=user
) )
) )
@ -90,16 +90,16 @@ def user_create(username, role, dept):
if not r: if not r:
sys.stderr.write("user_create: role {r} does not exists".format(r=r)) sys.stderr.write("user_create: role {r} does not exists".format(r=r))
return 1 return 1
u = User.query.filter_by(username=username).first() u = User.query.filter_by(user_name=username).first()
if u: if u:
sys.stderr.write("user_create: user {u} already exists".format(u=u)) sys.stderr.write("user_create: user {u} already exists".format(u=u))
return 2 return 2
u = User(username=username) u = User(user_name=username, dept=dept)
u.add_role(r, dept) u.add_role(r, dept)
db.session.add(u) db.session.add(u)
db.session.commit() db.session.commit()
click.echo( click.echo(
"created user, login: {u.username}, with role {r} in dept. {dept}".format( "created user, login: {u.user_name}, with role {r} in dept. {dept}".format(
u=u, r=r, dept=dept u=u, r=r, dept=dept
) )
) )
@ -113,7 +113,7 @@ def user_password(username, password=None):
if not password: if not password:
sys.stderr.write("user_password: missing password") sys.stderr.write("user_password: missing password")
return 1 return 1
u = User.query.filter_by(username=username).first() u = User.query.filter_by(user_name=username).first()
if not u: if not u:
sys.stderr.write("user_password: user {} does not exists".format(username)) sys.stderr.write("user_password: user {} does not exists".format(username))
return 1 return 1

View File

@ -98,5 +98,5 @@ class FakeUsers(object):
"date_expiration": None, "date_expiration": None,
} }
def get_userlist(self, dept=None, with_olds=False): def get_user_list(self, dept=None, with_olds=False):
return [self.user_info()] return [self.user_info()]

View File

@ -30,7 +30,7 @@ class UserModelCase(unittest.TestCase):
db.drop_all() db.drop_all()
def test_password_hashing(self): def test_password_hashing(self):
u = User(username="susan") u = User(user_name="susan")
u.set_password("cat") u.set_password("cat")
self.assertFalse(u.check_password("dog")) self.assertFalse(u.check_password("dog"))
self.assertTrue(u.check_password("cat")) self.assertTrue(u.check_password("cat"))
@ -68,7 +68,7 @@ class UserModelCase(unittest.TestCase):
dept = "XX" dept = "XX"
perm = Permission.ScoAbsChange perm = Permission.ScoAbsChange
perm2 = Permission.ScoView perm2 = Permission.ScoView
u = User(username="un enseignant") u = User(user_name="un enseignant")
db.session.add(u) db.session.add(u)
self.assertFalse(u.has_permission(perm, dept)) self.assertFalse(u.has_permission(perm, dept))
r = Role.get_named_role("Ens") r = Role.get_named_role("Ens")
@ -76,7 +76,7 @@ class UserModelCase(unittest.TestCase):
r = Role(name="Ens", permissions=perm) r = Role(name="Ens", permissions=perm)
u.add_role(r, dept) u.add_role(r, dept)
self.assertTrue(u.has_permission(perm, dept)) self.assertTrue(u.has_permission(perm, dept))
u = User(username="un autre") u = User(user_name="un autre")
u.add_role(r, dept) u.add_role(r, dept)
db.session.add(u) db.session.add(u)
db.session.commit() db.session.commit()
@ -86,7 +86,7 @@ class UserModelCase(unittest.TestCase):
r2 = Role(name="Secr", dept=dept, permissions=perm2) r2 = Role(name="Secr", dept=dept, permissions=perm2)
u.add_roles([r, r2], dept) u.add_roles([r, r2], dept)
self.assertTrue(len(u.roles) == 2) self.assertTrue(len(u.roles) == 2)
u = User(username="encore un") u = User(user_name="encore un")
db.session.add(u) db.session.add(u)
db.session.commit() db.session.commit()
u.set_roles([r, r2], dept) u.set_roles([r, r2], dept)
@ -101,7 +101,7 @@ class UserModelCase(unittest.TestCase):
def test_user_admin(self): def test_user_admin(self):
dept = "XX" dept = "XX"
perm = 0x1234 # a random perm perm = 0x1234 # a random perm
u = User(username="un admin", email=current_app.config["SCODOC_ADMIN_MAIL"]) u = User(user_name="un admin", email=current_app.config["SCODOC_ADMIN_MAIL"])
db.session.add(u) db.session.add(u)
self.assertTrue(len(u.roles) == 1) self.assertTrue(len(u.roles) == 1)
self.assertTrue(u.has_permission(perm, dept)) self.assertTrue(u.has_permission(perm, dept))