From f991ffdca5f1e9b3f021bd0c31e4f365574948d7 Mon Sep 17 00:00:00 2001 From: Emmanuel Viennet Date: Sat, 26 Jun 2021 21:57:54 +0200 Subject: [PATCH] =?UTF-8?q?d=C3=A9but=20impl=C3=A9mentation=20gestion=20ut?= =?UTF-8?q?ilisateurs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 39 +++++ app/__init__.py | 3 + app/auth/forms.py | 10 +- app/auth/models.py | 45 +++++- app/auth/routes.py | 12 +- app/decorators.py | 2 +- app/scodoc/sco_dept.py | 6 +- app/scodoc/sco_formsemestre_edit.py | 5 +- app/scodoc/sco_permissions_check.py | 25 +++ app/scodoc/sco_users.py | 205 +++++++++++++++++++++++- app/templates/base.html | 2 +- app/templates/email/reset_password.html | 2 +- app/templates/email/reset_password.txt | 2 +- app/views/__init__.py | 3 +- app/views/notes.py | 12 +- app/views/users.py | 85 ++++++++++ misc/check_zope_usage.py | 2 +- requirements.txt | 12 ++ scodoc.py | 16 +- scodoc_manager.py | 2 +- tests/test_users.py | 10 +- 21 files changed, 455 insertions(+), 45 deletions(-) create mode 100644 app/views/users.py diff --git a/README.md b/README.md index b05219bc8..c86756b66 100644 --- a/README.md +++ b/README.md @@ -51,9 +51,11 @@ Puis d'installer Flask comme suit: Installation: + apt-get install libcrack2-dev pip install flask # et pas mal d'autres paquets + donc utiliser: pip install -r requirements.txt @@ -62,6 +64,43 @@ pour régénerer ce fichier: 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 Installer le bon vieux `pyExcelerator` dans l'environnement: diff --git a/app/__init__.py b/app/__init__.py index 2c0d603d3..cb030ad51 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -68,12 +68,15 @@ def create_app(config_class=Config): from app.views import scolar_bp from app.views import notes_bp + from app.views import users_bp from app.views import absences_bp # https://scodoc.fr/ScoDoc/RT/Scolarite/... app.register_blueprint(scolar_bp, url_prefix="/ScoDoc//Scolarite") # https://scodoc.fr/ScoDoc/RT/Scolarite/Notes/... app.register_blueprint(notes_bp, url_prefix="/ScoDoc//Scolarite/Notes") + # https://scodoc.fr/ScoDoc/RT/Scolarite/Users/... + app.register_blueprint(users_bp, url_prefix="/ScoDoc//Scolarite/Users") # https://scodoc.fr/ScoDoc/RT/Scolarite/Absences/... app.register_blueprint( absences_bp, url_prefix="/ScoDoc//Scolarite/Absences" diff --git a/app/auth/forms.py b/app/auth/forms.py index 4dcb11831..bb201e26d 100644 --- a/app/auth/forms.py +++ b/app/auth/forms.py @@ -16,14 +16,14 @@ _l = _ class LoginForm(FlaskForm): - username = StringField(_l("Username"), validators=[DataRequired()]) + user_name = StringField(_l("Username"), validators=[DataRequired()]) password = PasswordField(_l("Password"), validators=[DataRequired()]) remember_me = BooleanField(_l("Remember Me")) submit = SubmitField(_l("Sign In")) class UserCreationForm(FlaskForm): - username = StringField(_l("Username"), validators=[DataRequired()]) + user_name = StringField(_l("Username"), validators=[DataRequired()]) email = StringField(_l("Email"), validators=[DataRequired(), Email()]) password = PasswordField(_l("Password"), validators=[DataRequired()]) password2 = PasswordField( @@ -31,10 +31,10 @@ class UserCreationForm(FlaskForm): ) submit = SubmitField(_l("Register")) - def validate_username(self, username): - user = User.query.filter_by(username=username.data).first() + def validate_user_name(self, user_name): + user = User.query.filter_by(user_name=user_name.data).first() 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): user = User.query.filter_by(email=email.data).first() diff --git a/app/auth/models.py b/app/auth/models.py index a1db43e62..a769a592b 100644 --- a/app/auth/models.py +++ b/app/auth/models.py @@ -26,13 +26,23 @@ class User(UserMixin, db.Model): """ScoDoc users, handled by Flask / SQLAlchemy""" 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) + + 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)) - about_me = db.Column(db.String(140)) 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_expiration = db.Column(db.DateTime) + roles = db.relationship("Role", secondary="user_role", viewonly=True) Permission = Permission @@ -52,10 +62,10 @@ class User(UserMixin, db.Model): current_app.logger.info("creating user with roles={}".format(self.roles)) def __repr__(self): - return "".format(self.username) + return "".format(u=self) def __str__(self): - return self.username + return self.user_name def set_password(self, password): "Set password" @@ -91,17 +101,32 @@ class User(UserMixin, db.Model): def to_dict(self, include_email=False): 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, - "username": self.username, + "is_old": self.is_old, + "status_txt": "(ancien)" if self.is_old else "", "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: data["email"] = self.email return data 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: setattr(self, field, data[field]) if new_user and "password" in data: @@ -169,6 +194,12 @@ class User(UserMixin, db.Model): for role in self.roles: 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): return self.has_permission(Permission.ScoSuperAdmin, None) diff --git a/app/auth/routes.py b/app/auth/routes.py index 96cc40c6f..b45e05c2f 100644 --- a/app/auth/routes.py +++ b/app/auth/routes.py @@ -15,9 +15,11 @@ from app.auth.forms import ( ResetPasswordRequestForm, ResetPasswordForm, ) +from app.auth.models import Permission from app.auth.models import User 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 _l = _ @@ -29,9 +31,9 @@ def login(): return redirect(url_for("main.index")) form = LoginForm() 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): - flash(_("Invalid username or password")) + flash(_("Invalid user_name or password")) return redirect(url_for("auth.login")) login_user(user, remember=form.remember_me.data) next_page = request.args.get("next") @@ -53,11 +55,11 @@ def create_user(): "Form creating new user" form = UserCreationForm() 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) db.session.add(user) db.session.commit() - flash("User {} created".format(user.username)) + flash("User {} created".format(user.user_name)) return redirect(url_for("main.index")) return render_template( "auth/register.html", title=u"Création utilisateur", form=form diff --git a/app/decorators.py b/app/decorators.py index cab531a80..720bd4e75 100644 --- a/app/decorators.py +++ b/app/decorators.py @@ -26,7 +26,7 @@ class ZUser(object): def __init__(self): "create, based on `flask_login.current_user`" - self.username = current_user.username + self.username = current_user.user_name def __str__(self): return self.username diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py index 8461b890b..6d5471f25 100644 --- a/app/scodoc/sco_dept.py +++ b/app/scodoc/sco_dept.py @@ -27,6 +27,9 @@ """Page accueil département (liste des semestres, etc) """ + +from flask import g + import app.scodoc.sco_utils as scu from app.scodoc.gen_tables import GenTable 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_preferences from app.scodoc import sco_up_to_date +from app.scodoc import sco_users 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 # 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( """

Aucun utilisateur défini !

Pour définir des utilisateurs passez par la page Utilisateurs. diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py index a199c6fbb..1a95d8977 100644 --- a/app/scodoc/sco_formsemestre_edit.py +++ b/app/scodoc/sco_formsemestre_edit.py @@ -43,6 +43,7 @@ from app.scodoc import sco_compute_moy from app.scodoc import sco_edit_matiere from app.scodoc import sco_edit_module from app.scodoc import sco_edit_ue +from app.scodoc import sco_etud from app.scodoc import sco_evaluations from app.scodoc import sco_formations 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_portal_apogee from app.scodoc import sco_preferences -from app.scodoc import sco_etud +from app.scodoc import sco_users 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 - 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)" for u in userlist: login2display[u["user_name"]] = u["nomplogin"] diff --git a/app/scodoc/sco_permissions_check.py b/app/scodoc/sco_permissions_check.py index a780a4690..60b9a9a4b 100644 --- a/app/scodoc/sco_permissions_check.py +++ b/app/scodoc/sco_permissions_check.py @@ -3,6 +3,8 @@ """Functions checking permissions for some common operations """ +from flask import g +from flask_login import current_user import app.scodoc.notesdb as ndb from app.scodoc.sco_permissions import Permission @@ -188,3 +190,26 @@ def can_change_groups(context, REQUEST, formsemestre_id): if uid in sem["responsables"]: return True 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 \ No newline at end of file diff --git a/app/scodoc/sco_users.py b/app/scodoc/sco_users.py index 2390fa67e..ac422791a 100644 --- a/app/scodoc/sco_users.py +++ b/app/scodoc/sco_users.py @@ -28,12 +28,48 @@ """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 + +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 +from app.scodoc.sco_exceptions import ( + AccessDenied, + ScoException, + ScoValueError, + ScoInvalidDateError, + ScoLockedFormError, + ScoGenError, +) + def is_valid_password(cleartxt): """Check password. @@ -50,3 +86,170 @@ def is_valid_password(cleartxt): return True except ValueError: 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("

Gestion des utilisateurs

") + + if current_user.has_permission(Permission.ScoUsersAdmin, g.scodoc_dept): + 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_olds: + olds_checked = "checked" + else: + olds_checked = "" + H.append( + """

+ Tous les départements + Avec anciens utilisateurs +

""" + % (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="""

%d utilisateurs %s

+

Cliquer sur un nom pour changer son mot de passe

""" + % (len(r), comm), + html_class="table_leftalign list_users", + html_with_td_classes=True, + html_sortable=True, + base_url="%s?all=%s" % (REQUEST.URL0, all), + pdf_link=False, # table is too wide to fit in a paper page => disable pdf + preferences=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 ] diff --git a/app/templates/base.html b/app/templates/base.html index 15a9ee46b..33c6df8bb 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -26,7 +26,7 @@ {% if current_user.is_anonymous %}
  • Login
  • {% else %} -
  • {{current_user.username}}
  • +
  • {{current_user.user_name}}
  • Logout
  • {% endif %} diff --git a/app/templates/email/reset_password.html b/app/templates/email/reset_password.html index e928c6c9f..430d53ded 100644 --- a/app/templates/email/reset_password.html +++ b/app/templates/email/reset_password.html @@ -1,4 +1,4 @@ -

    Bonjour {{ user.username }},

    +

    Bonjour {{ user.user_name }},

    Pour réinitialiser votre mot de passe ScoDoc, diff --git a/app/templates/email/reset_password.txt b/app/templates/email/reset_password.txt index ea5f14fda..2136a52d6 100644 --- a/app/templates/email/reset_password.txt +++ b/app/templates/email/reset_password.txt @@ -1,4 +1,4 @@ -Bonjour {{ user.username }}, +Bonjour {{ user.user_name }}, Pour réinitialiser votre mot de passe ScoDoc, suivre le lien: diff --git a/app/views/__init__.py b/app/views/__init__.py index 08815db7f..d390b2fd8 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -5,9 +5,10 @@ from flask import Blueprint scolar_bp = Blueprint("scolar", __name__) notes_bp = Blueprint("notes", __name__) +users_bp = Blueprint("users", __name__) absences_bp = Blueprint("absences", __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 diff --git a/app/views/notes.py b/app/views/notes.py index e62a3cce7..a02976fac 100644 --- a/app/views/notes.py +++ b/app/views/notes.py @@ -123,6 +123,7 @@ from app.scodoc import sco_synchro_etuds from app.scodoc import sco_tag_module from app.scodoc import sco_ue_external from app.scodoc import sco_undo_notes +from app.scodoc import sco_users from app.scodoc.gen_tables import GenTable from app.scodoc.sco_pdf import PDFLOCK 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) # 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)" for u in userlist: login2display[u["user_name"]] = u["nomplogin"] @@ -821,9 +822,9 @@ def edit_moduleimpl_resp(context, REQUEST, moduleimpl_id): bodyOnLoad="init_tf_form('')", ) ] - help = """

    Taper le début du nom de l'enseignant.

    """ + help_str = """

    Taper le début du nom de l'enseignant.

    """ # 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)" for u in userlist: login2display[u["user_name"]] = u["nomplogin"] @@ -863,7 +864,10 @@ def edit_moduleimpl_resp(context, REQUEST, moduleimpl_id): ) if tf[0] == 0: 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: return REQUEST.RESPONSE.redirect( diff --git a/app/views/users.py b/app/views/users.py new file mode 100644 index 000000000..456f62bd5 --- /dev/null +++ b/app/views/users.py @@ -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 +ré-é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() diff --git a/misc/check_zope_usage.py b/misc/check_zope_usage.py index d0b8fa4ab..ea6d723b3 100755 --- a/misc/check_zope_usage.py +++ b/misc/check_zope_usage.py @@ -8,7 +8,7 @@ Usage: check_zope_usage.py publishedmethods.csv string-constants.txt publishedmethods.csv : fichier texte, module et un nom de méthode / ligne - ZScoUsers get_userlist + ZScoUsers get_user_list comme extrait par zopelistmethods.py string-constants.txt : les constantes chaines, extraites par extract_code_strings.py diff --git a/requirements.txt b/requirements.txt index addb19ba9..ae1156626 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,18 @@ alembic==1.5.5 +astroid==1.6.6 attrdict==2.0.1 Babel==2.9.0 +backports.functools-lru-cache==1.6.4 blinker==1.4 certifi==2021.5.30 chardet==4.0.0 click==7.1.2 +configparser==4.0.2 +cracklib==2.9.3 dnspython==1.16.0 dominate==2.6.0 email-validator==1.1.2 +enum34==1.1.10 Flask==1.1.4 Flask-Babel==2.0.0 Flask-Bootstrap==3.3.7.1 @@ -17,18 +22,23 @@ Flask-Migrate==2.7.0 Flask-Moment==0.11.0 Flask-SQLAlchemy==2.4.4 Flask-WTF==0.14.3 +futures==3.3.0 icalendar==4.0.7 idna==2.10 +isort==4.3.21 itsdangerous==1.1.0 jaxml==3.2 Jinja2==2.11.2 +lazy-object-proxy==1.6.0 Mako==1.1.4 MarkupSafe==1.1.1 +mccabe==0.6.1 Pillow==6.2.2 pkg-resources==0.0.0 psycopg2==2.8.6 pyExcelerator==0.6.3a0 PyJWT==1.7.1 +pylint==1.9.5 PyRSS2Gen==1.1 python-dateutil==2.8.1 python-dotenv==0.15.0 @@ -36,6 +46,7 @@ python-editor==1.0.4 pytz==2021.1 reportlab==3.5.59 requests==2.25.1 +singledispatch==3.6.2 six==1.15.0 SQLAlchemy==1.3.23 stripogram==1.5 @@ -43,4 +54,5 @@ typing==3.7.4.3 urllib3==1.26.5 visitor==0.1.3 Werkzeug==1.0.1 +wrapt==1.12.1 WTForms==2.3.3 diff --git a/scodoc.py b/scodoc.py index 25559d07a..65e168687 100755 --- a/scodoc.py +++ b/scodoc.py @@ -52,14 +52,14 @@ def user_db_init(): click.echo("created initial roles") # Ensure that admin exists if Config.SCODOC_ADMIN_MAIL: - admin_username = Config.SCODOC_ADMIN_LOGIN - user = User.query.filter_by(username=admin_username).first() + admin_user_name = Config.SCODOC_ADMIN_LOGIN + user = User.query.filter_by(user_name=admin_user_name).first() 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.commit() 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 ) ) @@ -90,16 +90,16 @@ def user_create(username, role, dept): if not r: sys.stderr.write("user_create: role {r} does not exists".format(r=r)) return 1 - u = User.query.filter_by(username=username).first() + u = User.query.filter_by(user_name=username).first() if u: sys.stderr.write("user_create: user {u} already exists".format(u=u)) return 2 - u = User(username=username) + u = User(user_name=username, dept=dept) u.add_role(r, dept) db.session.add(u) db.session.commit() 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 ) ) @@ -113,7 +113,7 @@ def user_password(username, password=None): if not password: sys.stderr.write("user_password: missing password") return 1 - u = User.query.filter_by(username=username).first() + u = User.query.filter_by(user_name=username).first() if not u: sys.stderr.write("user_password: user {} does not exists".format(username)) return 1 diff --git a/scodoc_manager.py b/scodoc_manager.py index 9fb8ddf8f..d3ed4d839 100644 --- a/scodoc_manager.py +++ b/scodoc_manager.py @@ -98,5 +98,5 @@ class FakeUsers(object): "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()] diff --git a/tests/test_users.py b/tests/test_users.py index e0a2f1159..2012131b3 100644 --- a/tests/test_users.py +++ b/tests/test_users.py @@ -30,7 +30,7 @@ class UserModelCase(unittest.TestCase): db.drop_all() def test_password_hashing(self): - u = User(username="susan") + u = User(user_name="susan") u.set_password("cat") self.assertFalse(u.check_password("dog")) self.assertTrue(u.check_password("cat")) @@ -68,7 +68,7 @@ class UserModelCase(unittest.TestCase): dept = "XX" perm = Permission.ScoAbsChange perm2 = Permission.ScoView - u = User(username="un enseignant") + u = User(user_name="un enseignant") db.session.add(u) self.assertFalse(u.has_permission(perm, dept)) r = Role.get_named_role("Ens") @@ -76,7 +76,7 @@ class UserModelCase(unittest.TestCase): r = Role(name="Ens", permissions=perm) u.add_role(r, dept) self.assertTrue(u.has_permission(perm, dept)) - u = User(username="un autre") + u = User(user_name="un autre") u.add_role(r, dept) db.session.add(u) db.session.commit() @@ -86,7 +86,7 @@ class UserModelCase(unittest.TestCase): r2 = Role(name="Secr", dept=dept, permissions=perm2) u.add_roles([r, r2], dept) self.assertTrue(len(u.roles) == 2) - u = User(username="encore un") + u = User(user_name="encore un") db.session.add(u) db.session.commit() u.set_roles([r, r2], dept) @@ -101,7 +101,7 @@ class UserModelCase(unittest.TestCase): def test_user_admin(self): dept = "XX" 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) self.assertTrue(len(u.roles) == 1) self.assertTrue(u.has_permission(perm, dept))