Merge branch 'jmplace-change_email'

This commit is contained in:
Emmanuel Viennet 2021-10-17 11:20:27 +02:00
commit 29ec51c001
11 changed files with 238 additions and 136 deletions

View File

@ -33,7 +33,7 @@ token_auth = HTTPTokenAuth()
@basic_auth.verify_password @basic_auth.verify_password
def verify_password(username, password): def verify_password(username, password):
user = User.query.filter_by(username=username).first() user = User.query.filter_by(user_name=username).first()
if user and user.check_password(password): if user and user.check_password(password):
return user return user

View File

@ -43,8 +43,11 @@ class UserCreationForm(FlaskForm):
class ResetPasswordRequestForm(FlaskForm): class ResetPasswordRequestForm(FlaskForm):
email = StringField(_l("Email"), validators=[DataRequired(), Email()]) email = StringField(
submit = SubmitField(_l("Valider ce mot de passe")) _l("Adresse email associée à votre compte ScoDoc:"),
validators=[DataRequired(), Email()],
)
submit = SubmitField(_l("Envoyer"))
class ResetPasswordForm(FlaskForm): class ResetPasswordForm(FlaskForm):

View File

@ -98,7 +98,9 @@ def reset_password_request():
current_app.logger.info( current_app.logger.info(
"reset_password_request: for unkown user '{}'".format(form.email.data) "reset_password_request: for unkown user '{}'".format(form.email.data)
) )
flash(_("Voir les instructions envoyées par mail")) flash(
_("Voir les instructions envoyées par mail (pensez à regarder vos spams)")
)
return redirect(url_for("auth.login")) return redirect(url_for("auth.login"))
return render_template( return render_template(
"auth/reset_password_request.html", title=_("Reset Password"), form=form "auth/reset_password_request.html", title=_("Reset Password"), form=form

View File

@ -32,6 +32,7 @@
import re import re
from flask import url_for, g, request from flask import url_for, g, request
from flask.templating import render_template
from flask_login import current_user from flask_login import current_user
@ -271,102 +272,6 @@ def user_info(user_name_or_id=None, user=None):
return info return info
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")
H = [
html_sco_header.sco_header(
page_title="Utilisateur %s" % user.user_name,
)
]
F = html_sco_header.sco_footer()
H.append("<h2>Utilisateur: %s" % user.user_name)
info = user.to_dict()
if info:
H.append(" (%(status_txt)s)" % info)
H.append("</h2>")
if not info:
H.append(
"<p>L' utilisateur '%s' n'est pas défini dans ce module.</p>" % user_name
)
if user.has_permission(Permission.ScoEditAllNotes, dept):
H.append("<p>(il peut modifier toutes les notes de %s)</p>" % dept)
if user.has_permission(Permission.ScoEditAllEvals, dept):
H.append("<p>(il peut modifier toutes les évaluations de %s)</p>" % dept)
if user.has_permission(Permission.ScoImplement, dept):
H.append("<p>(il peut creer des formations en %s)</p>" % dept)
else:
H.append(
"""<p>
<b>Login :</b> %(user_name)s<br/>
<b>Nom :</b> %(nom)s<br/>
<b>Prénom :</b> %(prenom)s<br/>
<b>Mail :</b> %(email)s<br/>
<b>Roles :</b> %(roles_string)s<br/>
<b>Dept :</b> %(dept)s<br/>
<b>Dernière modif mot de passe:</b> %(date_modif_passwd)s<br/>
<b>Date d'expiration:</b> %(date_expiration)s
<p><ul>
<li><a class="stdlink" href="form_change_password?user_name=%(user_name)s">changer le mot de passe</a></li>"""
% info
)
if current_user.has_permission(Permission.ScoUsersAdmin, dept):
H.append(
f"""
<li><a class="stdlink" href="{url_for('users.create_user_form', scodoc_dept=g.scodoc_dept,
user_name=user.user_name, edit=1)}">modifier ce compte</a>
</li>
<li><a class="stdlink" href="{url_for('users.toggle_active_user', scodoc_dept=g.scodoc_dept,
user_name=user.user_name)
}">{"désactiver" if user.active else "activer"} ce compte</a>
</li>
"""
% info
)
H.append("</ul>")
if current_user.user_name == user_name:
H.append(
'<p><b>Se déconnecter: <a class="stdlink" href="%s">logout</a></b></p>'
% url_for("auth.logout")
)
# Liste des permissions
H.append(
'<div class="permissions"><p>Permissions de cet utilisateur dans le département %s:</p><ul>'
% dept
)
for p in Permission.description:
perm = getattr(Permission, p)
if user.has_permission(perm, dept):
b = "oui"
else:
b = "non"
H.append("<li>%s : %s</li>" % (Permission.description[p], b))
H.append("</ul></div>")
if current_user.has_permission(Permission.ScoUsersAdmin, dept):
H.append(
'<p><a class="stdlink" href="%s">Liste de tous les utilisateurs</a></p>'
% url_for("users.index_html", scodoc_dept=g.scodoc_dept)
)
return "\n".join(H) + F
def check_modif_user( def check_modif_user(
edit, edit,
enforce_optionals=False, enforce_optionals=False,

View File

@ -0,0 +1,52 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% macro render_field(field) %}
<tr style="">
<td class="wtf-field">{{ field.label }}</td>
<td class="wtf-field">{{ field(**kwargs)|safe }}
{% if field.errors %}
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
{% endif %}
</td>
</tr>
{% endmacro %}
{% block app_content %}
<h1>Modification du compte ScoDoc <tt>{{form.user_name.data}}</tt></h1>
<div class="help">
<p>Identifiez-vous avez votre mot de passe actuel</p>
<p>Vous pouvez changer le mot de passe et/ou l'adresse email.</p>
<p>Les champs vides ne seront pas changés.</p>
</div>
<form method=post>
{{ form.user_name }}
{{ form.csrf_token }}
<table class="tf"><tbody>
{{ render_field(form.old_password, size=14,
style="padding:1px; margin-left: 1em; margin-top: 4px;") }}
{{ render_field(form.new_password, size=14,
style="padding:1px; margin-left: 1em; margin-top: 12px;") }}
{{ render_field(form.bis_password, size=14,
style="padding:1px; margin-left: 1em; margin-top: 4px;") }}
{{ render_field(form.email, size=40,
style="padding:1px; margin-top: 12px;margin-bottom: 16px; margin-left: 1em;") }}
</tbody></table>
<input type="submit" value="Valider">
<input type="submit" name="cancel" value="Annuler" style="margin-left: 1em;>
</form>
{#<div class="row" style="margin-top: 30px;">#}
{#<div class="col-md-4">Votre identifiant: <b>{{user.user_name}}</b></div>#}
{#</div>#}
{##}
{#<div class="row" style="margin-top: 30px;">#}
{# <div class="col-md-4">#}
{# {{ wtf.quick_form(form) }}#}
{# </div>#}
{#</div>#}
{% endblock %}

View File

@ -2,7 +2,7 @@
{% import 'bootstrap/wtf.html' as wtf %} {% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %} {% block app_content %}
<h1>Reset Password</h1> <h1>Demande d'un nouveau mot de passe</h1>
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
{{ wtf.quick_form(form) }} {{ wtf.quick_form(form) }}

View File

@ -0,0 +1,67 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<h2>Utilisateur: {{user.user_name}} ({{'actif' if user.active else 'fermé'}})</h2>
<p>
<b>Login :</b> {{user.user_name}}<br/>
<b>Nom :</b> {{user.nom or ""}}<br/>
<b>Prénom :</b> {{user.prenom or ""}}<br/>
<b>Mail :</b> {{user.email}}<br/>
<b>Roles :</b> {{user.get_roles_string()}}<br/>
<b>Dept :</b> {{user.dept or ""}}<br/>
<b>Dernière modif mot de passe:</b>
{{user.date_modif_passwd.isoformat() if user.date_modif_passwd else ""}}<br/>
<b>Date d'expiration:</b>
{{user.date_expiration.isoformat() if user.date_expiration else "(sans limite)"}}
<p>
<ul>
<li><a class="stdlink" href="{{
url_for( 'users.form_change_password',
scodoc_dept=g.scodoc_dept, user_name=user.user_name)
}}">modifier le mot de passe ou l'adresse mail</a>
</li>
{% if current_user.has_permission(Permission.ScoUsersAdmin, dept) %}
<li><a class="stdlink" href="{{
url_for('users.create_user_form', scodoc_dept=g.scodoc_dept,
user_name=user.user_name, edit=1)
}}">modifier ce compte</a>
</li>
<li><a class="stdlink" href="{{
url_for('users.toggle_active_user', scodoc_dept=g.scodoc_dept,
user_name=user.user_name)
}}">{{"désactiver" if user.active else "activer"}} ce compte</a>
</li>
{% endif %}
</ul>
{% if current_user.id == user.id %}
<p><b>Se déconnecter:
<a class="stdlink" href="{{url_for('auth.logout')}}">logout</a>
</b></p>
{% endif %}
{# Liste des permissions #}
<div class="permissions">
<p>Permissions de cet utilisateur dans le département {dept}:</p>
<ul>
{% for p in Permission.description %}
<li>{{Permission.description[p]}} :
{{
"oui" if user.has_permission(Permission.get_by_name(p), dept) else "non"
}}
</li>
{% endfor %}
</ul>
</div>
{% if current_user.has_permission(Permission.ScoUsersAdmin, dept) %}
<p><a class="stdlink" href="
{{url_for('users.index_html', scodoc_dept=g.scodoc_dept)}}
">Liste de tous les utilisateurs</a></p>
{% endif %}
{% endblock %}

View File

@ -22,12 +22,19 @@
</button> </button>
<a class="navbar-brand" href="{{ url_for('scodoc.index') }}">ScoDoc</a> <a class="navbar-brand" href="{{ url_for('scodoc.index') }}">ScoDoc</a>
</div> </div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
{% if current_user.is_administrator() %}
<ul class="nav navbar-nav"> <ul class="nav navbar-nav">
<li><a href="{{ url_for('scodoc.configuration') }}">configuration</a></li> {% if current_user.is_administrator() %}
</ul> <li><a href="{{ url_for('scodoc.configuration') }}">Configuration</a></li>
{% endif %} {% endif %}
{% if g.scodoc_dept %}
<li><a href="{{
url_for('scolar.index_html', scodoc_dept=g.scodoc_dept)
}}">Dept. {{ g.scodoc_dept }}</a></li>
{% endif %}
</ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
{% if current_user.is_anonymous %} {% if current_user.is_anonymous %}
<li><a href="{{ url_for('auth.login') }}">connexion</a></li> <li><a href="{{ url_for('auth.login') }}">connexion</a></li>

View File

@ -5,7 +5,7 @@
cliquez sur ce lien cliquez sur ce lien
</a>. </a>.
</p> </p>
<p>Vous pouvez aussi copier ce lien dans votre navigateur Web::</p> <p>Vous pouvez aussi copier ce lien dans votre navigateur Web:</p>
<p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p> <p>{{ url_for('auth.reset_password', token=token, _external=True) }}</p>
<p>Si vous n'avez pas demandé à réinitialiser votre mot de passe sur <p>Si vous n'avez pas demandé à réinitialiser votre mot de passe sur

View File

@ -38,10 +38,12 @@ import re
from xml.etree import ElementTree from xml.etree import ElementTree
import flask import flask
from flask import g, url_for, request, current_app from flask import g, url_for, request, current_app, flash
from flask import redirect, render_template from flask import redirect, render_template
from flask_login import current_user from flask_login import current_user
from wtforms import HiddenField, PasswordField, StringField, SubmitField
from wtforms.validators import DataRequired, Email, ValidationError, EqualTo
from app import db from app import db
from app.auth.forms import DeactivateUserForm from app.auth.forms import DeactivateUserForm
@ -69,6 +71,41 @@ from app.scodoc.sco_import_users import generate_password
from app.scodoc.sco_permissions_check import can_handle_passwd from app.scodoc.sco_permissions_check import can_handle_passwd
from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message from app.scodoc.TrivialFormulator import TrivialFormulator, tf_error_message
from app.views import users_bp as bp from app.views import users_bp as bp
from flask_wtf import FlaskForm
_ = lambda x: x # sans babel
_l = _
class ChangePasswordForm(FlaskForm):
user_name = HiddenField()
old_password = PasswordField(_l("Ancien mot de passe"))
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",
),
],
)
email = StringField(_l("Email"), validators=[DataRequired(), Email()])
submit = SubmitField()
cancel = SubmitField("Annuler")
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is not None and self.user_name.data != user.user_name:
raise ValidationError(_("Adresse e-mail invalide"))
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):
if not current_user.check_password(old_password.data):
raise ValidationError("Mot de passe actuel incorrect, ré-essayez")
@bp.route("/") @bp.route("/")
@ -644,8 +681,31 @@ def import_users_form():
@scodoc @scodoc
@permission_required(Permission.ScoUsersView) @permission_required(Permission.ScoUsersView)
@scodoc7func @scodoc7func
def user_info_page(user_name): def user_info_page(user_name=None):
return sco_users.user_info_page(user_name=user_name) """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,
)
@bp.route("/get_user_list_xml") @bp.route("/get_user_list_xml")
@ -676,7 +736,7 @@ def get_user_list_xml(dept=None, start="", limit=25):
return scu.send_file(data, mime=scu.XML_MIMETYPE) return scu.send_file(data, mime=scu.XML_MIMETYPE)
@bp.route("/form_change_password") @bp.route("/form_change_password", methods=["GET", "POST"])
@scodoc @scodoc
@permission_required(Permission.ScoView) @permission_required(Permission.ScoView)
@scodoc7func @scodoc7func
@ -685,36 +745,42 @@ def form_change_password(user_name=None):
Un utilisateur peut toujours changer son propre mot de passe. Un utilisateur peut toujours changer son propre mot de passe.
""" """
if not user_name: if not user_name:
u = current_user user = current_user
else: else:
u = User.query.filter_by(user_name=user_name).first() user = User.query.filter_by(user_name=user_name).first()
H = [html_sco_header.sco_header(user_check=False)]
F = html_sco_header.sco_footer()
# check access # check access
if not can_handle_passwd(u): if not can_handle_passwd(user):
return ( return "\n".join(
"\n".join(H) [
+ "<p>Vous n'avez pas la permission de changer ce mot de passe</p>" html_sco_header.sco_header(user_check=False),
+ F "<p>Vous n'avez pas la permission de changer ce mot de passe</p>",
html_sco_header.sco_footer(),
]
) )
# form = ChangePasswordForm(user_name=user.user_name, email=user.email)
H.append( destination = url_for(
"""<h2>Changement du mot de passe de <font color="red">%(nomplogin)s</font></h2> "users.user_info_page",
<p> scodoc_dept=g.scodoc_dept,
<form action="change_password" method="post"><table> user_name=user_name,
<tr><td>Nouveau mot de passe:</td><td><input type="password" size="14" name="password"/></td></tr> )
<tr><td>Confirmation: </td><td><input type="password" size="14" name="password2" /></td></tr> if request.method == "POST" and form.cancel.data: # cancel button clicked
</table> return redirect(destination)
<input type="hidden" value="%(user_name)s" name="user_name"> if form.validate_on_submit():
<input type="submit" value="Changer"> messages = []
</p> if form.new_password.data != "": # change password
<p class="help">Note: en ScoDoc 9, les utilisateurs peuvent changer eux-même leur mot de passe user.set_password(form.new_password.data)
en indiquant l'adresse mail associée à leur compte. messages.append("Mot de passe modifié")
</p> if form.email.data != user.email: # change email
""" user.email = form.email.data
% {"nomplogin": u.get_nomplogin(), "user_name": user_name} messages.append("Adresse email modifiée")
db.session.commit()
flash("\n".join(messages))
return redirect(destination)
return render_template(
"auth/change_password.html", form=form, title="Modification compte ScoDoc"
) )
return "\n".join(H) + F
@bp.route("/change_password", methods=["POST"]) @bp.route("/change_password", methods=["POST"])

View File

@ -1,7 +1,7 @@
# -*- mode: python -*- # -*- mode: python -*-
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
SCOVERSION = "9.0.53" SCOVERSION = "9.0.54"
SCONAME = "ScoDoc" SCONAME = "ScoDoc"