Merge branch 'dev' into preprod

This commit is contained in:
Darks 2020-07-25 18:07:30 +02:00
commit ff7618c9be
Signed by: Darks
GPG Key ID: F61F10FA138E797C
13 changed files with 112 additions and 50 deletions

View File

@ -72,7 +72,7 @@ class UpdateAccountForm(FlaskForm):
],
)
password = PasswordField(
'Mot de passe',
'Nouveau mot de passe',
validators=[
Optional(),
vd.password,

View File

@ -35,6 +35,10 @@ class User(UserMixin, db.Model):
# Other fields populated automatically through relations:
# <posts> relationship populated from the Post class.
# Minimum and maximum user name length
NAME_MINLEN = 3
NAME_MAXLEN = 32
__mapper_args__ = {
'polymorphic_identity': __tablename__,
'polymorphic_on': type
@ -54,7 +58,7 @@ class Guest(User):
id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
# Reusable username, cannot be chosen as the name of a member
# but will be distinguished at rendering time if a member take it later
name = db.Column(db.Unicode(64))
name = db.Column(db.Unicode(User.NAME_MAXLEN))
def __init__(self, name):
self.name = name
@ -73,8 +77,8 @@ class Member(User):
id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
# Primary attributes (needed for the system to work)
name = db.Column(db.Unicode(V5Config.USER_NAME_MAXLEN), index=True)
norm = db.Column(db.Unicode(V5Config.USER_NAME_MAXLEN), index=True,
name = db.Column(db.Unicode(User.NAME_MAXLEN), index=True)
norm = db.Column(db.Unicode(User.NAME_MAXLEN), index=True,
unique=True)
email = db.Column(db.Unicode(120), index=True, unique=True)
email_confirmed = db.Column(db.Boolean)

View File

@ -35,7 +35,8 @@ def edit_account():
else:
flash('Erreur lors de la modification', 'error')
return render('account/account.html', form=form)
return render('account/account.html', scripts=["+scripts/entropy.js"],
form=form)
@app.route('/compte/reinitialiser', methods=['GET', 'POST'])
@guest_only
@ -74,7 +75,8 @@ def reset_password(token):
else:
flash('Erreur lors de la modification', 'error')
return render('account/reset_password.html', form=form)
return render('account/reset_password.html',
scripts=["+scripts/entropy.js"], form=form)
@app.route('/compte/supprimer', methods=['GET', 'POST'])
@ -113,7 +115,8 @@ def register():
send_validation_mail(member.name, member.email)
return redirect(url_for('validation') + "?email=" + form.email.data)
return render('account/register.html', title='Register', form=form)
return render('account/register.html', title='Register',
scripts=["+scripts/entropy.js"], form=form)
@app.route('/inscription/validation', methods=['GET'])

View File

@ -107,9 +107,10 @@ def adm_edit_account(user_id):
for g in user.groups:
groups_owned.add(f"g{g.id}")
return render('admin/edit_account.html', user=user, form=form,
trophy_form=trophy_form, trophies_owned=trophies_owned,
group_form=group_form, groups_owned=groups_owned)
return render('admin/edit_account.html', scripts=["+scripts/entropy.js"],
user=user, form=form, trophy_form=trophy_form,
trophies_owned=trophies_owned, group_form=group_form,
groups_owned=groups_owned)
@app.route('/admin/compte/<user_id>/supprimer', methods=['GET', 'POST'])

View File

@ -50,6 +50,31 @@
resize: vertical;
}
.form progress.entropy {
display: none; /* display with Js enabled */
width: 100%; margin-top: 5px;
background: var(--background);
border: var(--border);
}
.form progress.entropy.low::-moz-progress-bar {
background: var(--error);
}
.form progress.entropy.low::-webkit-progress-bar {
background: var(--error);
}
.form progress.entropy.medium::-moz-progress-bar {
background: var(--warn);
}
.form progress.entropy.medium::-webkit-progress-bar {
background: var(--warn);
}
.form progress.entropy.high::-moz-progress-bar {
background: var(--ok);
}
.form progress.entropy.high::-webkit-progress-bar {
background: var(--ok);
}
.form input[type="checkbox"],
.form input[type="radio"] {
display: inline;

View File

@ -0,0 +1,42 @@
function entropy(password) {
var chars = [
"abcdefghijklmnopqrstuvwxyz",
"ABCDFEGHIJKLMNOPQRSTUVWXYZ",
"0123456789",
" !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~§", // OWASP special chars
"áàâéèêíìîóòôúùûç"
];
used = new Set();
for(c in password) {
for(k in chars) {
if(chars[k].includes(password[c])) {
used.add(chars[k]);
}
}
}
return Math.log(Math.pow(Array.from(used).join("").length, password.length)) / Math.log(2);
}
function update_entropy(ev) {
var i = document.querySelector(".entropy").previousElementSibling;
var p = document.querySelector(".entropy");
var e = entropy(i.value);
p.classList.remove('low');
p.classList.remove('medium');
p.classList.remove('high');
if(e < 60) {
p.classList.add('low');
} else if(e < 100) {
p.classList.add('medium');
} else {
p.classList.add('high');
}
p.value = e;
}
document.querySelector(".entropy").previousElementSibling.addEventListener('input', update_entropy);
document.querySelector(".entropy").style.display = "block";

View File

@ -30,6 +30,7 @@
<div>
{{ form.password.label }}
{{ form.password(placeholder='************') }}
<progress class="entropy" value="0" max="100"></progress>
{% for error in form.password.errors %}
<span class="msgerror">{{ error }}</span>
{% endfor %}

View File

@ -24,6 +24,7 @@
<div>
{{ form.password.label }}
{{ form.password() }}
<progress class="entropy" value="0" max="100"></progress>
{% for error in form.password.errors %}
<span class="msgerror">{{ error }}</span>
{% endfor %}

View File

@ -10,6 +10,7 @@
{{ form.password.label }}
<div class=desc>{{ form.password.description }}</div>
{{ form.password() }}
<progress class="entropy" value="0" max="100"></progress>
{% for error in form.password.errors %}
<span class="msgerror">{{ error }}</span>
{% endfor %}

View File

@ -45,6 +45,7 @@
{{ form.password.label }}
<div class=desc>{{ form.password.description }}</div>
{{ form.password(placeholder='************') }}
<progress class="entropy" value="0" max="100"></progress>
{% for error in form.password.errors %}
<span class="msgerror">{{ error }}</span>
{% endfor %}

View File

@ -36,14 +36,12 @@ def render(*args, styles=[], scripts=[], **kwargs):
]
for s in styles:
print(s[1:])
if s[0] == '-':
styles_.remove(s[1:])
if s[0] == '+':
styles_.append(s[1:])
for s in scripts:
print(s[1:])
if s[0] == '-':
scripts_.remove(s[1:])
if s[0] == '+':

View File

@ -1,9 +1,10 @@
from flask_login import current_user
from wtforms.validators import ValidationError
from PIL import Image
from app.models.users import Member
from app.models.users import Member, User
from app.utils.valid_name import valid_name
from app.utils.unicode_names import normalize
from math import log
import app.utils.ldap as ldap
from config import V5Config
@ -14,10 +15,10 @@ def name_valid(form, name):
msg = {
"too-short":
"Le nom d'utilisateur doit faire au moins "
f"{V5Config.USER_NAME_MINLEN} caractères.",
f"{User.USER_NAME_MINLEN} caractères.",
"too-long":
"Le nom d'utilisateur doit faire au plus "
f"{V5Config.USER_NAME_MAXLEN} caractères.",
f"{User.USER_NAME_MAXLEN} caractères.",
"cant-normalize":
"Ce nom d'utilisateur contient des caractères interdits. Les "
"caractères autorisés sont les lettres, lettres accentuées, "
@ -63,37 +64,26 @@ def password(form, password):
if len(password.data) == 0:
return
errors = []
if len(password.data) < V5Config.PASSWORD_MINLEN:
errors.append('Le mot de passe doit faire au moins '
f'{V5Config.PASSWORD_MINLEN} caractères.')
def entropy(password):
"""Estimate entropy of a password, in bits"""
# If you edit this function, please edit accordingly the JS one
# in static/script/entropy.js
chars = [
"abcdefghijklmnopqrstuvwxyz",
"ABCDFEGHIJKLMNOPQRSTUVWXYZ",
"0123456789",
" !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~§", # OWASP special chars
"áàâéèêíìîóòôúùûç",
]
used = set()
for c in password:
for i in chars:
if c in i:
used.add(i)
return log(len(''.join(used)) ** len(password), 2)
checks = set()
for c in password.data:
if c in "abcdefghijklmnopqrstuvwxyz":
checks.add('lower')
elif c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
checks.add('upper')
elif c in "0123456789":
checks.add('numeric')
else:
checks.add('other')
missing = []
if 'lower' not in checks:
missing.append('une minuscule')
if 'upper' not in checks:
missing.append('une majuscule')
if 'numeric' not in checks:
missing.append('un chiffre')
if 'other' not in checks:
missing.append('un caractère spécial')
if missing != []:
errors.append('Le mot de passe doit aussi contenir ' + ', '.join(missing) + '.')
if errors != []:
raise ValidationError(' '.join(errors))
if entropy(password.data) < 60:
raise ValidationError("Mot de passe pas assez complexe")
def avatar(form, avatar):

View File

@ -30,11 +30,6 @@ class DefaultConfig(object):
FORBIDDEN_USERNAMES = ["admin", "root", "webmaster", "contact"]
# Unauthorized message (@priv_required)
UNAUTHORIZED_MSG = "Vous n'avez pas l'autorisation d'effectuer cette action!"
# Minimum and maximum user name length
USER_NAME_MINLEN = 3
USER_NAME_MAXLEN = 32
# Minimum password length for new users and new passwords
PASSWORD_MINLEN = 10
# Maximum thread name length
THREAD_NAME_MAXLEN = 32
# Amount of comments per thread page